From 468f60490090ee6e01ff900fe381f02efa362e4f Mon Sep 17 00:00:00 2001 From: David Schulz Date: Tue, 10 Sep 2019 08:03:58 +0200 Subject: LanguageClient: have one active client per open document open a document in all clients supporting the document, but have just one client that provide functionality like highlights, completions, and find usages. Change-Id: I6bd72eb022005ed643fefd1da139d482f4dd5279 Reviewed-by: Christian Stenger --- src/plugins/languageclient/client.cpp | 104 +++++++++++++------- src/plugins/languageclient/client.h | 4 +- .../languageclient/languageclientmanager.cpp | 109 ++++++++------------- src/plugins/languageclient/languageclientmanager.h | 11 ++- 4 files changed, 117 insertions(+), 111 deletions(-) diff --git a/src/plugins/languageclient/client.cpp b/src/plugins/languageclient/client.cpp index ad5f8d8e56..c4fb4058b9 100644 --- a/src/plugins/languageclient/client.cpp +++ b/src/plugins/languageclient/client.cpp @@ -278,63 +278,47 @@ Client::State Client::state() const return m_state; } -bool Client::openDocument(TextEditor::TextDocument *document) +void Client::openDocument(TextEditor::TextDocument *document) { using namespace TextEditor; if (!isSupportedDocument(document)) - return false; + return; + + m_openedDocument[document] = document->plainText(); + if (m_state != Initialized) + return; + const FilePath &filePath = document->filePath(); const QString method(DidOpenTextDocumentNotification::methodName); if (Utils::optional registered = m_dynamicCapabilities.isRegistered(method)) { if (!registered.value()) - return false; + return; const TextDocumentRegistrationOptions option( m_dynamicCapabilities.option(method).toObject()); if (option.isValid(nullptr) && !option.filterApplies(filePath, Utils::mimeTypeForName(document->mimeType()))) { - return false; + return; } } else if (Utils::optional _sync = m_serverCapabilities.textDocumentSync()) { if (auto options = Utils::get_if(&_sync.value())) { if (!options->openClose().value_or(true)) - return false; + return; } } - auto uri = DocumentUri::fromFilePath(filePath); - showDiagnostics(uri); - - connect(document, - &TextDocument::contentsChangedWithPosition, - this, + connect(document, &TextDocument::contentsChangedWithPosition, this, [this, document](int position, int charsRemoved, int charsAdded) { - documentContentsChanged(document, position, charsRemoved, charsAdded); - }); - - auto *oldCompletionProvider = qobject_cast( - document->completionAssistProvider()); - // only replace the completion assist provider if it is the default one or null - if (oldCompletionProvider || !document->completionAssistProvider()) - document->setCompletionAssistProvider(m_clientProviders.completionAssistProvider); - m_resetAssistProvider[document] = {oldCompletionProvider, - document->functionHintAssistProvider(), - document->quickFixAssistProvider()}; - document->setFunctionHintAssistProvider(m_clientProviders.functionHintProvider); - document->setQuickFixAssistProvider(m_clientProviders.quickFixAssistProvider); - connect(document, &QObject::destroyed, this, [this, document] { - m_resetAssistProvider.remove(document); + documentContentsChanged(document, position, charsRemoved, charsAdded); }); - const QString &text = document->plainText(); - m_openedDocument.insert(document, text); - TextDocumentItem item; item.setLanguageId(TextDocumentItem::mimeTypeToLanguageId(document->mimeType())); - item.setUri(uri); - item.setText(text); + item.setUri(DocumentUri::fromFilePath(filePath)); + item.setText(document->plainText()); item.setVersion(document->document()->revision()); sendContent(DidOpenTextDocumentNotification(DidOpenTextDocumentParams(item))); - return true; + if (LanguageClientManager::clientForDocument(document) == this) + activateDocument(document); } void Client::sendContent(const IContent &content) @@ -371,7 +355,38 @@ void Client::closeDocument(TextEditor::TextDocument *document) const DidCloseTextDocumentParams params(TextDocumentIdentifier{uri}); m_highlights[uri].clear(); sendContent(uri, DidCloseTextDocumentNotification(params)); + deactivateDocument(document); +} + +void Client::activateDocument(TextEditor::TextDocument *document) +{ + auto uri = DocumentUri::fromFilePath(document->filePath()); + showDiagnostics(uri); + SemanticHighligtingSupport::applyHighlight(document, m_highlights.value(uri), capabilities()); + // only replace the assist provider if the completion provider is the default one or null + if (!document->completionAssistProvider() + || qobject_cast( + document->completionAssistProvider())) { + m_resetAssistProvider[document] = {document->completionAssistProvider(), + document->functionHintAssistProvider(), + document->quickFixAssistProvider()}; + document->setCompletionAssistProvider(m_clientProviders.completionAssistProvider); + document->setFunctionHintAssistProvider(m_clientProviders.functionHintProvider); + document->setQuickFixAssistProvider(m_clientProviders.quickFixAssistProvider); + } + for (Core::IEditor *editor : Core::DocumentModel::editorsForDocument(document)) { + updateEditorToolBar(editor); + if (auto textEditor = qobject_cast(editor)) + textEditor->editorWidget()->addHoverHandler(hoverHandler()); + } +} + +void Client::deactivateDocument(TextEditor::TextDocument *document) +{ + hideDiagnostics(document); resetAssistProviders(document); + if (TextEditor::SyntaxHighlighter *highlighter = document->syntaxHighlighter()) + highlighter->clearAllExtraFormats(); } bool Client::documentOpen(TextEditor::TextDocument *document) const @@ -835,6 +850,8 @@ void Client::showDiagnostics(Core::IDocument *doc) void Client::hideDiagnostics(TextEditor::TextDocument *doc) { + if (!doc) + return; DocumentUri uri = DocumentUri::fromFilePath(doc->filePath()); for (TextMark *mark : m_diagnostics.value(uri)) doc->removeMark(mark); @@ -1038,9 +1055,12 @@ void Client::handleDiagnostics(const PublishDiagnosticsParams ¶ms) Utils::transform(diagnostics, [fileName = uri.toFilePath()](const Diagnostic &diagnostic) { return new TextMark(fileName, diagnostic); }); - showDiagnostics(uri); - - requestCodeActions(uri, diagnostics); + // TextMarks are already added in the TextEditor::TextMark constructor + // so hide them if we are not the active client for this document + if (LanguageClientManager::clientForUri(uri) != this) + hideDiagnostics(TextEditor::TextDocument::textDocumentForFileName(uri.toFilePath())); + else + requestCodeActions(uri, diagnostics); } void Client::handleSemanticHighlight(const SemanticHighlightingParams ¶ms) @@ -1051,8 +1071,10 @@ void Client::handleSemanticHighlight(const SemanticHighlightingParams ¶ms) TextEditor::TextDocument *doc = TextEditor::TextDocument::textDocumentForFileName( uri.toFilePath()); - if (!doc || (!version.isNull() && doc->document()->revision() != version.value())) + if (!doc || LanguageClientManager::clientForDocument(doc) != this + || (!version.isNull() && doc->document()->revision() != version.value())) { return; + } const TextEditor::HighlightingResults results = SemanticHighligtingSupport::generateResults( params.lines()); @@ -1066,8 +1088,10 @@ void Client::rehighlight() { using namespace TextEditor; for (auto it = m_highlights.begin(), end = m_highlights.end(); it != end; ++it) { - if (TextDocument *doc = TextDocument::textDocumentForFileName(it.key().toFilePath())) - SemanticHighligtingSupport::applyHighlight(doc, it.value(), capabilities()); + if (TextDocument *doc = TextDocument::textDocumentForFileName(it.key().toFilePath())) { + if (LanguageClientManager::clientForDocument(doc) == this) + SemanticHighligtingSupport::applyHighlight(doc, it.value(), capabilities()); + } } } @@ -1131,6 +1155,10 @@ void Client::intializeCallback(const InitializeRequest::Response &initResponse) .value_or(capabilities().documentSymbolProvider().value_or(false))) { TextEditor::IOutlineWidgetFactory::updateOutline(); } + + for (TextEditor::TextDocument *document : m_openedDocument.keys()) + openDocument(document); + emit initialized(m_serverCapabilities); } diff --git a/src/plugins/languageclient/client.h b/src/plugins/languageclient/client.h index eadcdfefd4..eca70444a6 100644 --- a/src/plugins/languageclient/client.h +++ b/src/plugins/languageclient/client.h @@ -95,8 +95,10 @@ public: bool reachable() const { return m_state == Initialized; } // document synchronization - bool openDocument(TextEditor::TextDocument *document); + void openDocument(TextEditor::TextDocument *document); void closeDocument(TextEditor::TextDocument *document); + void activateDocument(TextEditor::TextDocument *document); + void deactivateDocument(TextEditor::TextDocument *document); bool documentOpen(TextEditor::TextDocument *document) const; void documentContentsSaved(TextEditor::TextDocument *document); void documentWillSave(Core::IDocument *document); diff --git a/src/plugins/languageclient/languageclientmanager.cpp b/src/plugins/languageclient/languageclientmanager.cpp index e84d69768e..923b521bfd 100644 --- a/src/plugins/languageclient/languageclientmanager.cpp +++ b/src/plugins/languageclient/languageclientmanager.cpp @@ -117,9 +117,6 @@ void LanguageClientManager::startClient(Client *client) &Client::initialized, &managerInstance->m_currentDocumentLocatorFilter, &DocumentLocatorFilter::updateCurrentClient); - connect(client, &Client::initialized, managerInstance, [client]() { - managerInstance->clientInitialized(client); - }); } Client *LanguageClientManager::startClient(BaseSettings *setting, ProjectExplorer::Project *project) @@ -301,19 +298,23 @@ Client *LanguageClientManager::clientForDocument(TextEditor::TextDocument *docum return document == nullptr ? nullptr : managerInstance->m_clientForDocument[document].data(); } -bool LanguageClientManager::reOpenDocumentWithClient(TextEditor::TextDocument *document, Client *client) +Client *LanguageClientManager::clientForFilePath(const Utils::FilePath &filePath) { - Utils::ExecuteOnDestruction outlineUpdater(&TextEditor::IOutlineWidgetFactory::updateOutline); - if (Client *currentClient = clientForDocument(document)) { - currentClient->closeDocument(document); - currentClient->hideDiagnostics(document); - } - managerInstance->m_clientForDocument.remove(document); - if (!managerInstance->openDocumentWithClient(document, client)) - return false; + return clientForDocument(TextEditor::TextDocument::textDocumentForFileName(filePath)); +} - client->showDiagnostics(document); - return true; +Client *LanguageClientManager::clientForUri(const DocumentUri &uri) +{ + return clientForFilePath(uri.toFilePath()); +} + +void LanguageClientManager::reOpenDocumentWithClient(TextEditor::TextDocument *document, Client *client) +{ + Utils::ExecuteOnDestruction outlineUpdater(&TextEditor::IOutlineWidgetFactory::updateOutline); + if (Client *currentClient = clientForDocument(document)) + currentClient->deactivateDocument(document); + managerInstance->m_clientForDocument[document] = client; + client->activateDocument(document); } QVector LanguageClientManager::reachableClients() @@ -332,19 +333,6 @@ void LanguageClientManager::sendToAllReachableServers(const IContent &content) sendToInterfaces(content, reachableClients()); } -void LanguageClientManager::clientInitialized(Client *client) -{ - for (auto document : m_clientForDocument.keys(client)) { - if (client->openDocument(document)) { - for (Core::IEditor *editor : Core::DocumentModel::editorsForDocument(document)) { - updateEditorToolBar(editor); - if (auto textEditor = qobject_cast(editor)) - textEditor->editorWidget()->addHoverHandler(client->hoverHandler()); - } - } - } -} - void LanguageClientManager::clientFinished(Client *client) { constexpr int restartTimeoutS = 5; @@ -372,27 +360,23 @@ void LanguageClientManager::editorOpened(Core::IEditor *editor) if (auto *textEditor = qobject_cast(editor)) { if (TextEditorWidget *widget = textEditor->editorWidget()) { connect(widget, &TextEditorWidget::requestLinkAt, this, - [this, filePath = editor->document()->filePath()] + [this, document = textEditor->textDocument()] (const QTextCursor &cursor, Utils::ProcessLinkCallback &callback) { - findLinkAt(filePath, cursor, callback); + findLinkAt(document, cursor, callback); }); connect(widget, &TextEditorWidget::requestUsages, this, - [this, filePath = editor->document()->filePath()] - (const QTextCursor &cursor){ - findUsages(filePath, cursor); - }); - connect(widget, &TextEditorWidget::cursorPositionChanged, this, [this, widget](){ - // TODO This would better be a compressing timer - QTimer::singleShot(50, this, - [this, widget = QPointer(widget)]() { - if (widget) { - for (Client *client : this->reachableClients()) { - if (client->isSupportedDocument(widget->textDocument())) - client->cursorPositionChanged(widget); - } - } - }); + [this, document = textEditor->textDocument()](const QTextCursor &cursor) { + findUsages(document, cursor); }); + connect(widget, &TextEditorWidget::cursorPositionChanged, this, [this, widget]() { + // TODO This would better be a compressing timer + QTimer::singleShot(50, this, [widget = QPointer(widget)]() { + if (!widget) + return; + if (Client *client = clientForDocument(widget->textDocument())) + client->cursorPositionChanged(widget); + }); + }); updateEditorToolBar(editor); if (TextEditor::TextDocument *document = textEditor->textDocument()) { if (Client *client = m_clientForDocument[document]) @@ -409,7 +393,6 @@ void LanguageClientManager::documentOpened(Core::IDocument *document) return; // check whether we have to start servers for this document - bool opened = false; for (BaseSettings *setting : LanguageClientSettings::currentPageSettings()) { QVector clients = clientForSetting(setting); if (setting->isValid() && setting->m_enabled @@ -434,28 +417,20 @@ void LanguageClientManager::documentOpened(Core::IDocument *document) } else if (setting->m_startBehavior == BaseSettings::RequiresFile && clients.isEmpty()) { clients << startClient(setting); } - if (opened || clients.isEmpty()) - continue; - for (auto client : clients) { - if (openDocumentWithClient(textDocument, client)) { - opened = true; - continue; - } + openDocumentWithClient(textDocument, client); + if (m_clientForDocument.value(textDocument).isNull()) + m_clientForDocument[textDocument] = client; } } } } -bool LanguageClientManager::openDocumentWithClient(TextEditor::TextDocument *document, +void LanguageClientManager::openDocumentWithClient(TextEditor::TextDocument *document, Client *client) { - if (!client || client->state() == Client::Error) - return false; - m_clientForDocument[document] = client; - if (client->state() == Client::Initialized) - return client->openDocument(document); - return true; + if (client && client->state() != Client::Error) + client->openDocument(document); } void LanguageClientManager::documentClosed(Core::IDocument *document) @@ -483,14 +458,14 @@ void LanguageClientManager::documentWillSave(Core::IDocument *document) } } -void LanguageClientManager::findLinkAt(const Utils::FilePath &filePath, +void LanguageClientManager::findLinkAt(TextEditor::TextDocument *document, const QTextCursor &cursor, Utils::ProcessLinkCallback callback) { - const DocumentUri uri = DocumentUri::fromFilePath(filePath); - const TextDocumentIdentifier document(uri); + const DocumentUri uri = DocumentUri::fromFilePath(document->filePath()); + const TextDocumentIdentifier documentId(uri); const Position pos(cursor); - TextDocumentPositionParams params(document, pos); + TextDocumentPositionParams params(documentId, pos); GotoDefinitionRequest request(params); request.setResponseCallback([callback](const GotoDefinitionRequest::Response &response){ if (Utils::optional _result = response.result()) { @@ -547,14 +522,14 @@ QList generateSearchResultItems(const LanguageClientArra return result; } -void LanguageClientManager::findUsages(const Utils::FilePath &filePath, const QTextCursor &cursor) +void LanguageClientManager::findUsages(TextEditor::TextDocument *document, const QTextCursor &cursor) { - const DocumentUri uri = DocumentUri::fromFilePath(filePath); - const TextDocumentIdentifier document(uri); + const DocumentUri uri = DocumentUri::fromFilePath(document->filePath()); + const TextDocumentIdentifier documentId(uri); const Position pos(cursor); QTextCursor termCursor(cursor); termCursor.select(QTextCursor::WordUnderCursor); - ReferenceParams params(TextDocumentPositionParams(document, pos)); + ReferenceParams params(TextDocumentPositionParams(documentId, pos)); params.setContext(ReferenceParams::ReferenceContext(true)); FindReferencesRequest request(params); auto callback = [this, wordUnderCursor = termCursor.selectedText()] diff --git a/src/plugins/languageclient/languageclientmanager.h b/src/plugins/languageclient/languageclientmanager.h index f12e2f9d48..8c03eaa26b 100644 --- a/src/plugins/languageclient/languageclientmanager.h +++ b/src/plugins/languageclient/languageclientmanager.h @@ -77,7 +77,9 @@ public: static QVector clientForSetting(const BaseSettings *setting); static const BaseSettings *settingForClient(Client *setting); static Client *clientForDocument(TextEditor::TextDocument *document); - static bool reOpenDocumentWithClient(TextEditor::TextDocument *document, Client *client); + static Client *clientForFilePath(const Utils::FilePath &filePath); + static Client *clientForUri(const LanguageServerProtocol::DocumentUri &uri); + static void reOpenDocumentWithClient(TextEditor::TextDocument *document, Client *client); signals: void shutdownFinished(); @@ -87,13 +89,13 @@ private: void editorOpened(Core::IEditor *editor); void documentOpened(Core::IDocument *document); - bool openDocumentWithClient(TextEditor::TextDocument *document, Client *client); + void openDocumentWithClient(TextEditor::TextDocument *document, Client *client); void documentClosed(Core::IDocument *document); void documentContentsSaved(Core::IDocument *document); void documentWillSave(Core::IDocument *document); - void findLinkAt(const Utils::FilePath &filePath, const QTextCursor &cursor, + void findLinkAt(TextEditor::TextDocument *document, const QTextCursor &cursor, Utils::ProcessLinkCallback callback); - void findUsages(const Utils::FilePath &filePath, const QTextCursor &cursor); + void findUsages(TextEditor::TextDocument *document, const QTextCursor &cursor); void projectAdded(ProjectExplorer::Project *project); void projectRemoved(ProjectExplorer::Project *project); @@ -101,7 +103,6 @@ private: QVector reachableClients(); void sendToAllReachableServers(const LanguageServerProtocol::IContent &content); - void clientInitialized(Client *client); void clientFinished(Client *client); bool m_shuttingDown = false; -- cgit v1.2.1