aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/languageclient/client.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/languageclient/client.cpp')
-rw-r--r--src/plugins/languageclient/client.cpp263
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> &registrations)
@@ -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 &params)
void Client::handleSemanticHighlight(const SemanticHighlightingParams &params)
{
- 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);