diff --git a/src/plugins/languageclient/client.cpp b/src/plugins/languageclient/client.cpp index 3d675a1e8b4..0fce76921d6 100644 --- a/src/plugins/languageclient/client.cpp +++ b/src/plugins/languageclient/client.cpp @@ -107,6 +107,10 @@ Client::Client(BaseClientInterface *clientInterface) m_clientProviders.functionHintProvider = new FunctionHintAssistProvider(this); m_clientProviders.quickFixAssistProvider = new LanguageClientQuickFixProvider(this); + m_documentUpdateTimer.setSingleShot(true); + m_documentUpdateTimer.setInterval(500); + connect(&m_documentUpdateTimer, &QTimer::timeout, this, &Client::sendPostponedDocumentUpdates); + m_contentHandler.insert(JsonRpcMessageHandler::jsonRpcMimeType(), &JsonRpcMessageHandler::parseContent); QTC_ASSERT(clientInterface, return); @@ -347,6 +351,7 @@ void Client::sendContent(const IContent &content) { QTC_ASSERT(m_clientInterface, return); QTC_ASSERT(m_state == Initialized, return); + sendPostponedDocumentUpdates(); content.registerResponseHandler(&m_responseHandlers); QString error; if (!QTC_GUARD(content.isValid(&error))) @@ -514,15 +519,8 @@ void Client::documentContentsChanged(TextEditor::TextDocument *document, syncKind = option.isValid(nullptr) ? option.syncKind() : syncKind; } } - auto textDocument = qobject_cast(document); - const auto uri = DocumentUri::fromFilePath(document->filePath()); - m_highlights[uri].clear(); if (syncKind != TextDocumentSyncKind::None) { - VersionedTextDocumentIdentifier docId(uri); - docId.setVersion(textDocument ? textDocument->document()->revision() : 0); - DidChangeTextDocumentParams params; - params.setTextDocument(docId); if (syncKind == TextDocumentSyncKind::Incremental) { DidChangeTextDocumentParams::TextDocumentContentChangeEvent change; QTextDocument oldDoc(m_openedDocument[document]); @@ -539,20 +537,22 @@ void Client::documentContentsChanged(TextEditor::TextDocument *document, change.setRange(Range(cursor)); change.setRangeLength(cursor.selectionEnd() - cursor.selectionStart()); change.setText(document->textAt(position, charsAdded)); - params.setContentChanges({change}); + m_documentsToUpdate[document] << change; } else { - params.setContentChanges({document->plainText()}); + m_documentsToUpdate[document] = {document->plainText()}; } m_openedDocument[document] = document->plainText(); - sendContent(DidChangeTextDocumentNotification(params)); } - if (textDocument) { - using namespace TextEditor; - for (BaseTextEditor *editor : BaseTextEditor::textEditorsForDocument(textDocument)) - if (TextEditorWidget *widget = editor->editorWidget()) - widget->setRefactorMarkers(RefactorMarker::filterOutType(widget->refactorMarkers(), id())); + using namespace TextEditor; + for (BaseTextEditor *editor : BaseTextEditor::textEditorsForDocument(document)) { + if (TextEditorWidget *widget = editor->editorWidget()) { + widget->setRefactorMarkers( + RefactorMarker::filterOutType(widget->refactorMarkers(), id())); + } } + + m_documentUpdateTimer.start(); } void Client::registerCapabilities(const QList ®istrations) @@ -578,6 +578,8 @@ TextEditor::HighlightingResult createHighlightingResult(const SymbolInformation void Client::cursorPositionChanged(TextEditor::TextEditorWidget *widget) { + if (m_documentsToUpdate.contains(widget->textDocument())) + return; // we are currently changing this document so postpone the DocumentHighlightsRequest const auto uri = DocumentUri::fromFilePath(widget->textDocument()->filePath()); if (m_dynamicCapabilities.isRegistered(DocumentHighlightsRequest::methodName).value_or(false)) { TextDocumentRegistrationOptions option( @@ -1131,6 +1133,30 @@ void Client::resetAssistProviders(TextEditor::TextDocument *document) document->setQuickFixAssistProvider(providers.quickFixAssistProvider); } +void Client::sendPostponedDocumentUpdates() +{ + m_documentUpdateTimer.stop(); + if (m_documentsToUpdate.isEmpty()) + return; + TextEditor::TextEditorWidget *currentWidget + = TextEditor::TextEditorWidget::currentTextEditorWidget(); + const QList documents = m_documentsToUpdate.keys(); + for (auto document : documents) { + const auto uri = DocumentUri::fromFilePath(document->filePath()); + m_highlights[uri].clear(); + VersionedTextDocumentIdentifier docId(uri); + docId.setVersion(document->document()->revision()); + DidChangeTextDocumentParams params; + params.setTextDocument(docId); + params.setContentChanges(m_documentsToUpdate.take(document)); + sendContent(DidChangeTextDocumentNotification(params)); + emit documentUpdated(document); + + if (currentWidget->textDocument() == document) + cursorPositionChanged(currentWidget); + } +} + void Client::handleResponse(const MessageId &id, const QByteArray &content, QTextCodec *codec) { if (auto handler = m_responseHandlers[id]) @@ -1283,6 +1309,13 @@ void Client::rehighlight() } } +bool Client::documentUpdatePostponed(const QString &fileName) const +{ + return Utils::contains(m_documentsToUpdate.keys(), [fileName](const TextEditor::TextDocument *doc) { + return doc->filePath() == Utils::FilePath::fromString(fileName); + }); +} + void Client::initializeCallback(const InitializeRequest::Response &initResponse) { QTC_ASSERT(m_state == InitializeRequested, return); diff --git a/src/plugins/languageclient/client.h b/src/plugins/languageclient/client.h index 97078e12d2e..f3246660a45 100644 --- a/src/plugins/languageclient/client.h +++ b/src/plugins/languageclient/client.h @@ -178,8 +178,11 @@ public: HoverHandler *hoverHandler(); void rehighlight(); + bool documentUpdatePostponed(const QString &fileName) const; + signals: void initialized(LanguageServerProtocol::ServerCapabilities capabilities); + void documentUpdated(TextEditor::TextDocument *document); void finished(); protected: @@ -207,6 +210,7 @@ private: void showDiagnostics(const LanguageServerProtocol::DocumentUri &uri); void removeDiagnostics(const LanguageServerProtocol::DocumentUri &uri); void resetAssistProviders(TextEditor::TextDocument *document); + void sendPostponedDocumentUpdates(); using ContentHandler = std::function m_openedDocument; + QMap> + m_documentsToUpdate; + QTimer m_documentUpdateTimer; Core::Id m_id; LanguageServerProtocol::ServerCapabilities m_serverCapabilities; DynamicCapabilities m_dynamicCapabilities; diff --git a/src/plugins/languageclient/languageclientcompletionassist.cpp b/src/plugins/languageclient/languageclientcompletionassist.cpp index 8a279b0dac0..0b38b42edc9 100644 --- a/src/plugins/languageclient/languageclientcompletionassist.cpp +++ b/src/plugins/languageclient/languageclientcompletionassist.cpp @@ -274,6 +274,7 @@ class LanguageClientCompletionAssistProcessor : public IAssistProcessor { public: LanguageClientCompletionAssistProcessor(Client *client); + ~LanguageClientCompletionAssistProcessor() override; IAssistProposal *perform(const AssistInterface *interface) override; bool running() override; bool needsRestart() const override { return true; } @@ -285,6 +286,7 @@ private: QPointer m_document; QPointer m_client; Utils::optional m_currentRequest; + QMetaObject::Connection m_postponedUpdateConnection; int m_pos = -1; }; @@ -292,6 +294,11 @@ LanguageClientCompletionAssistProcessor::LanguageClientCompletionAssistProcessor : m_client(client) { } +LanguageClientCompletionAssistProcessor::~LanguageClientCompletionAssistProcessor() +{ + QTC_ASSERT(!running(), cancel()); +} + static QString assistReasonString(AssistReason reason) { switch (reason) { @@ -315,7 +322,20 @@ IAssistProposal *LanguageClientCompletionAssistProcessor::perform(const AssistIn ++delta; if (delta < 3) return nullptr; + if (m_client->documentUpdatePostponed(interface->fileName())) { + m_postponedUpdateConnection + = QObject::connect(m_client, + &Client::documentUpdated, + [this, interface](TextEditor::TextDocument *document) { + if (document->filePath() + == Utils::FilePath::fromString(interface->fileName())) + perform(interface); + }); + return nullptr; + } } + if (m_postponedUpdateConnection) + QObject::disconnect(m_postponedUpdateConnection); CompletionRequest completionRequest; CompletionParams::CompletionContext context; if (interface->reason() == ActivationCharacter) { @@ -353,15 +373,17 @@ IAssistProposal *LanguageClientCompletionAssistProcessor::perform(const AssistIn bool LanguageClientCompletionAssistProcessor::running() { - return m_currentRequest.has_value(); + return m_currentRequest.has_value() || m_postponedUpdateConnection; } void LanguageClientCompletionAssistProcessor::cancel() { - if (running()) { + if (m_currentRequest.has_value()) { m_client->cancelRequest(m_currentRequest.value()); m_client->removeAssistProcessor(this); m_currentRequest.reset(); + } else if (m_postponedUpdateConnection) { + QObject::disconnect(m_postponedUpdateConnection); } }