LSP: Compress document change notifications

Change-Id: Iaf6cb99784f4e1ed1291ace1f4cc18cf6af88672
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
David Schulz
2020-06-17 14:15:13 +02:00
parent 92a4c0d38a
commit e323529b3c
3 changed files with 80 additions and 17 deletions

View File

@@ -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<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]);
@@ -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<Registration> &registrations)
@@ -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<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])
@@ -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);

View File

@@ -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<void(const QByteArray &, QTextCodec *, QString &,
LanguageServerProtocol::ResponseHandlers,
@@ -219,6 +223,10 @@ private:
LanguageFilter m_languagFilter;
QJsonObject m_initializationOptions;
QMap<TextEditor::TextDocument *, QString> m_openedDocument;
QMap<TextEditor::TextDocument *,
QList<LanguageServerProtocol::DidChangeTextDocumentParams::TextDocumentContentChangeEvent>>
m_documentsToUpdate;
QTimer m_documentUpdateTimer;
Core::Id m_id;
LanguageServerProtocol::ServerCapabilities m_serverCapabilities;
DynamicCapabilities m_dynamicCapabilities;

View File

@@ -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<QTextDocument> m_document;
QPointer<Client> m_client;
Utils::optional<MessageId> 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);
}
}