diff options
Diffstat (limited to 'src/plugins/languageclient/client.cpp')
-rw-r--r-- | src/plugins/languageclient/client.cpp | 263 |
1 files changed, 190 insertions, 73 deletions
diff --git a/src/plugins/languageclient/client.cpp b/src/plugins/languageclient/client.cpp index e3b8adc6d5..acfd0d3a69 100644 --- a/src/plugins/languageclient/client.cpp +++ b/src/plugins/languageclient/client.cpp @@ -40,13 +40,15 @@ #include <projectexplorer/project.h> #include <projectexplorer/session.h> #include <texteditor/codeassist/documentcontentcompletion.h> +#include <texteditor/codeassist/iassistprocessor.h> +#include <texteditor/ioutlinewidget.h> #include <texteditor/syntaxhighlighter.h> #include <texteditor/tabsettings.h> #include <texteditor/textdocument.h> #include <texteditor/texteditor.h> +#include <texteditor/texteditoractionhandler.h> #include <texteditor/texteditorsettings.h> #include <texteditor/textmark.h> -#include <texteditor/ioutlinewidget.h> #include <utils/mimetypes/mimedatabase.h> #include <utils/qtcprocess.h> #include <utils/synchronousprocess.h> @@ -72,7 +74,7 @@ static Q_LOGGING_CATEGORY(LOGLSPCLIENT, "qtc.languageclient.client", QtWarningMs class TextMark : public TextEditor::TextMark { public: - TextMark(const Utils::FilePath &fileName, const Diagnostic &diag, const Core::Id &clientId) + TextMark(const Utils::FilePath &fileName, const Diagnostic &diag, const Utils::Id &clientId) : TextEditor::TextMark(fileName, diag.range().start().line() + 1, clientId) , m_diagnostic(diag) { @@ -95,15 +97,20 @@ private: }; Client::Client(BaseClientInterface *clientInterface) - : m_id(Core::Id::fromString(QUuid::createUuid().toString())) + : m_id(Utils::Id::fromString(QUuid::createUuid().toString())) , m_clientInterface(clientInterface) , m_documentSymbolCache(this) , m_hoverHandler(this) + , m_symbolSupport(this) { m_clientProviders.completionAssistProvider = new LanguageClientCompletionAssistProvider(this); 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); @@ -146,6 +153,8 @@ Client::~Client() highlighter->clearAllExtraFormats(); } } + for (IAssistProcessor *processor : m_runningAssistProcessors) + processor->setAsyncProposalAvailable(nullptr); updateEditorToolBar(m_openedDocument.keys()); } @@ -231,6 +240,11 @@ static ClientCapabilities generateClientCapabilities() hover.setDynamicRegistration(true); documentCapabilities.setHover(hover); + TextDocumentClientCapabilities::RenameClientCapabilities rename; + rename.setPrepareSupport(true); + rename.setDynamicRegistration(true); + documentCapabilities.setRename(rename); + documentCapabilities.setReferences(allowDynamicRegistration); documentCapabilities.setDocumentHighlight(allowDynamicRegistration); documentCapabilities.setDefinition(allowDynamicRegistration); @@ -250,6 +264,7 @@ void Client::initialize() InitializeRequest initRequest; auto params = initRequest.params().value_or(InitializeParams()); params.setCapabilities(generateClientCapabilities()); + params.setInitializationOptions(m_initializationOptions); if (m_project) { params.setRootUri(DocumentUri::fromFilePath(m_project->projectDirectory())); params.setWorkSpaceFolders(Utils::transform(SessionManager::projects(), [](Project *pro){ @@ -325,14 +340,18 @@ void Client::openDocument(TextEditor::TextDocument *document) item.setVersion(document->document()->revision()); sendContent(DidOpenTextDocumentNotification(DidOpenTextDocumentParams(item))); - if (LanguageClientManager::clientForDocument(document) == this) + const Client *currentClient = LanguageClientManager::clientForDocument(document); + if (currentClient == this) // this is the active client for the document so directly activate it activateDocument(document); + else if (currentClient == nullptr) // there is no client for this document so assign it to this server + LanguageClientManager::openDocumentWithClient(document, this); } 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))) @@ -374,22 +393,29 @@ 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<TextEditor::DocumentContentCompletionProvider *>( - document->completionAssistProvider())) { - m_resetAssistProvider[document] = {document->completionAssistProvider(), - document->functionHintAssistProvider(), - document->quickFixAssistProvider()}; + // only replace the assist provider if the language server support it + if (m_serverCapabilities.completionProvider()) { + m_resetAssistProvider[document].completionAssistProvider = document->completionAssistProvider(); document->setCompletionAssistProvider(m_clientProviders.completionAssistProvider); + } + if (m_serverCapabilities.signatureHelpProvider()) { + m_resetAssistProvider[document].functionHintProvider = document->functionHintAssistProvider(); document->setFunctionHintAssistProvider(m_clientProviders.functionHintProvider); + } + if (m_serverCapabilities.codeActionProvider()) { + m_resetAssistProvider[document].quickFixAssistProvider = document->quickFixAssistProvider(); document->setQuickFixAssistProvider(m_clientProviders.quickFixAssistProvider); } document->setFormatter(new LanguageClientFormatter(document, this)); for (Core::IEditor *editor : Core::DocumentModel::editorsForDocument(document)) { updateEditorToolBar(editor); - if (auto textEditor = qobject_cast<TextEditor::BaseTextEditor *>(editor)) + if (auto textEditor = qobject_cast<TextEditor::BaseTextEditor *>(editor)) { textEditor->editorWidget()->addHoverHandler(&m_hoverHandler); + if (symbolSupport().supportsRename(document)) { + textEditor->editorWidget()->addOptionalActions( + TextEditor::TextEditorActionHandler::RenameSymbol); + } + } } } @@ -493,38 +519,40 @@ void Client::documentContentsChanged(TextEditor::TextDocument *document, syncKind = option.isValid(nullptr) ? option.syncKind() : syncKind; } } - auto textDocument = qobject_cast<TextEditor::TextDocument *>(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]); QTextCursor cursor(&oldDoc); - cursor.setPosition(position + charsRemoved); + // Workaround https://bugreports.qt.io/browse/QTBUG-80662 + // The contentsChanged gives a character count that can be wrong for QTextCursor + // when there are special characters removed/added (like formating characters). + // Also, characterCount return the number of characters + 1 because of the hidden + // paragraph separator character. + // This implementation is based on QWidgetTextControlPrivate::_q_contentsChanged. + // For charsAdded, textAt handles the case itself. + cursor.setPosition(qMin(oldDoc.characterCount() - 1, position + charsRemoved)); cursor.setPosition(position, QTextCursor::KeepAnchor); 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<Registration> ®istrations) @@ -537,43 +565,6 @@ void Client::unregisterCapabilities(const QList<Unregistration> &unregistrations m_dynamicCapabilities.unregisterCapability(unregistrations); } -template <typename Request> -static bool sendTextDocumentPositionParamsRequest(Client *client, - const Request &request, - const DynamicCapabilities &dynamicCapabilities, - const optional<bool> &serverCapability) -{ - if (!request.isValid(nullptr)) - return false; - const DocumentUri uri = request.params().value().textDocument().uri(); - const bool supportedFile = client->isSupportedUri(uri); - bool sendMessage = dynamicCapabilities.isRegistered(Request::methodName).value_or(false); - if (sendMessage) { - const TextDocumentRegistrationOptions option(dynamicCapabilities.option(Request::methodName)); - if (option.isValid(nullptr)) - sendMessage = option.filterApplies(FilePath::fromString(QUrl(uri).adjusted(QUrl::PreferLocalFile).toString())); - else - sendMessage = supportedFile; - } else { - sendMessage = serverCapability.value_or(sendMessage) && supportedFile; - } - if (sendMessage) - client->sendContent(request); - return sendMessage; -} - -bool Client::findLinkAt(GotoDefinitionRequest &request) -{ - return LanguageClient::sendTextDocumentPositionParamsRequest( - this, request, m_dynamicCapabilities, m_serverCapabilities.definitionProvider()); -} - -bool Client::findUsages(FindReferencesRequest &request) -{ - return LanguageClient::sendTextDocumentPositionParamsRequest( - this, request, m_dynamicCapabilities, m_serverCapabilities.referencesProvider()); -} - TextEditor::HighlightingResult createHighlightingResult(const SymbolInformation &info) { if (!info.isValid(nullptr)) @@ -587,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( @@ -636,6 +629,11 @@ void Client::cursorPositionChanged(TextEditor::TextEditorWidget *widget) sendContent(request); } +SymbolSupport &Client::symbolSupport() +{ + return m_symbolSupport; +} + void Client::requestCodeActions(const DocumentUri &uri, const QList<Diagnostic> &diagnostics) { const Utils::FilePath fileName = uri.toFilePath(); @@ -829,7 +827,12 @@ const ProjectExplorer::Project *Client::project() const void Client::setCurrentProject(ProjectExplorer::Project *project) { + using namespace ProjectExplorer; + if (m_project) + disconnect(m_project, &Project::fileListChanged, this, &Client::projectFileListChanged); m_project = project; + if (m_project) + connect(m_project, &Project::fileListChanged, this, &Client::projectFileListChanged); } void Client::projectOpened(ProjectExplorer::Project *project) @@ -865,11 +868,26 @@ void Client::projectClosed(ProjectExplorer::Project *project) sendContent(change); } +void Client::projectFileListChanged() +{ + for (Core::IDocument *doc : Core::DocumentModel::openedDocuments()) { + if (m_project->isKnownFile(doc->filePath())) { + if (auto textDocument = qobject_cast<TextEditor::TextDocument *>(doc)) + openDocument(textDocument); + } + } +} + void Client::setSupportedLanguage(const LanguageFilter &filter) { m_languagFilter = filter; } +void Client::setInitializationOptions(const QJsonObject &initializationOptions) +{ + m_initializationOptions = initializationOptions; +} + bool Client::isSupportedDocument(const TextEditor::TextDocument *document) const { QTC_ASSERT(document, return false); @@ -887,11 +905,22 @@ bool Client::isSupportedUri(const DocumentUri &uri) const Utils::mimeTypeForFile(uri.toFilePath().fileName()).name()); } +void Client::addAssistProcessor(TextEditor::IAssistProcessor *processor) +{ + m_runningAssistProcessors.insert(processor); +} + +void Client::removeAssistProcessor(TextEditor::IAssistProcessor *processor) +{ + m_runningAssistProcessors.remove(processor); +} + bool Client::needsRestart(const BaseSettings *settings) const { QTC_ASSERT(settings, return false); return m_languagFilter.mimeTypes != settings->m_languageFilter.mimeTypes - || m_languagFilter.filePattern != settings->m_languageFilter.filePattern; + || m_languagFilter.filePattern != settings->m_languageFilter.filePattern + || m_initializationOptions != settings->initializationOptions(); } QList<Diagnostic> Client::diagnosticsAt(const DocumentUri &uri, const Range &range) const @@ -926,6 +955,9 @@ bool Client::reset() document->disconnect(this); for (TextEditor::TextDocument *document : m_resetAssistProvider.keys()) resetAssistProviders(document); + for (TextEditor::IAssistProcessor *processor : m_runningAssistProcessors) + processor->setAsyncProposalAvailable(nullptr); + m_runningAssistProcessors.clear(); return true; } @@ -1029,13 +1061,52 @@ void Client::showMessageBox(const ShowMessageRequestParams &message, const Messa box->show(); } +static void addDiagnosticsSelections(const Diagnostic &diagnostic, + QTextDocument *textDocument, + QList<QTextEdit::ExtraSelection> &extraSelections) +{ + QTextCursor cursor(textDocument); + cursor.setPosition(::Utils::Text::positionInText(textDocument, + diagnostic.range().start().line() + 1, + diagnostic.range().start().character() + 1)); + cursor.setPosition(::Utils::Text::positionInText(textDocument, + diagnostic.range().end().line() + 1, + diagnostic.range().end().character() + 1), + QTextCursor::KeepAnchor); + + QTextCharFormat format; + const TextEditor::FontSettings& fontSettings = TextEditor::TextEditorSettings::instance()->fontSettings(); + DiagnosticSeverity severity = diagnostic.severity().value_or(DiagnosticSeverity::Warning); + + if (severity == DiagnosticSeverity::Error) { + format = fontSettings.toTextCharFormat(TextEditor::C_ERROR); + } else { + format = fontSettings.toTextCharFormat(TextEditor::C_WARNING); + } + + extraSelections.push_back(std::move(QTextEdit::ExtraSelection{cursor, format})); +} + void Client::showDiagnostics(const DocumentUri &uri) { const FilePath &filePath = uri.toFilePath(); if (TextEditor::TextDocument *doc = TextEditor::TextDocument::textDocumentForFilePath( - uri.toFilePath())) { - for (const Diagnostic &diagnostic : m_diagnostics.value(uri)) + filePath)) { + QList<QTextEdit::ExtraSelection> extraSelections; + + for (const Diagnostic &diagnostic : m_diagnostics.value(uri)) { doc->addMark(new TextMark(filePath, diagnostic, id())); + addDiagnosticsSelections(diagnostic, doc->document(), extraSelections); + } + + for (Core::IEditor *editor : Core::DocumentModel::editorsForDocument(doc)) { + if (auto textEditor = qobject_cast<TextEditor::BaseTextEditor *>(editor)) { + TextEditor::TextEditorWidget *widget = textEditor->editorWidget(); + + widget->setExtraSelections(TextEditor::TextEditorWidget::CodeWarningsSelection, + extraSelections); + } + } } } @@ -1048,14 +1119,44 @@ void Client::removeDiagnostics(const DocumentUri &uri) void Client::resetAssistProviders(TextEditor::TextDocument *document) { const AssistProviders providers = m_resetAssistProvider.take(document); - if (document->completionAssistProvider() == m_clientProviders.completionAssistProvider) + + if (document->completionAssistProvider() == m_clientProviders.completionAssistProvider && + providers.completionAssistProvider) document->setCompletionAssistProvider(providers.completionAssistProvider); - if (document->functionHintAssistProvider() == m_clientProviders.functionHintProvider) + + if (document->functionHintAssistProvider() == m_clientProviders.functionHintProvider && + providers.functionHintProvider) document->setFunctionHintAssistProvider(providers.functionHintProvider); - if (document->quickFixAssistProvider() == m_clientProviders.quickFixAssistProvider) + + if (document->quickFixAssistProvider() == m_clientProviders.quickFixAssistProvider && + providers.quickFixAssistProvider) document->setQuickFixAssistProvider(providers.quickFixAssistProvider); } +void Client::sendPostponedDocumentUpdates() +{ + m_documentUpdateTimer.stop(); + if (m_documentsToUpdate.isEmpty()) + return; + TextEditor::TextEditorWidget *currentWidget + = TextEditor::TextEditorWidget::currentTextEditorWidget(); + const QList<TextEditor::TextDocument *> 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]) @@ -1169,9 +1270,18 @@ void Client::handleDiagnostics(const PublishDiagnosticsParams ¶ms) void Client::handleSemanticHighlight(const SemanticHighlightingParams ¶ms) { - const DocumentUri &uri = params.textDocument().uri(); + DocumentUri uri; + LanguageClientValue<int> version; + auto textDocument = params.textDocument(); + + if (Utils::holds_alternative<VersionedTextDocumentIdentifier>(textDocument)) { + uri = Utils::get<VersionedTextDocumentIdentifier>(textDocument).uri(); + version = Utils::get<VersionedTextDocumentIdentifier>(textDocument).version(); + } else { + uri = Utils::get<TextDocumentIdentifier>(textDocument).uri(); + } + m_highlights[uri].clear(); - const LanguageClientValue<int> &version = params.textDocument().version(); TextEditor::TextDocument *doc = TextEditor::TextDocument::textDocumentForFilePath( uri.toFilePath()); @@ -1199,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); |