LanguageClient: Improve formatting

Use Utils::ChangeSet and TextEditor::RefactoringChanges to apply the
text edits that are reported from language server to format the current
file.

Change-Id: Id2f490b6e2791f676ebc751219bfbbf9e178f120
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
David Schulz
2021-01-29 13:53:20 +01:00
parent 2d92ae391a
commit 15b0a902e9
6 changed files with 32 additions and 22 deletions

View File

@@ -67,16 +67,16 @@ static const FormattingOptions formattingOptions(const TextEditor::TabSettings &
return options; return options;
} }
QFutureWatcher<Utils::Text::Replacements> *LanguageClientFormatter::format( QFutureWatcher<ChangeSet> *LanguageClientFormatter::format(
const QTextCursor &cursor, const TextEditor::TabSettings &tabSettings) const QTextCursor &cursor, const TextEditor::TabSettings &tabSettings)
{ {
cancelCurrentRequest(); cancelCurrentRequest();
m_progress = QFutureInterface<Utils::Text::Replacements>(); m_progress = QFutureInterface<ChangeSet>();
const FilePath &filePath = m_document->filePath(); const FilePath &filePath = m_document->filePath();
const DynamicCapabilities dynamicCapabilities = m_client->dynamicCapabilities(); const DynamicCapabilities dynamicCapabilities = m_client->dynamicCapabilities();
const QString method(DocumentRangeFormattingRequest::methodName); const QString method(DocumentRangeFormattingRequest::methodName);
if (Utils::optional<bool> registered = dynamicCapabilities.isRegistered(method)) { if (optional<bool> registered = dynamicCapabilities.isRegistered(method)) {
if (!registered.value()) if (!registered.value())
return nullptr; return nullptr;
const TextDocumentRegistrationOptions option(dynamicCapabilities.option(method).toObject()); const TextDocumentRegistrationOptions option(dynamicCapabilities.option(method).toObject());
@@ -107,7 +107,7 @@ QFutureWatcher<Utils::Text::Replacements> *LanguageClientFormatter::format(
// ignore first contents changed, because this function is called inside a begin/endEdit block // ignore first contents changed, because this function is called inside a begin/endEdit block
m_ignoreCancel = true; m_ignoreCancel = true;
m_progress.reportStarted(); m_progress.reportStarted();
auto watcher = new QFutureWatcher<Text::Replacements>(); auto watcher = new QFutureWatcher<ChangeSet>();
watcher->setFuture(m_progress.future()); watcher->setFuture(m_progress.future());
QObject::connect(watcher, &QFutureWatcher<Text::Replacements>::canceled, [this]() { QObject::connect(watcher, &QFutureWatcher<Text::Replacements>::canceled, [this]() {
cancelCurrentRequest(); cancelCurrentRequest();
@@ -131,16 +131,12 @@ void LanguageClientFormatter::handleResponse(const DocumentRangeFormattingReques
m_currentRequest = nullopt; m_currentRequest = nullopt;
if (const optional<DocumentRangeFormattingRequest::Response::Error> &error = response.error()) if (const optional<DocumentRangeFormattingRequest::Response::Error> &error = response.error())
m_client->log(*error); m_client->log(*error);
Text::Replacements replacements; ChangeSet changeSet;
if (optional<LanguageClientArray<TextEdit>> result = response.result()) { if (optional<LanguageClientArray<TextEdit>> result = response.result()) {
if (!result->isNull()) { if (!result->isNull())
const QList<TextEdit> results = result->toList(); changeSet = editsToChangeSet(result->toList(), m_document->document());
replacements.reserve(results.size());
for (const TextEdit &edit : results)
replacements.emplace_back(edit.toReplacement(m_document->document()));
} }
} m_progress.reportResult(changeSet);
m_progress.reportResult(replacements);
m_progress.reportFinished(); m_progress.reportFinished();
} }

View File

@@ -41,7 +41,7 @@ public:
LanguageClientFormatter(TextEditor::TextDocument *document, Client *client); LanguageClientFormatter(TextEditor::TextDocument *document, Client *client);
~LanguageClientFormatter() override; ~LanguageClientFormatter() override;
QFutureWatcher<Utils::Text::Replacements> *format( QFutureWatcher<Utils::ChangeSet> *format(
const QTextCursor &cursor, const TextEditor::TabSettings &tabSettings) override; const QTextCursor &cursor, const TextEditor::TabSettings &tabSettings) override;
private: private:
@@ -53,7 +53,7 @@ private:
QMetaObject::Connection m_cancelConnection; QMetaObject::Connection m_cancelConnection;
TextEditor::TextDocument *m_document; // not owned TextEditor::TextDocument *m_document; // not owned
bool m_ignoreCancel = false; bool m_ignoreCancel = false;
QFutureInterface<Utils::Text::Replacements> m_progress; QFutureInterface<Utils::ChangeSet> m_progress;
Utils::optional<LanguageServerProtocol::MessageId> m_currentRequest; Utils::optional<LanguageServerProtocol::MessageId> m_currentRequest;
}; };

View File

@@ -29,6 +29,7 @@
#include <languageserverprotocol/languagefeatures.h> #include <languageserverprotocol/languagefeatures.h>
#include <texteditor/refactoroverlay.h> #include <texteditor/refactoroverlay.h>
#include <utils/changeset.h>
namespace Core { class IEditor; } namespace Core { class IEditor; }
@@ -41,6 +42,8 @@ namespace LanguageClient {
class Client; class Client;
Utils::ChangeSet editsToChangeSet(const QList<LanguageServerProtocol::TextEdit> &edits,
const QTextDocument *doc);
bool applyWorkspaceEdit(const LanguageServerProtocol::WorkspaceEdit &edit); bool applyWorkspaceEdit(const LanguageServerProtocol::WorkspaceEdit &edit);
bool applyTextDocumentEdit(const LanguageServerProtocol::TextDocumentEdit &edit); bool applyTextDocumentEdit(const LanguageServerProtocol::TextDocumentEdit &edit);
bool applyTextEdits(const LanguageServerProtocol::DocumentUri &uri, bool applyTextEdits(const LanguageServerProtocol::DocumentUri &uri,

View File

@@ -25,9 +25,9 @@
#pragma once #pragma once
#include "texteditor_global.h" #include "refactoringchanges.h"
#include <utils/textutils.h> #include <utils/changeset.h>
#include <QFutureWatcher> #include <QFutureWatcher>
#include <QString> #include <QString>
@@ -47,7 +47,7 @@ public:
Formatter() = default; Formatter() = default;
virtual ~Formatter() = default; virtual ~Formatter() = default;
virtual QFutureWatcher<Utils::Text::Replacements> *format( virtual QFutureWatcher<Utils::ChangeSet> *format(
const QTextCursor & /*cursor*/, const TextEditor::TabSettings & /*tabSettings*/) const QTextCursor & /*cursor*/, const TextEditor::TabSettings & /*tabSettings*/)
{ {
return nullptr; return nullptr;
@@ -55,14 +55,14 @@ public:
virtual bool isElectricCharacter(const QChar & /*ch*/) const { return false; } virtual bool isElectricCharacter(const QChar & /*ch*/) const { return false; }
virtual bool supportsAutoFormat() const { return false; } virtual bool supportsAutoFormat() const { return false; }
virtual QFutureWatcher<Utils::Text::Replacements> *autoFormat( virtual QFutureWatcher<Utils::ChangeSet> *autoFormat(
const QTextCursor & /*cursor*/, const TextEditor::TabSettings & /*tabSettings*/) const QTextCursor & /*cursor*/, const TextEditor::TabSettings & /*tabSettings*/)
{ {
return nullptr; return nullptr;
} }
virtual bool supportsFormatOnSave() const { return false; } virtual bool supportsFormatOnSave() const { return false; }
virtual QFutureWatcher<Utils::Text::Replacements> *formatOnSave( virtual QFutureWatcher<Utils::ChangeSet> *formatOnSave(
const QTextCursor & /*cursor*/, const TextEditor::TabSettings & /*tabSettings*/) const QTextCursor & /*cursor*/, const TextEditor::TabSettings & /*tabSettings*/)
{ {
return nullptr; return nullptr;

View File

@@ -518,15 +518,25 @@ void TextDocument::autoFormat(const QTextCursor &cursor)
using namespace Utils::Text; using namespace Utils::Text;
if (!d->m_formatter) if (!d->m_formatter)
return; return;
if (QFutureWatcher<Replacements> *watcher = d->m_formatter->format(cursor, tabSettings())) { if (QFutureWatcher<ChangeSet> *watcher = d->m_formatter->format(cursor, tabSettings())) {
connect(watcher, &QFutureWatcher<Replacements>::finished, this, [this, watcher]() { connect(watcher, &QFutureWatcher<ChangeSet>::finished, this, [this, watcher]() {
if (!watcher->isCanceled()) if (!watcher->isCanceled())
Utils::Text::applyReplacements(document(), watcher->result()); applyChangeSet(watcher->result());
delete watcher; delete watcher;
}); });
} }
} }
bool TextDocument::applyChangeSet(const ChangeSet &changeSet)
{
if (changeSet.isEmpty())
return true;
RefactoringChanges changes;
const RefactoringFilePtr file = changes.file(filePath().toString());
file->setChangeSet(changeSet);
return file->apply();
}
const ExtraEncodingSettings &TextDocument::extraEncodingSettings() const const ExtraEncodingSettings &TextDocument::extraEncodingSettings() const
{ {
return d->m_extraEncodingSettings; return d->m_extraEncodingSettings;

View File

@@ -101,6 +101,7 @@ public:
void setFormatter(Formatter *indenter); // transfers ownership void setFormatter(Formatter *indenter); // transfers ownership
void autoFormat(const QTextCursor &cursor); void autoFormat(const QTextCursor &cursor);
bool applyChangeSet(const Utils::ChangeSet &changeSet);
TextMarks marks() const; TextMarks marks() const;
bool addMark(TextMark *mark); bool addMark(TextMark *mark);