From 80633a59aabca94e52e473837164203041e72c6e Mon Sep 17 00:00:00 2001 From: David Schulz Date: Tue, 5 Sep 2023 11:14:10 +0200 Subject: [PATCH] LanguageClient: support additional goto targets in symbol support Allow to follow to the symbol definition as well as to the type definition for the symbol under the cursor position. Change-Id: I8ff50b33a1e739f81b0832b1b28ffc525e1f7177 Reviewed-by: Christian Stenger --- src/plugins/clangcodemodel/clangdclient.cpp | 6 +- .../clangcodemodel/clangdfindreferences.cpp | 6 +- .../clangcodemodel/clangdfollowsymbol.cpp | 6 +- src/plugins/cppeditor/cppeditorconstants.h | 2 - src/plugins/cppeditor/cppeditorplugin.cpp | 30 +---- src/plugins/cppeditor/cppeditorwidget.cpp | 40 +++--- src/plugins/cppeditor/cppeditorwidget.h | 6 +- src/plugins/languageclient/client.cpp | 10 +- src/plugins/languageclient/client.h | 5 +- .../languageclient/languageclientmanager.cpp | 20 ++- .../languageclientsymbolsupport.cpp | 117 ++++++++++++++---- .../languageclientsymbolsupport.h | 9 +- src/plugins/qmljseditor/qmljseditor.cpp | 6 +- .../texteditor/plaintexteditorfactory.cpp | 3 +- src/plugins/texteditor/texteditor.cpp | 72 ++++++++--- src/plugins/texteditor/texteditor.h | 10 +- .../texteditor/texteditoractionhandler.cpp | 12 ++ .../texteditor/texteditoractionhandler.h | 9 +- src/plugins/texteditor/texteditorconstants.h | 2 + 19 files changed, 265 insertions(+), 106 deletions(-) diff --git a/src/plugins/clangcodemodel/clangdclient.cpp b/src/plugins/clangcodemodel/clangdclient.cpp index 5631f9fbc3c..87f3e77c5d2 100644 --- a/src/plugins/clangcodemodel/clangdclient.cpp +++ b/src/plugins/clangcodemodel/clangdclient.cpp @@ -1013,7 +1013,11 @@ void ClangdClient::followSymbol(TextDocument *document, const QTextCursor adjustedCursor = d->adjustedCursor(cursor, document); if (followTo == FollowTo::SymbolDef && !resolveTarget) { - symbolSupport().findLinkAt(document, adjustedCursor, callback, false); + symbolSupport().findLinkAt(document, + adjustedCursor, + callback, + false, + LanguageClient::LinkTarget::SymbolDef); return; } diff --git a/src/plugins/clangcodemodel/clangdfindreferences.cpp b/src/plugins/clangcodemodel/clangdfindreferences.cpp index 24175a82504..c3ba6c2b8f6 100644 --- a/src/plugins/clangcodemodel/clangdfindreferences.cpp +++ b/src/plugins/clangcodemodel/clangdfindreferences.cpp @@ -714,7 +714,11 @@ void ClangdFindLocalReferences::Private::findDefinition() if (sentinel) getDefinitionAst(l); }; - client()->symbolSupport().findLinkAt(document, cursor, linkHandler, true); + client()->symbolSupport().findLinkAt(document, + cursor, + linkHandler, + true, + LanguageClient::LinkTarget::SymbolDef); } void ClangdFindLocalReferences::Private::getDefinitionAst(const Link &link) diff --git a/src/plugins/clangcodemodel/clangdfollowsymbol.cpp b/src/plugins/clangcodemodel/clangdfollowsymbol.cpp index 730a3fdd375..ee56e3f4c75 100644 --- a/src/plugins/clangcodemodel/clangdfollowsymbol.cpp +++ b/src/plugins/clangcodemodel/clangdfollowsymbol.cpp @@ -154,7 +154,11 @@ ClangdFollowSymbol::ClangdFollowSymbol(ClangdClient *client, const QTextCursor & if (self->d->cursorNode) self->d->handleGotoDefinitionResult(); }; - client->symbolSupport().findLinkAt(document, cursor, std::move(gotoDefCallback), true); + client->symbolSupport().findLinkAt(document, + cursor, + std::move(gotoDefCallback), + true, + LanguageClient::LinkTarget::SymbolDef); const auto astHandler = [self = QPointer(this)](const ClangdAstNode &ast, const MessageId &) { qCDebug(clangdLog) << "received ast response for cursor"; diff --git a/src/plugins/cppeditor/cppeditorconstants.h b/src/plugins/cppeditor/cppeditorconstants.h index ee8ec86a27e..3e3c02dc434 100644 --- a/src/plugins/cppeditor/cppeditorconstants.h +++ b/src/plugins/cppeditor/cppeditorconstants.h @@ -16,8 +16,6 @@ const char G_GLOBAL[] = "CppEditor.GGlobal"; const char CPPEDITOR_ID[] = "CppEditor.C++Editor"; const char SWITCH_DECLARATION_DEFINITION[] = "CppEditor.SwitchDeclarationDefinition"; const char OPEN_DECLARATION_DEFINITION_IN_NEXT_SPLIT[] = "CppEditor.OpenDeclarationDefinitionInNextSplit"; -const char FOLLOW_SYMBOL_TO_TYPE[] = "TextEditor.FollowSymbolToType"; -const char FOLLOW_SYMBOL_TO_TYPE_IN_NEXT_SPLIT[] = "TextEditor.FollowSymbolToTypeInNextSplit"; const char OPEN_PREPROCESSOR_DIALOG[] = "CppEditor.OpenPreprocessorDialog"; const char MULTIPLE_PARSE_CONTEXTS_AVAILABLE[] = "CppEditor.MultipleParseContextsAvailable"; const char M_REFACTORING_MENU_INSERTION_POINT[] = "CppEditor.RefactorGroup"; diff --git a/src/plugins/cppeditor/cppeditorplugin.cpp b/src/plugins/cppeditor/cppeditorplugin.cpp index 6d4adb3ca86..13d62e4cee2 100644 --- a/src/plugins/cppeditor/cppeditorplugin.cpp +++ b/src/plugins/cppeditor/cppeditorplugin.cpp @@ -152,6 +152,7 @@ public: | TextEditorActionHandler::UnCommentSelection | TextEditorActionHandler::UnCollapseAll | TextEditorActionHandler::FollowSymbolUnderCursor + | TextEditorActionHandler::FollowTypeUnderCursor | TextEditorActionHandler::RenameSymbol | TextEditorActionHandler::FindUsage); } @@ -336,31 +337,10 @@ void CppEditorPlugin::addPerSymbolActions() touchBar->addAction(cmd, Core::Constants::G_TOUCHBAR_NAVIGATION); addSymbolActionToMenus(ActionManager::command( TextEditor::Constants::FOLLOW_SYMBOL_UNDER_CURSOR_IN_NEXT_SPLIT)); - - QAction * const followSymbolToType = new QAction(Tr::tr("Follow Symbol Under Cursor to Type"), - this); - cmd = ActionManager::registerAction(followSymbolToType, Constants::FOLLOW_SYMBOL_TO_TYPE, - context, true); - cmd->setDefaultKeySequence(QKeySequence(Tr::tr("Ctrl+Shift+F2"))); - connect(followSymbolToType, &QAction::triggered, this, []{ - if (CppEditorWidget *editorWidget = currentCppEditorWidget()) - editorWidget->followSymbolToType(false); - }); - addSymbolActionToMenus(cmd); - - QAction * const followSymbolToTypeInNextSplit = - new QAction(Tr::tr("Follow Symbol to Type in Next Split"), this); - cmd = ActionManager::registerAction(followSymbolToTypeInNextSplit, - Constants::FOLLOW_SYMBOL_TO_TYPE_IN_NEXT_SPLIT, - context, true); - cmd->setDefaultKeySequence(QKeySequence(HostOsInfo::isMacHost() - ? Tr::tr("Meta+E, Ctrl+Shift+F2") - : Tr::tr("Ctrl+E, Ctrl+Shift+F2"))); - connect(followSymbolToTypeInNextSplit, &QAction::triggered, this, []{ - if (CppEditorWidget *editorWidget = currentCppEditorWidget()) - editorWidget->followSymbolToType(true); - }); - addSymbolActionToMenus(cmd); + addSymbolActionToMenus(ActionManager::command( + TextEditor::Constants::FOLLOW_SYMBOL_TO_TYPE)); + addSymbolActionToMenus(ActionManager::command( + TextEditor::Constants::FOLLOW_SYMBOL_TO_TYPE_IN_NEXT_SPLIT)); QAction * const switchDeclarationDefinition = new QAction(Tr::tr("Switch Between Function Declaration/Definition"), this); diff --git a/src/plugins/cppeditor/cppeditorwidget.cpp b/src/plugins/cppeditor/cppeditorwidget.cpp index 43c6365c89c..7559e028e2e 100644 --- a/src/plugins/cppeditor/cppeditorwidget.cpp +++ b/src/plugins/cppeditor/cppeditorwidget.cpp @@ -893,20 +893,6 @@ void CppEditorWidget::switchDeclarationDefinition(bool inNextSplit) CppModelManager::switchDeclDef(cursor, std::move(callback)); } -void CppEditorWidget::followSymbolToType(bool inNextSplit) -{ - if (!CppModelManager::instance()) - return; - - const CursorInEditor cursor(textCursor(), textDocument()->filePath(), this, textDocument()); - const auto callback = [self = QPointer(this), - split = inNextSplit != alwaysOpenLinksInNextSplit()](const Link &link) { - if (self && link.hasValidTarget()) - self->openLink(link, split); - }; - CppModelManager::followSymbolToType(cursor, callback, inNextSplit); -} - bool CppEditorWidget::followUrl(const QTextCursor &cursor, const Utils::LinkHandler &processLinkCallback) { @@ -997,11 +983,27 @@ void CppEditorWidget::findLinkAt(const QTextCursor &cursor, } callback(link); }; - CppModelManager::followSymbol( - CursorInEditor{cursor, filePath, this, textDocument()}, - callbackWrapper, - resolveTarget, - inNextSplit); + CppModelManager::followSymbol(CursorInEditor{cursor, filePath, this, textDocument()}, + callbackWrapper, + resolveTarget, + inNextSplit); +} + +void CppEditorWidget::findTypeAt(const QTextCursor &cursor, + const Utils::LinkHandler &processLinkCallback, + bool resolveTarget, + bool inNextSplit) +{ + if (!CppModelManager::instance()) + return; + + const CursorInEditor cursorInEditor(cursor, textDocument()->filePath(), this, textDocument()); + const auto callback = [self = QPointer(this), + split = inNextSplit != alwaysOpenLinksInNextSplit()](const Link &link) { + if (self && link.hasValidTarget()) + self->openLink(link, split); + }; + CppModelManager::followSymbolToType(cursorInEditor, callback, inNextSplit); } unsigned CppEditorWidget::documentRevision() const diff --git a/src/plugins/cppeditor/cppeditorwidget.h b/src/plugins/cppeditor/cppeditorwidget.h index a379c5f55b4..58d40c8a592 100644 --- a/src/plugins/cppeditor/cppeditorwidget.h +++ b/src/plugins/cppeditor/cppeditorwidget.h @@ -57,7 +57,6 @@ public: void selectAll() override; void switchDeclarationDefinition(bool inNextSplit); - void followSymbolToType(bool inNextSplit); void showPreProcessorWidget(); void findUsages() override; @@ -105,6 +104,11 @@ protected: bool resolveTarget = true, bool inNextSplit = false) override; + void findTypeAt(const QTextCursor &cursor, + const Utils::LinkHandler &processLinkCallback, + bool resolveTarget = true, + bool inNextSplit = false) override; + void slotCodeStyleSettingsChanged(const QVariant &) override; private: diff --git a/src/plugins/languageclient/client.cpp b/src/plugins/languageclient/client.cpp index 13f51d2cde9..c0922538c3f 100644 --- a/src/plugins/languageclient/client.cpp +++ b/src/plugins/languageclient/client.cpp @@ -936,6 +936,10 @@ void Client::activateEditor(Core::IEditor *editor) optionalActions |= TextEditor::TextEditorActionHandler::FindUsage; if (symbolSupport().supportsRename(widget->textDocument())) optionalActions |= TextEditor::TextEditorActionHandler::RenameSymbol; + if (symbolSupport().supportsFindLink(widget->textDocument(), LinkTarget::SymbolDef)) + optionalActions |= TextEditor::TextEditorActionHandler::FollowSymbolUnderCursor; + if (symbolSupport().supportsFindLink(widget->textDocument(), LinkTarget::SymbolTypeDef)) + optionalActions |= TextEditor::TextEditorActionHandler::FollowTypeUnderCursor; if (CallHierarchyFactory::supportsCallHierarchy(this, textEditor->document())) optionalActions |= TextEditor::TextEditorActionHandler::CallHierarchy; widget->setOptionalActions(optionalActions); @@ -1306,7 +1310,8 @@ SymbolSupport &Client::symbolSupport() void Client::findLinkAt(TextEditor::TextDocument *document, const QTextCursor &cursor, Utils::LinkHandler callback, - const bool resolveTarget) + const bool resolveTarget, + LinkTarget target) { if (d->m_runningFindLinkRequest.isValid()) cancelRequest(d->m_runningFindLinkRequest); @@ -1317,7 +1322,8 @@ void Client::findLinkAt(TextEditor::TextDocument *document, d->m_runningFindLinkRequest = {}; callback(link); }, - resolveTarget); + resolveTarget, + target); } void Client::requestCodeActions(const LanguageServerProtocol::DocumentUri &uri, diff --git a/src/plugins/languageclient/client.h b/src/plugins/languageclient/client.h index 8aede4b249d..da8c8166644 100644 --- a/src/plugins/languageclient/client.h +++ b/src/plugins/languageclient/client.h @@ -4,6 +4,7 @@ #pragma once #include "languageclient_global.h" +#include "languageclientsymbolsupport.h" #include "languageclientutils.h" #include "semantichighlightsupport.h" @@ -45,7 +46,6 @@ class LanguageClientOutlineItem; class LanguageClientQuickFixProvider; class LanguageFilter; class ProgressManager; -class SymbolSupport; class LANGUAGECLIENT_EXPORT Client : public QObject { @@ -157,7 +157,8 @@ public: void findLinkAt(TextEditor::TextDocument *document, const QTextCursor &cursor, Utils::LinkHandler callback, - const bool resolveTarget); + const bool resolveTarget, + LinkTarget target); DocumentSymbolCache *documentSymbolCache(); HoverHandler *hoverHandler(); QList diagnosticsAt(const Utils::FilePath &filePath, diff --git a/src/plugins/languageclient/languageclientmanager.cpp b/src/plugins/languageclient/languageclientmanager.cpp index fcfc7c49fa7..c05b84db4aa 100644 --- a/src/plugins/languageclient/languageclientmanager.cpp +++ b/src/plugins/languageclient/languageclientmanager.cpp @@ -479,8 +479,24 @@ void LanguageClientManager::editorOpened(Core::IEditor *editor) connect(widget, &TextEditorWidget::requestLinkAt, this, [document = textEditor->textDocument()] (const QTextCursor &cursor, const Utils::LinkHandler &callback, bool resolveTarget) { - if (auto client = clientForDocument(document)) - client->findLinkAt(document, cursor, callback, resolveTarget); + if (auto client = clientForDocument(document)) { + client->findLinkAt(document, + cursor, + callback, + resolveTarget, + LinkTarget::SymbolDef); + } + }); + connect(widget, &TextEditorWidget::requestTypeAt, this, + [document = textEditor->textDocument()] + (const QTextCursor &cursor, const Utils::LinkHandler &callback, bool resolveTarget) { + if (auto client = clientForDocument(document)) { + client->findLinkAt(document, + cursor, + callback, + resolveTarget, + LinkTarget::SymbolTypeDef); + } }); connect(widget, &TextEditorWidget::requestUsages, this, [document = textEditor->textDocument()](const QTextCursor &cursor) { diff --git a/src/plugins/languageclient/languageclientsymbolsupport.cpp b/src/plugins/languageclient/languageclientsymbolsupport.cpp index b6ac1bdb307..268bd391e81 100644 --- a/src/plugins/languageclient/languageclientsymbolsupport.cpp +++ b/src/plugins/languageclient/languageclientsymbolsupport.cpp @@ -77,15 +77,14 @@ SymbolSupport::SymbolSupport(Client *client) {} template -static void sendTextDocumentPositionParamsRequest(Client *client, - const Request &request, - const DynamicCapabilities &dynamicCapabilities, - const ServerCapabilities &serverCapability) +static MessageId sendTextDocumentPositionParamsRequest(Client *client, const Request &request) { if (!request.isValid(nullptr)) - return; + return {}; const DocumentUri uri = request.params().value().textDocument().uri(); const bool supportedFile = client->isSupportedUri(uri); + const DynamicCapabilities dynamicCapabilities = client->dynamicCapabilities(); + const ServerCapabilities serverCapability = client->capabilities(); bool sendMessage = dynamicCapabilities.isRegistered(Request::methodName).value_or(false); if (sendMessage) { const TextDocumentRegistrationOptions option( @@ -102,14 +101,17 @@ static void sendTextDocumentPositionParamsRequest(Client *client, if (sendMessage && std::holds_alternative(*provider)) sendMessage = std::get(*provider); } - if (sendMessage) + if (sendMessage) { client->sendMessage(request); + return request.id(); + } + return {}; } -static void handleGotoDefinitionResponse(const GotoDefinitionRequest::Response &response, - Utils::LinkHandler callback, - std::optional linkUnderCursor, - const Client *client) +static void handleGotoResponse(const GotoDefinitionRequest::Response &response, + Utils::LinkHandler callback, + std::optional linkUnderCursor, + const Client *client) { if (std::optional result = response.result()) { if (std::holds_alternative(*result)) { @@ -137,14 +139,69 @@ static TextDocumentPositionParams generateDocPosParams(TextEditor::TextDocument return TextDocumentPositionParams(documentId, pos); } +template +static MessageId sendGotoRequest(TextEditor::TextDocument *document, + const QTextCursor &cursor, + Utils::LinkHandler callback, + Client *client, + std::optional linkUnderCursor) +{ + Request request(generateDocPosParams(document, cursor, client)); + request.setResponseCallback([callback, linkUnderCursor, client]( + const GotoDefinitionRequest::Response &response) { + handleGotoResponse(response, callback, linkUnderCursor, client); + }); + return sendTextDocumentPositionParamsRequest(client, request); + return request.id(); +} + +bool SymbolSupport::supportsFindLink(TextEditor::TextDocument *document, LinkTarget target) const +{ + const DocumentUri uri = m_client->hostPathToServerUri(document->filePath()); + const DynamicCapabilities dynamicCapabilities = m_client->dynamicCapabilities(); + const ServerCapabilities serverCapability = m_client->capabilities(); + QString methodName; + std::optional> provider; + switch (target) { + case LinkTarget::SymbolDef: + methodName = GotoDefinitionRequest::methodName; + provider = serverCapability.definitionProvider(); + break; + case LinkTarget::SymbolTypeDef: + methodName = GotoTypeDefinitionRequest::methodName; + provider = serverCapability.typeDefinitionProvider(); + break; + case LinkTarget::SymbolImplementation: + methodName = GotoImplementationRequest::methodName; + provider = serverCapability.implementationProvider(); + break; + } + if (methodName.isEmpty()) + return false; + bool supported = dynamicCapabilities.isRegistered(methodName).value_or(false); + if (supported) { + const TextDocumentRegistrationOptions option(dynamicCapabilities.option(methodName)); + if (option.isValid()) + supported = option.filterApplies( + Utils::FilePath::fromString(QUrl(uri).adjusted(QUrl::PreferLocalFile).toString())); + else + supported = m_client->isSupportedUri(uri); + } else { + supported = provider.has_value(); + if (supported && std::holds_alternative(*provider)) + supported = std::get(*provider); + } + return supported; +} + MessageId SymbolSupport::findLinkAt(TextEditor::TextDocument *document, const QTextCursor &cursor, Utils::LinkHandler callback, - const bool resolveTarget) + const bool resolveTarget, + const LinkTarget target) { if (!m_client->reachable()) return {}; - GotoDefinitionRequest request(generateDocPosParams(document, cursor, m_client)); std::optional linkUnderCursor; if (!resolveTarget) { QTextCursor linkCursor = cursor; @@ -156,16 +213,29 @@ MessageId SymbolSupport::findLinkAt(TextEditor::TextDocument *document, link.linkTextEnd = linkCursor.selectionEnd(); linkUnderCursor = link; } - request.setResponseCallback([callback, linkUnderCursor, client = m_client]( - const GotoDefinitionRequest::Response &response) { - handleGotoDefinitionResponse(response, callback, linkUnderCursor, client); - }); - sendTextDocumentPositionParamsRequest(m_client, - request, - m_client->dynamicCapabilities(), - m_client->capabilities()); - return request.id(); + const TextDocumentPositionParams params = generateDocPosParams(document, cursor, m_client); + switch (target) { + case LinkTarget::SymbolDef: + return sendGotoRequest(document, + cursor, + callback, + m_client, + linkUnderCursor); + case LinkTarget::SymbolTypeDef: + return sendGotoRequest(document, + cursor, + callback, + m_client, + linkUnderCursor); + case LinkTarget::SymbolImplementation: + return sendGotoRequest(document, + cursor, + callback, + m_client, + linkUnderCursor); + } + return {}; } bool SymbolSupport::supportsFindUsages(TextEditor::TextDocument *document) const @@ -317,10 +387,7 @@ std::optional SymbolSupport::findUsages(TextEditor::TextDocument *doc handleFindReferencesResponse(response, wordUnderCursor, handler); }); - sendTextDocumentPositionParamsRequest(m_client, - request, - m_client->dynamicCapabilities(), - m_client->capabilities()); + sendTextDocumentPositionParamsRequest(m_client, request); return request.id(); } diff --git a/src/plugins/languageclient/languageclientsymbolsupport.h b/src/plugins/languageclient/languageclientsymbolsupport.h index 54666e7b1f9..b3c7f4edb50 100644 --- a/src/plugins/languageclient/languageclientsymbolsupport.h +++ b/src/plugins/languageclient/languageclientsymbolsupport.h @@ -19,16 +19,19 @@ namespace LanguageServerProtocol { class MessageId; } namespace LanguageClient { class Client; +enum class LinkTarget { SymbolDef, SymbolTypeDef, SymbolImplementation }; class LANGUAGECLIENT_EXPORT SymbolSupport : public QObject { public: explicit SymbolSupport(Client *client); + bool supportsFindLink(TextEditor::TextDocument *document, LinkTarget target) const; LanguageServerProtocol::MessageId findLinkAt(TextEditor::TextDocument *document, - const QTextCursor &cursor, - Utils::LinkHandler callback, - const bool resolveTarget); + const QTextCursor &cursor, + Utils::LinkHandler callback, + const bool resolveTarget, + const LinkTarget target); bool supportsFindUsages(TextEditor::TextDocument *document) const; using ResultHandler = std::function &)>; diff --git a/src/plugins/qmljseditor/qmljseditor.cpp b/src/plugins/qmljseditor/qmljseditor.cpp index e6ceb513072..69948be3835 100644 --- a/src/plugins/qmljseditor/qmljseditor.cpp +++ b/src/plugins/qmljseditor/qmljseditor.cpp @@ -757,7 +757,11 @@ void QmlJSEditorWidget::findLinkAt(const QTextCursor &cursor, bool /*inNextSplit*/) { if (auto client = getQmllsClient(textDocument()->filePath())) { - client->findLinkAt(textDocument(), cursor, processLinkCallback, resolveTarget); + client->findLinkAt(textDocument(), + cursor, + processLinkCallback, + resolveTarget, + LanguageClient::LinkTarget::SymbolDef); return; } diff --git a/src/plugins/texteditor/plaintexteditorfactory.cpp b/src/plugins/texteditor/plaintexteditorfactory.cpp index a28f05e38e2..fd6b8d96b9b 100644 --- a/src/plugins/texteditor/plaintexteditorfactory.cpp +++ b/src/plugins/texteditor/plaintexteditorfactory.cpp @@ -48,8 +48,7 @@ PlainTextEditorFactory::PlainTextEditorFactory() setEditorActionHandlers(TextEditorActionHandler::Format | TextEditorActionHandler::UnCommentSelection | - TextEditorActionHandler::UnCollapseAll | - TextEditorActionHandler::FollowSymbolUnderCursor); + TextEditorActionHandler::UnCollapseAll); } PlainTextEditorFactory *PlainTextEditorFactory::instance() diff --git a/src/plugins/texteditor/texteditor.cpp b/src/plugins/texteditor/texteditor.cpp index 66d5617f363..33e6cd295ab 100644 --- a/src/plugins/texteditor/texteditor.cpp +++ b/src/plugins/texteditor/texteditor.cpp @@ -717,6 +717,7 @@ public: KSyntaxHighlighting::Definition currentDefinition(); void rememberCurrentSyntaxDefinition(); void openLinkUnderCursor(bool openInNextSplit); + void openTypeUnderCursor(bool openInNextSplit); qreal charWidth() const; public: @@ -2446,6 +2447,16 @@ void TextEditorWidget::openLinkUnderCursorInNextSplit() d->openLinkUnderCursor(!alwaysOpenLinksInNextSplit()); } +void TextEditorWidget::openTypeUnderCursor() +{ + d->openTypeUnderCursor(alwaysOpenLinksInNextSplit()); +} + +void TextEditorWidget::openTypeUnderCursorInNextSplit() +{ + d->openTypeUnderCursor(!alwaysOpenLinksInNextSplit()); +} + void TextEditorWidget::findUsages() { emit requestUsages(textCursor()); @@ -3642,11 +3653,26 @@ void TextEditorWidgetPrivate::rememberCurrentSyntaxDefinition() void TextEditorWidgetPrivate::openLinkUnderCursor(bool openInNextSplit) { - q->findLinkAt(q->textCursor(), - [openInNextSplit, self = QPointer(q)](const Link &symbolLink) { - if (self) - self->openLink(symbolLink, openInNextSplit); - }, true, openInNextSplit); + q->findLinkAt( + q->textCursor(), + [openInNextSplit, self = QPointer(q)](const Link &symbolLink) { + if (self) + self->openLink(symbolLink, openInNextSplit); + }, + true, + openInNextSplit); +} + +void TextEditorWidgetPrivate::openTypeUnderCursor(bool openInNextSplit) +{ + q->findTypeAt( + q->textCursor(), + [openInNextSplit, self = QPointer(q)](const Link &symbolLink) { + if (self) + self->openLink(symbolLink, openInNextSplit); + }, + true, + openInNextSplit); } qreal TextEditorWidgetPrivate::charWidth() const @@ -6682,6 +6708,14 @@ void TextEditorWidget::findLinkAt(const QTextCursor &cursor, emit requestLinkAt(cursor, callback, resolveTarget, inNextSplit); } +void TextEditorWidget::findTypeAt(const QTextCursor &cursor, + const Utils::LinkHandler &callback, + bool resolveTarget, + bool inNextSplit) +{ + emit requestTypeAt(cursor, callback, resolveTarget, inNextSplit); +} + bool TextEditorWidget::openLink(const Utils::Link &link, bool inNextSplit) { #ifdef WITH_TESTS @@ -8394,20 +8428,30 @@ void TextEditorWidget::setupFallBackEditor(Id id) void TextEditorWidget::appendStandardContextMenuActions(QMenu *menu) { + if (optionalActions() & TextEditorActionHandler::FollowSymbolUnderCursor) { + const auto action = ActionManager::command(Constants::FOLLOW_SYMBOL_UNDER_CURSOR)->action(); + if (!menu->actions().contains(action)) + menu->addAction(action); + } + if (optionalActions() & TextEditorActionHandler::FollowTypeUnderCursor) { + const auto action = ActionManager::command(Constants::FOLLOW_SYMBOL_TO_TYPE)->action(); + if (!menu->actions().contains(action)) + menu->addAction(action); + } if (optionalActions() & TextEditorActionHandler::FindUsage) { - const auto findUsage = ActionManager::command(Constants::FIND_USAGES)->action(); - if (!menu->actions().contains(findUsage)) - menu->addAction(findUsage); + const auto action = ActionManager::command(Constants::FIND_USAGES)->action(); + if (!menu->actions().contains(action)) + menu->addAction(action); } if (optionalActions() & TextEditorActionHandler::RenameSymbol) { - const auto renameSymbol = ActionManager::command(Constants::RENAME_SYMBOL)->action(); - if (!menu->actions().contains(renameSymbol)) - menu->addAction(renameSymbol); + const auto action = ActionManager::command(Constants::RENAME_SYMBOL)->action(); + if (!menu->actions().contains(action)) + menu->addAction(action); } if (optionalActions() & TextEditorActionHandler::CallHierarchy) { - const auto callHierarchy = ActionManager::command(Constants::OPEN_CALL_HIERARCHY)->action(); - if (!menu->actions().contains(callHierarchy)) - menu->addAction(callHierarchy); + const auto action = ActionManager::command(Constants::OPEN_CALL_HIERARCHY)->action(); + if (!menu->actions().contains(action)) + menu->addAction(action); } menu->addSeparator(); diff --git a/src/plugins/texteditor/texteditor.h b/src/plugins/texteditor/texteditor.h index 0afa17f83a0..6f67fe67748 100644 --- a/src/plugins/texteditor/texteditor.h +++ b/src/plugins/texteditor/texteditor.h @@ -154,7 +154,6 @@ private: Internal::BaseTextEditorPrivate *d; }; - class TEXTEDITOR_EXPORT TextEditorWidget : public QPlainTextEdit { Q_OBJECT @@ -438,6 +437,8 @@ public: void openLinkUnderCursor(); void openLinkUnderCursorInNextSplit(); + void openTypeUnderCursor(); + void openTypeUnderCursorInNextSplit(); virtual void findUsages(); virtual void renameSymbolUnderCursor(); @@ -496,6 +497,8 @@ signals: void requestLinkAt(const QTextCursor &cursor, const Utils::LinkHandler &callback, bool resolveTarget, bool inNextSplit); + void requestTypeAt(const QTextCursor &cursor, const Utils::LinkHandler &callback, + bool resolveTarget, bool inNextSplit); void requestUsages(const QTextCursor &cursor); void requestRename(const QTextCursor &cursor); void requestCallHierarchy(const QTextCursor &cursor); @@ -588,6 +591,11 @@ protected: bool resolveTarget = true, bool inNextSplit = false); + virtual void findTypeAt(const QTextCursor &, + const Utils::LinkHandler &processLinkCallback, + bool resolveTarget = true, + bool inNextSplit = false); + /*! Returns whether the link was opened successfully. */ diff --git a/src/plugins/texteditor/texteditoractionhandler.cpp b/src/plugins/texteditor/texteditoractionhandler.cpp index d8405d33469..80625cc42e1 100644 --- a/src/plugins/texteditor/texteditoractionhandler.cpp +++ b/src/plugins/texteditor/texteditoractionhandler.cpp @@ -118,6 +118,8 @@ public: QAction *m_unfoldAllAction = nullptr; QAction *m_followSymbolAction = nullptr; QAction *m_followSymbolInNextSplitAction = nullptr; + QAction *m_followToTypeAction = nullptr; + QAction *m_followToTypeInNextSplitAction = nullptr; QAction *m_findUsageAction = nullptr; QAction *m_openCallHierarchyAction = nullptr; QAction *m_renameSymbolAction = nullptr; @@ -224,6 +226,12 @@ void TextEditorActionHandlerPrivate::createActions() m_followSymbolInNextSplitAction = registerAction(FOLLOW_SYMBOL_UNDER_CURSOR_IN_NEXT_SPLIT, [] (TextEditorWidget *w) { w->openLinkUnderCursorInNextSplit(); }, true, Tr::tr("Follow Symbol Under Cursor in Next Split"), QKeySequence(Utils::HostOsInfo::isMacHost() ? Tr::tr("Meta+E, F2") : Tr::tr("Ctrl+E, F2"))); + m_followToTypeAction = registerAction(FOLLOW_SYMBOL_TO_TYPE, + [] (TextEditorWidget *w) { w->openTypeUnderCursor(); }, true, Tr::tr("Follow Type Under Cursor"), + QKeySequence(Tr::tr("Ctrl+Shift+F2"))); + m_followToTypeInNextSplitAction = registerAction(FOLLOW_SYMBOL_TO_TYPE_IN_NEXT_SPLIT, + [] (TextEditorWidget *w) { w->openTypeUnderCursorInNextSplit(); }, true, Tr::tr("Follow Type Under Cursor in Next Split"), + QKeySequence(Utils::HostOsInfo::isMacHost() ? Tr::tr("Meta+E, Shift+F2") : Tr::tr("Ctrl+E, Ctrl+Shift+F2"))); m_findUsageAction = registerAction(FIND_USAGES, [] (TextEditorWidget *w) { w->findUsages(); }, true, Tr::tr("Find References to Symbol Under Cursor"), QKeySequence(Tr::tr("Ctrl+Shift+U"))); @@ -492,6 +500,10 @@ void TextEditorActionHandlerPrivate::updateOptionalActions() optionalActions & TextEditorActionHandler::FollowSymbolUnderCursor); m_followSymbolInNextSplitAction->setEnabled( optionalActions & TextEditorActionHandler::FollowSymbolUnderCursor); + m_followToTypeAction->setEnabled( + optionalActions & TextEditorActionHandler::FollowTypeUnderCursor); + m_followToTypeInNextSplitAction->setEnabled( + optionalActions & TextEditorActionHandler::FollowTypeUnderCursor); m_findUsageAction->setEnabled( optionalActions & TextEditorActionHandler::FindUsage); m_jumpToFileAction->setEnabled( diff --git a/src/plugins/texteditor/texteditoractionhandler.h b/src/plugins/texteditor/texteditoractionhandler.h index 0886b6a909b..e1d06f7e3cd 100644 --- a/src/plugins/texteditor/texteditoractionhandler.h +++ b/src/plugins/texteditor/texteditoractionhandler.h @@ -34,10 +34,11 @@ public: UnCommentSelection = 2, UnCollapseAll = 4, FollowSymbolUnderCursor = 8, - JumpToFileUnderCursor = 16, - RenameSymbol = 32, - FindUsage = 64, - CallHierarchy = 128 + FollowTypeUnderCursor = 16, + JumpToFileUnderCursor = 32, + RenameSymbol = 64, + FindUsage = 128, + CallHierarchy = 256 }; using TextEditorWidgetResolver = std::function; diff --git a/src/plugins/texteditor/texteditorconstants.h b/src/plugins/texteditor/texteditorconstants.h index 03433fd0e8c..4cac6a9dd7f 100644 --- a/src/plugins/texteditor/texteditorconstants.h +++ b/src/plugins/texteditor/texteditorconstants.h @@ -206,6 +206,8 @@ const char INDENT[] = "TextEditor.Indent"; const char UNINDENT[] = "TextEditor.Unindent"; const char FOLLOW_SYMBOL_UNDER_CURSOR[] = "TextEditor.FollowSymbolUnderCursor"; const char FOLLOW_SYMBOL_UNDER_CURSOR_IN_NEXT_SPLIT[] = "TextEditor.FollowSymbolUnderCursorInNextSplit"; +const char FOLLOW_SYMBOL_TO_TYPE[] = "TextEditor.FollowSymbolToType"; +const char FOLLOW_SYMBOL_TO_TYPE_IN_NEXT_SPLIT[] = "TextEditor.FollowSymbolToTypeInNextSplit"; const char FIND_USAGES[] = "TextEditor.FindUsages"; // moved from CppEditor to TextEditor avoid breaking the setting by using the old key const char RENAME_SYMBOL[] = "CppEditor.RenameSymbolUnderCursor";