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 <christian.stenger@qt.io>
This commit is contained in:
David Schulz
2019-09-10 08:03:58 +02:00
parent 6200af0822
commit 468f604900
4 changed files with 117 additions and 111 deletions

View File

@@ -278,63 +278,47 @@ Client::State Client::state() const
return m_state; return m_state;
} }
bool Client::openDocument(TextEditor::TextDocument *document) void Client::openDocument(TextEditor::TextDocument *document)
{ {
using namespace TextEditor; using namespace TextEditor;
if (!isSupportedDocument(document)) if (!isSupportedDocument(document))
return false; return;
m_openedDocument[document] = document->plainText();
if (m_state != Initialized)
return;
const FilePath &filePath = document->filePath(); const FilePath &filePath = document->filePath();
const QString method(DidOpenTextDocumentNotification::methodName); const QString method(DidOpenTextDocumentNotification::methodName);
if (Utils::optional<bool> registered = m_dynamicCapabilities.isRegistered(method)) { if (Utils::optional<bool> registered = m_dynamicCapabilities.isRegistered(method)) {
if (!registered.value()) if (!registered.value())
return false; return;
const TextDocumentRegistrationOptions option( const TextDocumentRegistrationOptions option(
m_dynamicCapabilities.option(method).toObject()); m_dynamicCapabilities.option(method).toObject());
if (option.isValid(nullptr) if (option.isValid(nullptr)
&& !option.filterApplies(filePath, Utils::mimeTypeForName(document->mimeType()))) { && !option.filterApplies(filePath, Utils::mimeTypeForName(document->mimeType()))) {
return false; return;
} }
} else if (Utils::optional<ServerCapabilities::TextDocumentSync> _sync } else if (Utils::optional<ServerCapabilities::TextDocumentSync> _sync
= m_serverCapabilities.textDocumentSync()) { = m_serverCapabilities.textDocumentSync()) {
if (auto options = Utils::get_if<TextDocumentSyncOptions>(&_sync.value())) { if (auto options = Utils::get_if<TextDocumentSyncOptions>(&_sync.value())) {
if (!options->openClose().value_or(true)) if (!options->openClose().value_or(true))
return false; return;
} }
} }
auto uri = DocumentUri::fromFilePath(filePath); connect(document, &TextDocument::contentsChangedWithPosition, this,
showDiagnostics(uri);
connect(document,
&TextDocument::contentsChangedWithPosition,
this,
[this, document](int position, int charsRemoved, int charsAdded) { [this, document](int position, int charsRemoved, int charsAdded) {
documentContentsChanged(document, position, charsRemoved, charsAdded); documentContentsChanged(document, position, charsRemoved, charsAdded);
}); });
auto *oldCompletionProvider = qobject_cast<DocumentContentCompletionProvider *>(
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);
});
const QString &text = document->plainText();
m_openedDocument.insert(document, text);
TextDocumentItem item; TextDocumentItem item;
item.setLanguageId(TextDocumentItem::mimeTypeToLanguageId(document->mimeType())); item.setLanguageId(TextDocumentItem::mimeTypeToLanguageId(document->mimeType()));
item.setUri(uri); item.setUri(DocumentUri::fromFilePath(filePath));
item.setText(text); item.setText(document->plainText());
item.setVersion(document->document()->revision()); item.setVersion(document->document()->revision());
sendContent(DidOpenTextDocumentNotification(DidOpenTextDocumentParams(item))); sendContent(DidOpenTextDocumentNotification(DidOpenTextDocumentParams(item)));
return true; if (LanguageClientManager::clientForDocument(document) == this)
activateDocument(document);
} }
void Client::sendContent(const IContent &content) void Client::sendContent(const IContent &content)
@@ -371,7 +355,38 @@ void Client::closeDocument(TextEditor::TextDocument *document)
const DidCloseTextDocumentParams params(TextDocumentIdentifier{uri}); const DidCloseTextDocumentParams params(TextDocumentIdentifier{uri});
m_highlights[uri].clear(); m_highlights[uri].clear();
sendContent(uri, DidCloseTextDocumentNotification(params)); 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<TextEditor::DocumentContentCompletionProvider *>(
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<TextEditor::BaseTextEditor *>(editor))
textEditor->editorWidget()->addHoverHandler(hoverHandler());
}
}
void Client::deactivateDocument(TextEditor::TextDocument *document)
{
hideDiagnostics(document);
resetAssistProviders(document); resetAssistProviders(document);
if (TextEditor::SyntaxHighlighter *highlighter = document->syntaxHighlighter())
highlighter->clearAllExtraFormats();
} }
bool Client::documentOpen(TextEditor::TextDocument *document) const bool Client::documentOpen(TextEditor::TextDocument *document) const
@@ -835,6 +850,8 @@ void Client::showDiagnostics(Core::IDocument *doc)
void Client::hideDiagnostics(TextEditor::TextDocument *doc) void Client::hideDiagnostics(TextEditor::TextDocument *doc)
{ {
if (!doc)
return;
DocumentUri uri = DocumentUri::fromFilePath(doc->filePath()); DocumentUri uri = DocumentUri::fromFilePath(doc->filePath());
for (TextMark *mark : m_diagnostics.value(uri)) for (TextMark *mark : m_diagnostics.value(uri))
doc->removeMark(mark); doc->removeMark(mark);
@@ -1038,8 +1055,11 @@ void Client::handleDiagnostics(const PublishDiagnosticsParams &params)
Utils::transform(diagnostics, [fileName = uri.toFilePath()](const Diagnostic &diagnostic) { Utils::transform(diagnostics, [fileName = uri.toFilePath()](const Diagnostic &diagnostic) {
return new TextMark(fileName, diagnostic); return new TextMark(fileName, diagnostic);
}); });
showDiagnostics(uri); // 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); requestCodeActions(uri, diagnostics);
} }
@@ -1051,8 +1071,10 @@ void Client::handleSemanticHighlight(const SemanticHighlightingParams &params)
TextEditor::TextDocument *doc = TextEditor::TextDocument::textDocumentForFileName( TextEditor::TextDocument *doc = TextEditor::TextDocument::textDocumentForFileName(
uri.toFilePath()); 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; return;
}
const TextEditor::HighlightingResults results = SemanticHighligtingSupport::generateResults( const TextEditor::HighlightingResults results = SemanticHighligtingSupport::generateResults(
params.lines()); params.lines());
@@ -1066,10 +1088,12 @@ void Client::rehighlight()
{ {
using namespace TextEditor; using namespace TextEditor;
for (auto it = m_highlights.begin(), end = m_highlights.end(); it != end; ++it) { for (auto it = m_highlights.begin(), end = m_highlights.end(); it != end; ++it) {
if (TextDocument *doc = TextDocument::textDocumentForFileName(it.key().toFilePath())) if (TextDocument *doc = TextDocument::textDocumentForFileName(it.key().toFilePath())) {
if (LanguageClientManager::clientForDocument(doc) == this)
SemanticHighligtingSupport::applyHighlight(doc, it.value(), capabilities()); SemanticHighligtingSupport::applyHighlight(doc, it.value(), capabilities());
} }
} }
}
void Client::intializeCallback(const InitializeRequest::Response &initResponse) void Client::intializeCallback(const InitializeRequest::Response &initResponse)
{ {
@@ -1131,6 +1155,10 @@ void Client::intializeCallback(const InitializeRequest::Response &initResponse)
.value_or(capabilities().documentSymbolProvider().value_or(false))) { .value_or(capabilities().documentSymbolProvider().value_or(false))) {
TextEditor::IOutlineWidgetFactory::updateOutline(); TextEditor::IOutlineWidgetFactory::updateOutline();
} }
for (TextEditor::TextDocument *document : m_openedDocument.keys())
openDocument(document);
emit initialized(m_serverCapabilities); emit initialized(m_serverCapabilities);
} }

View File

@@ -95,8 +95,10 @@ public:
bool reachable() const { return m_state == Initialized; } bool reachable() const { return m_state == Initialized; }
// document synchronization // document synchronization
bool openDocument(TextEditor::TextDocument *document); void openDocument(TextEditor::TextDocument *document);
void closeDocument(TextEditor::TextDocument *document); void closeDocument(TextEditor::TextDocument *document);
void activateDocument(TextEditor::TextDocument *document);
void deactivateDocument(TextEditor::TextDocument *document);
bool documentOpen(TextEditor::TextDocument *document) const; bool documentOpen(TextEditor::TextDocument *document) const;
void documentContentsSaved(TextEditor::TextDocument *document); void documentContentsSaved(TextEditor::TextDocument *document);
void documentWillSave(Core::IDocument *document); void documentWillSave(Core::IDocument *document);

View File

@@ -117,9 +117,6 @@ void LanguageClientManager::startClient(Client *client)
&Client::initialized, &Client::initialized,
&managerInstance->m_currentDocumentLocatorFilter, &managerInstance->m_currentDocumentLocatorFilter,
&DocumentLocatorFilter::updateCurrentClient); &DocumentLocatorFilter::updateCurrentClient);
connect(client, &Client::initialized, managerInstance, [client]() {
managerInstance->clientInitialized(client);
});
} }
Client *LanguageClientManager::startClient(BaseSettings *setting, ProjectExplorer::Project *project) 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(); return document == nullptr ? nullptr : managerInstance->m_clientForDocument[document].data();
} }
bool LanguageClientManager::reOpenDocumentWithClient(TextEditor::TextDocument *document, Client *client) Client *LanguageClientManager::clientForFilePath(const Utils::FilePath &filePath)
{
return clientForDocument(TextEditor::TextDocument::textDocumentForFileName(filePath));
}
Client *LanguageClientManager::clientForUri(const DocumentUri &uri)
{
return clientForFilePath(uri.toFilePath());
}
void LanguageClientManager::reOpenDocumentWithClient(TextEditor::TextDocument *document, Client *client)
{ {
Utils::ExecuteOnDestruction outlineUpdater(&TextEditor::IOutlineWidgetFactory::updateOutline); Utils::ExecuteOnDestruction outlineUpdater(&TextEditor::IOutlineWidgetFactory::updateOutline);
if (Client *currentClient = clientForDocument(document)) { if (Client *currentClient = clientForDocument(document))
currentClient->closeDocument(document); currentClient->deactivateDocument(document);
currentClient->hideDiagnostics(document); managerInstance->m_clientForDocument[document] = client;
} client->activateDocument(document);
managerInstance->m_clientForDocument.remove(document);
if (!managerInstance->openDocumentWithClient(document, client))
return false;
client->showDiagnostics(document);
return true;
} }
QVector<Client *> LanguageClientManager::reachableClients() QVector<Client *> LanguageClientManager::reachableClients()
@@ -332,19 +333,6 @@ void LanguageClientManager::sendToAllReachableServers(const IContent &content)
sendToInterfaces(content, reachableClients()); 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<TextEditor::BaseTextEditor *>(editor))
textEditor->editorWidget()->addHoverHandler(client->hoverHandler());
}
}
}
}
void LanguageClientManager::clientFinished(Client *client) void LanguageClientManager::clientFinished(Client *client)
{ {
constexpr int restartTimeoutS = 5; constexpr int restartTimeoutS = 5;
@@ -372,25 +360,21 @@ void LanguageClientManager::editorOpened(Core::IEditor *editor)
if (auto *textEditor = qobject_cast<BaseTextEditor *>(editor)) { if (auto *textEditor = qobject_cast<BaseTextEditor *>(editor)) {
if (TextEditorWidget *widget = textEditor->editorWidget()) { if (TextEditorWidget *widget = textEditor->editorWidget()) {
connect(widget, &TextEditorWidget::requestLinkAt, this, connect(widget, &TextEditorWidget::requestLinkAt, this,
[this, filePath = editor->document()->filePath()] [this, document = textEditor->textDocument()]
(const QTextCursor &cursor, Utils::ProcessLinkCallback &callback) { (const QTextCursor &cursor, Utils::ProcessLinkCallback &callback) {
findLinkAt(filePath, cursor, callback); findLinkAt(document, cursor, callback);
}); });
connect(widget, &TextEditorWidget::requestUsages, this, connect(widget, &TextEditorWidget::requestUsages, this,
[this, filePath = editor->document()->filePath()] [this, document = textEditor->textDocument()](const QTextCursor &cursor) {
(const QTextCursor &cursor){ findUsages(document, cursor);
findUsages(filePath, cursor);
}); });
connect(widget, &TextEditorWidget::cursorPositionChanged, this, [this, widget]() { connect(widget, &TextEditorWidget::cursorPositionChanged, this, [this, widget]() {
// TODO This would better be a compressing timer // TODO This would better be a compressing timer
QTimer::singleShot(50, this, QTimer::singleShot(50, this, [widget = QPointer<TextEditorWidget>(widget)]() {
[this, widget = QPointer<TextEditorWidget>(widget)]() { if (!widget)
if (widget) { return;
for (Client *client : this->reachableClients()) { if (Client *client = clientForDocument(widget->textDocument()))
if (client->isSupportedDocument(widget->textDocument()))
client->cursorPositionChanged(widget); client->cursorPositionChanged(widget);
}
}
}); });
}); });
updateEditorToolBar(editor); updateEditorToolBar(editor);
@@ -409,7 +393,6 @@ void LanguageClientManager::documentOpened(Core::IDocument *document)
return; return;
// check whether we have to start servers for this document // check whether we have to start servers for this document
bool opened = false;
for (BaseSettings *setting : LanguageClientSettings::currentPageSettings()) { for (BaseSettings *setting : LanguageClientSettings::currentPageSettings()) {
QVector<Client *> clients = clientForSetting(setting); QVector<Client *> clients = clientForSetting(setting);
if (setting->isValid() && setting->m_enabled 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()) { } else if (setting->m_startBehavior == BaseSettings::RequiresFile && clients.isEmpty()) {
clients << startClient(setting); clients << startClient(setting);
} }
if (opened || clients.isEmpty())
continue;
for (auto client : clients) { for (auto client : clients) {
if (openDocumentWithClient(textDocument, client)) { openDocumentWithClient(textDocument, client);
opened = true; if (m_clientForDocument.value(textDocument).isNull())
continue; m_clientForDocument[textDocument] = client;
}
} }
} }
} }
} }
bool LanguageClientManager::openDocumentWithClient(TextEditor::TextDocument *document, void LanguageClientManager::openDocumentWithClient(TextEditor::TextDocument *document,
Client *client) Client *client)
{ {
if (!client || client->state() == Client::Error) if (client && client->state() != Client::Error)
return false; client->openDocument(document);
m_clientForDocument[document] = client;
if (client->state() == Client::Initialized)
return client->openDocument(document);
return true;
} }
void LanguageClientManager::documentClosed(Core::IDocument *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, const QTextCursor &cursor,
Utils::ProcessLinkCallback callback) Utils::ProcessLinkCallback callback)
{ {
const DocumentUri uri = DocumentUri::fromFilePath(filePath); const DocumentUri uri = DocumentUri::fromFilePath(document->filePath());
const TextDocumentIdentifier document(uri); const TextDocumentIdentifier documentId(uri);
const Position pos(cursor); const Position pos(cursor);
TextDocumentPositionParams params(document, pos); TextDocumentPositionParams params(documentId, pos);
GotoDefinitionRequest request(params); GotoDefinitionRequest request(params);
request.setResponseCallback([callback](const GotoDefinitionRequest::Response &response){ request.setResponseCallback([callback](const GotoDefinitionRequest::Response &response){
if (Utils::optional<GotoResult> _result = response.result()) { if (Utils::optional<GotoResult> _result = response.result()) {
@@ -547,14 +522,14 @@ QList<Core::SearchResultItem> generateSearchResultItems(const LanguageClientArra
return result; 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 DocumentUri uri = DocumentUri::fromFilePath(document->filePath());
const TextDocumentIdentifier document(uri); const TextDocumentIdentifier documentId(uri);
const Position pos(cursor); const Position pos(cursor);
QTextCursor termCursor(cursor); QTextCursor termCursor(cursor);
termCursor.select(QTextCursor::WordUnderCursor); termCursor.select(QTextCursor::WordUnderCursor);
ReferenceParams params(TextDocumentPositionParams(document, pos)); ReferenceParams params(TextDocumentPositionParams(documentId, pos));
params.setContext(ReferenceParams::ReferenceContext(true)); params.setContext(ReferenceParams::ReferenceContext(true));
FindReferencesRequest request(params); FindReferencesRequest request(params);
auto callback = [this, wordUnderCursor = termCursor.selectedText()] auto callback = [this, wordUnderCursor = termCursor.selectedText()]

View File

@@ -77,7 +77,9 @@ public:
static QVector<Client *> clientForSetting(const BaseSettings *setting); static QVector<Client *> clientForSetting(const BaseSettings *setting);
static const BaseSettings *settingForClient(Client *setting); static const BaseSettings *settingForClient(Client *setting);
static Client *clientForDocument(TextEditor::TextDocument *document); 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: signals:
void shutdownFinished(); void shutdownFinished();
@@ -87,13 +89,13 @@ private:
void editorOpened(Core::IEditor *editor); void editorOpened(Core::IEditor *editor);
void documentOpened(Core::IDocument *document); 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 documentClosed(Core::IDocument *document);
void documentContentsSaved(Core::IDocument *document); void documentContentsSaved(Core::IDocument *document);
void documentWillSave(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); 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 projectAdded(ProjectExplorer::Project *project);
void projectRemoved(ProjectExplorer::Project *project); void projectRemoved(ProjectExplorer::Project *project);
@@ -101,7 +103,6 @@ private:
QVector<Client *> reachableClients(); QVector<Client *> reachableClients();
void sendToAllReachableServers(const LanguageServerProtocol::IContent &content); void sendToAllReachableServers(const LanguageServerProtocol::IContent &content);
void clientInitialized(Client *client);
void clientFinished(Client *client); void clientFinished(Client *client);
bool m_shuttingDown = false; bool m_shuttingDown = false;