From 3f1f50c44847a0e76a95507c9566a35dfe809a78 Mon Sep 17 00:00:00 2001 From: Eike Ziller Date: Mon, 25 Sep 2017 11:57:34 +0200 Subject: Support "Follow Symbol Under Cursor" --- plugins/haskell/followsymbol.cpp | 138 +++++++++++++++++++++++++++++++ plugins/haskell/followsymbol.h | 94 +++++++++++++++++++++ plugins/haskell/haskell.pro | 8 +- plugins/haskell/haskelleditorfactory.cpp | 5 +- plugins/haskell/haskelleditorwidget.cpp | 86 +++++++++++++++++++ plugins/haskell/haskelleditorwidget.h | 55 ++++++++++++ plugins/haskell/haskellhoverhandler.cpp | 38 +++------ plugins/haskell/haskellmanager.cpp | 6 ++ plugins/haskell/haskellmanager.h | 2 + 9 files changed, 403 insertions(+), 29 deletions(-) create mode 100644 plugins/haskell/followsymbol.cpp create mode 100644 plugins/haskell/followsymbol.h create mode 100644 plugins/haskell/haskelleditorwidget.cpp create mode 100644 plugins/haskell/haskelleditorwidget.h diff --git a/plugins/haskell/followsymbol.cpp b/plugins/haskell/followsymbol.cpp new file mode 100644 index 0000000..1f06467 --- /dev/null +++ b/plugins/haskell/followsymbol.cpp @@ -0,0 +1,138 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "followsymbol.h" + +#include "haskelleditorwidget.h" +#include "haskellmanager.h" +#include "haskelltokenizer.h" + +#include +#include +#include +#include +#include + +#include + +using namespace TextEditor; +using namespace Utils; + +namespace Haskell { +namespace Internal { + +IAssistProvider::RunType FollowSymbolAssistProvider::runType() const +{ + return AsynchronousWithThread; +} + +IAssistProcessor *FollowSymbolAssistProvider::createProcessor() const +{ + return new FollowSymbolAssistProcessor(m_inNextSplit); +} + +void FollowSymbolAssistProvider::setOpenInNextSplit(bool inNextSplit) +{ + m_inNextSplit = inNextSplit; +} + +FollowSymbolAssistProcessor::FollowSymbolAssistProcessor(bool inNextSplit) + : m_inNextSplit(inNextSplit) +{ +} + +IAssistProposal *FollowSymbolAssistProcessor::immediateProposal(const AssistInterface *interface) +{ + int line, column; + const optional symbol + = HaskellEditorWidget::symbolAt(interface->textDocument(), interface->position(), + &line, &column); + QTC_ASSERT(symbol, return nullptr); // should have been checked before + const auto filePath = FileName::fromString(interface->fileName()); + m_ghcmod = HaskellManager::ghcModForFile(filePath); + m_symbolFuture = m_ghcmod->findSymbol(filePath, symbol->text.toString()); + + auto item = new AssistProposalItem(); + item->setText(HaskellManager::trLookingUp(symbol->text.toString())); + item->setData(QString()); + item->setOrder(-1000); + + auto proposal = new GenericProposal(interface->position(), {item}); + proposal->setFragile(true); + return proposal; +} + +IAssistProposal *FollowSymbolAssistProcessor::perform(const AssistInterface *interface) +{ + const int position = interface->position(); + delete interface; + const optional info = m_symbolFuture.result(); + auto item = new FollowSymbolAssistProposalItem(m_ghcmod->basePath(), info, m_inNextSplit); + return new InstantProposal(position, {item}); +} + +FollowSymbolAssistProposalItem::FollowSymbolAssistProposalItem(const FileName &basePath, + const optional &info, + bool inNextSplit) + : m_basePath(basePath), + m_inNextSplit(inNextSplit) +{ + if (info && !info->file.isEmpty()) { + m_info = info; + setText(m_basePath.toString() + '/' + m_info->file.toString()); + } +} + +void FollowSymbolAssistProposalItem::apply(TextDocumentManipulatorInterface &, int) const +{ + if (m_info) + Core::EditorManager::openEditorAt(m_basePath.toString() + '/' + m_info->file.toString(), + m_info->line, m_info->col - 1, Core::Id(), + m_inNextSplit ? Core::EditorManager::OpenInOtherSplit + : Core::EditorManager::NoFlags); +} + +void InstantActivationProposalWidget::showProposal(const QString &prefix) +{ + if (model() && model()->size() == 1) { + emit proposalItemActivated(model()->proposalItem(0)); + deleteLater(); + return; + } + GenericProposalWidget::showProposal(prefix); +} + +InstantProposal::InstantProposal(int cursorPos, const QList &items) + : GenericProposal(cursorPos, items) +{ +} + +IAssistProposalWidget *InstantProposal::createWidget() const +{ + return new InstantActivationProposalWidget(); +} + +} // namespace Internal +} // namespace Haskell diff --git a/plugins/haskell/followsymbol.h b/plugins/haskell/followsymbol.h new file mode 100644 index 0000000..2e8f454 --- /dev/null +++ b/plugins/haskell/followsymbol.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "ghcmod.h" + +#include +#include +#include +#include +#include + +namespace Haskell { +namespace Internal { + +class FollowSymbolAssistProposalItem : public TextEditor::AssistProposalItem +{ +public: + FollowSymbolAssistProposalItem(const Utils::FileName &basePath, + const Utils::optional &info, + bool inNextSplit); + + void apply(TextEditor::TextDocumentManipulatorInterface &, int) const override; + +private: + Utils::FileName m_basePath; + Utils::optional m_info; + bool m_inNextSplit; +}; + +class InstantActivationProposalWidget : public TextEditor::GenericProposalWidget +{ +protected: + void showProposal(const QString &prefix) override; +}; + +class InstantProposal : public TextEditor::GenericProposal +{ +public: + InstantProposal(int cursorPos, const QList &items); + + TextEditor::IAssistProposalWidget *createWidget() const override; +}; + +class FollowSymbolAssistProvider : public TextEditor::IAssistProvider +{ +public: + RunType runType() const override; + TextEditor::IAssistProcessor *createProcessor() const override; + void setOpenInNextSplit(bool inNextSplit); + +private: + bool m_inNextSplit = false; +}; + +class FollowSymbolAssistProcessor : public TextEditor::IAssistProcessor +{ +public: + FollowSymbolAssistProcessor(bool inNextSplit); + + TextEditor::IAssistProposal *immediateProposal(const TextEditor::AssistInterface *interface) override; + TextEditor::IAssistProposal *perform(const TextEditor::AssistInterface *interface) override; + +private: + std::shared_ptr m_ghcmod; + QFuture> m_symbolFuture; + bool m_inNextSplit; +}; + +} // namespace Internal +} // namespace Haskell diff --git a/plugins/haskell/haskell.pro b/plugins/haskell/haskell.pro index 8f5bbc5..2c9739a 100644 --- a/plugins/haskell/haskell.pro +++ b/plugins/haskell/haskell.pro @@ -13,7 +13,9 @@ SOURCES += \ haskellmanager.cpp \ haskelldocument.cpp \ optionspage.cpp \ - filecache.cpp + filecache.cpp \ + haskelleditorwidget.cpp \ + followsymbol.cpp HEADERS += \ haskell_global.h \ @@ -28,7 +30,9 @@ HEADERS += \ haskellmanager.h \ haskelldocument.h \ optionspage.h \ - filecache.h + filecache.h \ + haskelleditorwidget.h \ + followsymbol.h ## uncomment to build plugin into user config directory ## /plugins/ diff --git a/plugins/haskell/haskelleditorfactory.cpp b/plugins/haskell/haskelleditorfactory.cpp index e1d245f..8e5d837 100644 --- a/plugins/haskell/haskelleditorfactory.cpp +++ b/plugins/haskell/haskelleditorfactory.cpp @@ -28,6 +28,7 @@ #include "haskellcompletionassist.h" #include "haskellconstants.h" #include "haskelldocument.h" +#include "haskelleditorwidget.h" #include "haskellhighlighter.h" #include "haskellhoverhandler.h" @@ -44,9 +45,11 @@ HaskellEditorFactory::HaskellEditorFactory() setId(Constants::C_HASKELLEDITOR_ID); setDisplayName(QCoreApplication::translate("OpenWith::Editors", "Haskell Editor")); addMimeType("text/x-haskell"); - setEditorActionHandlers(TextEditor::TextEditorActionHandler::UnCommentSelection); + setEditorActionHandlers(TextEditor::TextEditorActionHandler::UnCommentSelection + | TextEditor::TextEditorActionHandler::FollowSymbolUnderCursor); addHoverHandler(new HaskellHoverHandler); setDocumentCreator([] { return new HaskellDocument(); }); + setEditorWidgetCreator([] { return new HaskellEditorWidget; }); setCommentDefinition(Utils::CommentDefinition("--", "{-", "-}")); setParenthesesMatchingEnabled(true); setMarksVisible(true); diff --git a/plugins/haskell/haskelleditorwidget.cpp b/plugins/haskell/haskelleditorwidget.cpp new file mode 100644 index 0000000..785f4e0 --- /dev/null +++ b/plugins/haskell/haskelleditorwidget.cpp @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "haskelleditorwidget.h" + +#include "haskelltokenizer.h" + +#include + +#include + +using namespace TextEditor; + +namespace Haskell { +namespace Internal { + +HaskellEditorWidget::HaskellEditorWidget(QWidget *parent) + : TextEditorWidget(parent) +{ +} + +Utils::optional HaskellEditorWidget::symbolAt(QTextDocument *doc, int position, + int *line, int *column) +{ + Utils::Text::convertPosition(doc, position, line, column); + if (*line < 0 || *column < 0) + return Utils::nullopt; + const QTextBlock block = doc->findBlockByNumber(*line - 1); + if (block.text().isEmpty()) + return Utils::nullopt; + const int startState = block.previous().isValid() ? block.previous().userState() : -1; + const Tokens tokens = HaskellTokenizer::tokenize(block.text(), startState); + const Token token = tokens.tokenAtColumn(*column); + if (token.isValid() + && (token.type == TokenType::Variable + || token.type == TokenType::Operator + || token.type == TokenType::Constructor + || token.type == TokenType::OperatorConstructor)) { + return token; + } + return Utils::nullopt; +} + +TextEditorWidget::Link HaskellEditorWidget::findLinkAt(const QTextCursor &cursor, + bool resolveTarget, bool inNextSplit) +{ + int line, column; + const Utils::optional symbol = symbolAt(document(), cursor.position(), &line, &column); + if (symbol) { + const QTextBlock block = document()->findBlockByNumber(line - 1); + Link link; + link.linkTextStart = block.position() + symbol->startCol; + link.linkTextEnd = link.linkTextStart + symbol->length; + if (resolveTarget) { + m_followSymbolAssistProvider.setOpenInNextSplit(inNextSplit); + invokeAssist(FollowSymbol, &m_followSymbolAssistProvider); + } + return link; + } + return Link(); +} + +} // namespace Internal +} // namespace Haskell diff --git a/plugins/haskell/haskelleditorwidget.h b/plugins/haskell/haskelleditorwidget.h new file mode 100644 index 0000000..f8b2102 --- /dev/null +++ b/plugins/haskell/haskelleditorwidget.h @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "followsymbol.h" + +#include +#include + +namespace Haskell { +namespace Internal { + +class Token; + +class HaskellEditorWidget : public TextEditor::TextEditorWidget +{ +public: + HaskellEditorWidget(QWidget *parent = 0); + + static Utils::optional symbolAt(QTextDocument *doc, int position, + int *line, int *column); + +protected: + Link findLinkAt(const QTextCursor &cursor, bool resolveTarget = true, + bool inNextSplit = false) override; + +private: + FollowSymbolAssistProvider m_followSymbolAssistProvider; +}; + +} // namespace Internal +} // namespace Haskell diff --git a/plugins/haskell/haskellhoverhandler.cpp b/plugins/haskell/haskellhoverhandler.cpp index 5e03e32..f418170 100644 --- a/plugins/haskell/haskellhoverhandler.cpp +++ b/plugins/haskell/haskellhoverhandler.cpp @@ -26,6 +26,8 @@ #include "haskellhoverhandler.h" #include "haskelldocument.h" +#include "haskelleditorwidget.h" +#include "haskellmanager.h" #include "haskelltokenizer.h" #include @@ -35,11 +37,6 @@ #include #include -#include -#include - -#include - using namespace Utils; static QString toCode(const QString &s) @@ -77,27 +74,16 @@ void HaskellHoverHandler::identifyMatch(TextEditor::TextEditorWidget *editorWidg { cancel(); m_name.clear(); - editorWidget->convertPosition(pos, &m_line, &m_col); - if (m_line < 0 || m_col < 0) - return; - QTextBlock block = editorWidget->document()->findBlockByNumber(m_line - 1); - if (block.text().isEmpty()) - return; - m_filePath = editorWidget->textDocument()->filePath(); - const int startState = block.previous().isValid() ? block.previous().userState() : -1; - const Tokens tokens = HaskellTokenizer::tokenize(block.text(), startState); - const Token token = tokens.tokenAtColumn(m_col); - if (token.isValid() - && (token.type == TokenType::Variable - || token.type == TokenType::Operator - || token.type == TokenType::Constructor - || token.type == TokenType::OperatorConstructor)) { - m_name = token.text.toString(); - } - if (m_name.isEmpty()) - setPriority(-1); - else + m_filePath.clear(); + const Utils::optional token = HaskellEditorWidget::symbolAt(editorWidget->document(), + pos, &m_line, &m_col); + if (token) { + m_filePath = editorWidget->textDocument()->filePath(); + m_name = token->text.toString(); setPriority(Priority_Tooltip); + } else { + setPriority(-1); + } } static void tryShowToolTip(const QPointer &widget, const QPoint &point, @@ -125,7 +111,7 @@ void HaskellHoverHandler::operateTooltip(TextEditor::TextEditorWidget *editorWid Utils::ToolTip::hide(); return; } - Utils::ToolTip::show(point, tr("Looking up \"%1\"").arg(m_name), editorWidget); + Utils::ToolTip::show(point, HaskellManager::trLookingUp(m_name), editorWidget); QPointer widget = editorWidget; std::shared_ptr ghcmod; diff --git a/plugins/haskell/haskellmanager.cpp b/plugins/haskell/haskellmanager.cpp index 0867edf..a0ab153 100644 --- a/plugins/haskell/haskellmanager.cpp +++ b/plugins/haskell/haskellmanager.cpp @@ -29,6 +29,7 @@ #include +#include #include #include #include @@ -126,5 +127,10 @@ void HaskellManager::writeSettings(QSettings *settings) settings->setValue(kStackExecutableKey, m_d->stackExecutable.toString()); } +QString HaskellManager::trLookingUp(const QString &name) +{ + return QCoreApplication::translate("HaskellManager", "Looking up \"%1\"...").arg(name); +} + } // namespace Internal } // namespace Haskell diff --git a/plugins/haskell/haskellmanager.h b/plugins/haskell/haskellmanager.h index 87c08e8..aea4106 100644 --- a/plugins/haskell/haskellmanager.h +++ b/plugins/haskell/haskellmanager.h @@ -52,6 +52,8 @@ public: static void readSettings(QSettings *settings); static void writeSettings(QSettings *settings); + static QString trLookingUp(const QString &name); + signals: void stackExecutableChanged(const Utils::FileName &filePath); }; -- cgit v1.2.3