LanguageClient: manually track document version

Using the document revision causes issues for some servers.

Task-number: QTCREATORBUG-25766
Change-Id: Ic858e19c6fe39e57c9d3124913887aafee0a3cd0
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
David Schulz
2021-06-14 11:47:04 +02:00
parent 200723cf13
commit 439bb9c4ae
8 changed files with 35 additions and 23 deletions

View File

@@ -222,7 +222,7 @@ void JLSClient::executeCommand(const LanguageServerProtocol::Command &command)
continue; continue;
LanguageServerProtocol::WorkspaceEdit edit(argument.toObject()); LanguageServerProtocol::WorkspaceEdit edit(argument.toObject());
if (edit.isValid()) if (edit.isValid())
LanguageClient::applyWorkspaceEdit(edit); LanguageClient::applyWorkspaceEdit(this, edit);
} }
} else { } else {
Client::executeCommand(command); Client::executeCommand(command);

View File

@@ -385,7 +385,9 @@ void Client::openDocument(TextEditor::TextDocument *document)
item.setLanguageId(TextDocumentItem::mimeTypeToLanguageId(document->mimeType())); item.setLanguageId(TextDocumentItem::mimeTypeToLanguageId(document->mimeType()));
item.setUri(DocumentUri::fromFilePath(filePath)); item.setUri(DocumentUri::fromFilePath(filePath));
item.setText(document->plainText()); item.setText(document->plainText());
item.setVersion(document->document()->revision()); if (!m_documentVersions.contains(filePath))
m_documentVersions[filePath] = 0;
item.setVersion(m_documentVersions[filePath]);
sendContent(DidOpenTextDocumentNotification(DidOpenTextDocumentParams(item))); sendContent(DidOpenTextDocumentNotification(DidOpenTextDocumentParams(item)));
const Client *currentClient = LanguageClientManager::clientForDocument(document); const Client *currentClient = LanguageClientManager::clientForDocument(document);
@@ -547,8 +549,9 @@ void Client::requestDocumentHighlights(TextEditor::TextEditorWidget *widget)
void Client::activateDocument(TextEditor::TextDocument *document) void Client::activateDocument(TextEditor::TextDocument *document)
{ {
auto uri = DocumentUri::fromFilePath(document->filePath()); const FilePath &filePath = document->filePath();
m_diagnosticManager.showDiagnostics(uri); auto uri = DocumentUri::fromFilePath(filePath);
m_diagnosticManager.showDiagnostics(uri, m_documentVersions.value(filePath));
SemanticHighligtingSupport::applyHighlight(document, m_highlights.value(uri), capabilities()); SemanticHighligtingSupport::applyHighlight(document, m_highlights.value(uri), capabilities());
m_tokentSupport.updateSemanticTokens(document); m_tokentSupport.updateSemanticTokens(document);
// only replace the assist provider if the language server support it // only replace the assist provider if the language server support it
@@ -1020,6 +1023,7 @@ bool Client::reset()
qDeleteAll(m_documentHighlightsTimer); qDeleteAll(m_documentHighlightsTimer);
m_documentHighlightsTimer.clear(); m_documentHighlightsTimer.clear();
m_progressManager.reset(); m_progressManager.reset();
m_documentVersions.clear();
return true; return true;
} }
@@ -1143,10 +1147,11 @@ void Client::sendPostponedDocumentUpdates()
const QList<TextEditor::TextDocument *> documents = m_documentsToUpdate.keys(); const QList<TextEditor::TextDocument *> documents = m_documentsToUpdate.keys();
for (auto document : documents) { for (auto document : documents) {
const auto uri = DocumentUri::fromFilePath(document->filePath()); const FilePath &filePath = document->filePath();
const auto uri = DocumentUri::fromFilePath(filePath);
m_highlights[uri].clear(); m_highlights[uri].clear();
VersionedTextDocumentIdentifier docId(uri); VersionedTextDocumentIdentifier docId(uri);
docId.setVersion(document->document()->revision()); docId.setVersion(++m_documentVersions[filePath]);
DidChangeTextDocumentParams params; DidChangeTextDocumentParams params;
params.setTextDocument(docId); params.setTextDocument(docId);
params.setContentChanges(m_documentsToUpdate.take(document)); params.setContentChanges(m_documentsToUpdate.take(document));
@@ -1233,7 +1238,7 @@ void Client::handleMethod(const QString &method, const MessageId &id, const ICon
} else if (method == ApplyWorkspaceEditRequest::methodName) { } else if (method == ApplyWorkspaceEditRequest::methodName) {
auto params = dynamic_cast<const ApplyWorkspaceEditRequest *>(content)->params().value_or(ApplyWorkspaceEditParams()); auto params = dynamic_cast<const ApplyWorkspaceEditRequest *>(content)->params().value_or(ApplyWorkspaceEditParams());
if (params.isValid()) if (params.isValid())
applyWorkspaceEdit(params.edit()); applyWorkspaceEdit(this, params.edit());
else else
logError(params); logError(params);
} else if (method == WorkSpaceFolderRequest::methodName) { } else if (method == WorkSpaceFolderRequest::methodName) {
@@ -1280,7 +1285,7 @@ void Client::handleDiagnostics(const PublishDiagnosticsParams &params)
const QList<Diagnostic> &diagnostics = params.diagnostics(); const QList<Diagnostic> &diagnostics = params.diagnostics();
m_diagnosticManager.setDiagnostics(uri, diagnostics, params.version()); m_diagnosticManager.setDiagnostics(uri, diagnostics, params.version());
if (LanguageClientManager::clientForUri(uri) == this) { if (LanguageClientManager::clientForUri(uri) == this) {
m_diagnosticManager.showDiagnostics(uri); m_diagnosticManager.showDiagnostics(uri, m_documentVersions.value(uri.toFilePath()));
requestCodeActions(uri, diagnostics); requestCodeActions(uri, diagnostics);
} }
} }
@@ -1303,7 +1308,7 @@ void Client::handleSemanticHighlight(const SemanticHighlightingParams &params)
uri.toFilePath()); uri.toFilePath());
if (!doc || LanguageClientManager::clientForDocument(doc) != this if (!doc || LanguageClientManager::clientForDocument(doc) != this
|| (!version.isNull() && doc->document()->revision() != version.value())) { || (!version.isNull() && m_documentVersions.value(uri.toFilePath()) != version.value())) {
return; return;
} }
@@ -1333,6 +1338,11 @@ bool Client::documentUpdatePostponed(const Utils::FilePath &fileName) const
}); });
} }
int Client::documentVersion(const Utils::FilePath &filePath) const
{
return m_documentVersions.value(filePath);
}
void Client::initializeCallback(const InitializeRequest::Response &initResponse) void Client::initializeCallback(const InitializeRequest::Response &initResponse)
{ {
QTC_ASSERT(m_state == InitializeRequested, return); QTC_ASSERT(m_state == InitializeRequested, return);

View File

@@ -145,6 +145,7 @@ public:
int charsAdded); int charsAdded);
void cursorPositionChanged(TextEditor::TextEditorWidget *widget); void cursorPositionChanged(TextEditor::TextEditorWidget *widget);
bool documentUpdatePostponed(const Utils::FilePath &fileName) const; bool documentUpdatePostponed(const Utils::FilePath &fileName) const;
int documentVersion(const Utils::FilePath &filePath) const;
// workspace control // workspace control
virtual void setCurrentProject(ProjectExplorer::Project *project); virtual void setCurrentProject(ProjectExplorer::Project *project);
@@ -231,6 +232,7 @@ private:
LanguageFilter m_languagFilter; LanguageFilter m_languagFilter;
QJsonObject m_initializationOptions; QJsonObject m_initializationOptions;
QMap<TextEditor::TextDocument *, QString> m_openedDocument; QMap<TextEditor::TextDocument *, QString> m_openedDocument;
QMap<Utils::FilePath, int> m_documentVersions;
QMap<TextEditor::TextDocument *, QMap<TextEditor::TextDocument *,
QList<LanguageServerProtocol::DidChangeTextDocumentParams::TextDocumentContentChangeEvent>> QList<LanguageServerProtocol::DidChangeTextDocumentParams::TextDocumentContentChangeEvent>>
m_documentsToUpdate; m_documentsToUpdate;

View File

@@ -117,14 +117,13 @@ static QTextEdit::ExtraSelection toDiagnosticsSelections(const Diagnostic &diagn
return QTextEdit::ExtraSelection{cursor, fontSettings.toTextCharFormat(style)}; return QTextEdit::ExtraSelection{cursor, fontSettings.toTextCharFormat(style)};
} }
void DiagnosticManager::showDiagnostics(const DocumentUri &uri) void DiagnosticManager::showDiagnostics(const DocumentUri &uri, int version)
{ {
const FilePath &filePath = uri.toFilePath(); const FilePath &filePath = uri.toFilePath();
if (TextDocument *doc = TextDocument::textDocumentForFilePath(filePath)) { if (TextDocument *doc = TextDocument::textDocumentForFilePath(filePath)) {
QList<QTextEdit::ExtraSelection> extraSelections; QList<QTextEdit::ExtraSelection> extraSelections;
const VersionedDiagnostics &versionedDiagnostics = m_diagnostics.value(uri); const VersionedDiagnostics &versionedDiagnostics = m_diagnostics.value(uri);
const int docRevision = doc->document()->revision(); if (versionedDiagnostics.version.value_or(version) == version) {
if (versionedDiagnostics.version.value_or(docRevision) == docRevision) {
const auto icon = QIcon::fromTheme("edit-copy", Utils::Icons::COPY.icon()); const auto icon = QIcon::fromTheme("edit-copy", Utils::Icons::COPY.icon());
const QString tooltip = tr("Copy to Clipboard"); const QString tooltip = tr("Copy to Clipboard");
for (const Diagnostic &diagnostic : versionedDiagnostics.diagnostics) { for (const Diagnostic &diagnostic : versionedDiagnostics.diagnostics) {

View File

@@ -47,7 +47,7 @@ public:
const Utils::optional<int> &version); const Utils::optional<int> &version);
void removeDiagnostics(const LanguageServerProtocol::DocumentUri &uri); void removeDiagnostics(const LanguageServerProtocol::DocumentUri &uri);
void showDiagnostics(const LanguageServerProtocol::DocumentUri &uri); void showDiagnostics(const LanguageServerProtocol::DocumentUri &uri, int version);
void hideDiagnostics(TextEditor::TextDocument *doc); void hideDiagnostics(TextEditor::TextDocument *doc);
void clearDiagnostics(); void clearDiagnostics();

View File

@@ -50,7 +50,7 @@ public:
void perform() override void perform() override
{ {
if (Utils::optional<WorkspaceEdit> edit = m_action.edit()) { if (Utils::optional<WorkspaceEdit> edit = m_action.edit()) {
applyWorkspaceEdit(*edit); applyWorkspaceEdit(m_client, *edit);
} else if (Utils::optional<Command> command = m_action.command()) { } else if (Utils::optional<Command> command = m_action.command()) {
if (m_client) if (m_client)
m_client->executeCommand(*command); m_client->executeCommand(*command);

View File

@@ -78,15 +78,16 @@ ChangeSet editsToChangeSet(const QList<TextEdit> &edits, const QTextDocument *do
return changeSet; return changeSet;
} }
bool applyTextDocumentEdit(const TextDocumentEdit &edit) bool applyTextDocumentEdit(const Client *client, const TextDocumentEdit &edit)
{ {
const QList<TextEdit> &edits = edit.edits(); const QList<TextEdit> &edits = edit.edits();
if (edits.isEmpty()) if (edits.isEmpty())
return true; return true;
const DocumentUri &uri = edit.textDocument().uri(); const DocumentUri &uri = edit.textDocument().uri();
if (TextDocument* doc = TextDocument::textDocumentForFilePath(uri.toFilePath())) { const FilePath &filePath = uri.toFilePath();
if (TextDocument* doc = TextDocument::textDocumentForFilePath(filePath)) {
LanguageClientValue<int> version = edit.textDocument().version(); LanguageClientValue<int> version = edit.textDocument().version();
if (!version.isNull() && version.value(0) < doc->document()->revision()) if (!version.isNull() && version.value(0) < client->documentVersion(filePath))
return false; return false;
} }
return applyTextEdits(uri, edits); return applyTextEdits(uri, edits);
@@ -120,14 +121,14 @@ void applyTextEdit(TextDocumentManipulatorInterface &manipulator,
} }
} }
bool applyWorkspaceEdit(const WorkspaceEdit &edit) bool applyWorkspaceEdit(const Client *client, const WorkspaceEdit &edit)
{ {
bool result = true; bool result = true;
const QList<TextDocumentEdit> &documentChanges const QList<TextDocumentEdit> &documentChanges
= edit.documentChanges().value_or(QList<TextDocumentEdit>()); = edit.documentChanges().value_or(QList<TextDocumentEdit>());
if (!documentChanges.isEmpty()) { if (!documentChanges.isEmpty()) {
for (const TextDocumentEdit &documentChange : documentChanges) for (const TextDocumentEdit &documentChange : documentChanges)
result |= applyTextDocumentEdit(documentChange); result |= applyTextDocumentEdit(client, documentChange);
} else { } else {
const WorkspaceEdit::Changes &changes = edit.changes().value_or(WorkspaceEdit::Changes()); const WorkspaceEdit::Changes &changes = edit.changes().value_or(WorkspaceEdit::Changes());
for (auto it = changes.cbegin(); it != changes.cend(); ++it) for (auto it = changes.cbegin(); it != changes.cend(); ++it)
@@ -164,8 +165,8 @@ void updateCodeActionRefactoringMarker(Client *client,
marker.tooltip = action.title(); marker.tooltip = action.title();
if (action.edit().has_value()) { if (action.edit().has_value()) {
WorkspaceEdit edit = action.edit().value(); WorkspaceEdit edit = action.edit().value();
marker.callback = [edit](const TextEditorWidget *) { marker.callback = [client, edit](const TextEditorWidget *) {
applyWorkspaceEdit(edit); applyWorkspaceEdit(client, edit);
}; };
if (diagnostics.isEmpty()) { if (diagnostics.isEmpty()) {
QList<TextEdit> edits; QList<TextEdit> edits;

View File

@@ -46,9 +46,9 @@ class Client;
Utils::ChangeSet editsToChangeSet(const QList<LanguageServerProtocol::TextEdit> &edits, Utils::ChangeSet editsToChangeSet(const QList<LanguageServerProtocol::TextEdit> &edits,
const QTextDocument *doc); const QTextDocument *doc);
bool LANGUAGECLIENT_EXPORT applyWorkspaceEdit(const LanguageServerProtocol::WorkspaceEdit &edit); bool LANGUAGECLIENT_EXPORT applyWorkspaceEdit(const Client *client, const LanguageServerProtocol::WorkspaceEdit &edit);
bool LANGUAGECLIENT_EXPORT bool LANGUAGECLIENT_EXPORT
applyTextDocumentEdit(const LanguageServerProtocol::TextDocumentEdit &edit); applyTextDocumentEdit(const Client *client, const LanguageServerProtocol::TextDocumentEdit &edit);
bool LANGUAGECLIENT_EXPORT applyTextEdits(const LanguageServerProtocol::DocumentUri &uri, bool LANGUAGECLIENT_EXPORT applyTextEdits(const LanguageServerProtocol::DocumentUri &uri,
const QList<LanguageServerProtocol::TextEdit> &edits); const QList<LanguageServerProtocol::TextEdit> &edits);
void LANGUAGECLIENT_EXPORT applyTextEdit(TextEditor::TextDocumentManipulatorInterface &manipulator, void LANGUAGECLIENT_EXPORT applyTextEdit(TextEditor::TextDocumentManipulatorInterface &manipulator,