forked from qt-creator/qt-creator
LSP: add resource operations to the protocol implementation
This allows the language server to request file creation, renaming, or deletion. Fixes: QTCREATORBUG-29542 Change-Id: I31ab3c0b36f87d3b797b54ff4261cab85a322e2c Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
@@ -77,4 +77,46 @@ bool SemanticTokensClientCapabilities::isValid() const
|
||||
&& contains(formatsKey);
|
||||
}
|
||||
|
||||
const char resourceOperationCreate[] = "create";
|
||||
const char resourceOperationRename[] = "rename";
|
||||
const char resourceOperationDelete[] = "delete";
|
||||
|
||||
std::optional<QList<WorkspaceClientCapabilities::WorkspaceEditCapabilities::ResourceOperationKind>>
|
||||
WorkspaceClientCapabilities::WorkspaceEditCapabilities::resourceOperations() const
|
||||
{
|
||||
if (!contains(resourceOperationsKey))
|
||||
return std::nullopt;
|
||||
QList<ResourceOperationKind> result;
|
||||
for (const QJsonValue &value : this->value(resourceOperationsKey).toArray()) {
|
||||
const QString str = value.toString();
|
||||
if (str == resourceOperationCreate)
|
||||
result << ResourceOperationKind::Create;
|
||||
else if (str == resourceOperationRename)
|
||||
result << ResourceOperationKind::Rename;
|
||||
else if (str == resourceOperationDelete)
|
||||
result << ResourceOperationKind::Delete;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void WorkspaceClientCapabilities::WorkspaceEditCapabilities::setResourceOperations(
|
||||
const QList<ResourceOperationKind> &resourceOperations)
|
||||
{
|
||||
QJsonArray array;
|
||||
for (const auto &kind : resourceOperations) {
|
||||
switch (kind) {
|
||||
case ResourceOperationKind::Create:
|
||||
array << resourceOperationCreate;
|
||||
break;
|
||||
case ResourceOperationKind::Rename:
|
||||
array << resourceOperationRename;
|
||||
break;
|
||||
case ResourceOperationKind::Delete:
|
||||
array << resourceOperationDelete;
|
||||
break;
|
||||
}
|
||||
}
|
||||
insert(resourceOperationsKey, array);
|
||||
}
|
||||
|
||||
} // namespace LanguageServerProtocol
|
||||
|
||||
@@ -555,6 +555,14 @@ public:
|
||||
void setDocumentChanges(bool documentChanges)
|
||||
{ insert(documentChangesKey, documentChanges); }
|
||||
void clearDocumentChanges() { remove(documentChangesKey); }
|
||||
|
||||
enum class ResourceOperationKind { Create, Rename, Delete };
|
||||
|
||||
// The resource operations the client supports. Clients should at least support 'create',
|
||||
// 'rename' and 'delete' files and folders.
|
||||
std::optional<QList<ResourceOperationKind>> resourceOperations() const;
|
||||
void setResourceOperations(const QList<ResourceOperationKind> &resourceOperations);
|
||||
void clearResourceOperations() { remove(resourceOperationsKey); }
|
||||
};
|
||||
|
||||
// Capabilities specific to `WorkspaceEdit`s
|
||||
|
||||
@@ -98,6 +98,8 @@ constexpr char hierarchicalDocumentSymbolSupportKey[] = "hierarchicalDocumentSym
|
||||
constexpr char hoverKey[] = "hover";
|
||||
constexpr char hoverProviderKey[] = "hoverProvider";
|
||||
constexpr char idKey[] = "id";
|
||||
constexpr char ignoreIfExistsKey[] = "ignoreIfExists";
|
||||
constexpr char ignoreIfNotExistsKey[] = "ignoreIfNotExists";
|
||||
constexpr char implementationKey[] = "implementation";
|
||||
constexpr char implementationProviderKey[] = "implementationProvider";
|
||||
constexpr char includeDeclarationKey[] = "includeDeclaration";
|
||||
@@ -127,11 +129,14 @@ constexpr char multiLineTokenSupportKey[] = "multiLineTokenSupport";
|
||||
constexpr char nameKey[] = "name";
|
||||
constexpr char newNameKey[] = "newName";
|
||||
constexpr char newTextKey[] = "newText";
|
||||
constexpr char newUriKey[] = "newUri";
|
||||
constexpr char oldUriKey[] = "oldUri";
|
||||
constexpr char onTypeFormattingKey[] = "onTypeFormatting";
|
||||
constexpr char onlyKey[] = "only";
|
||||
constexpr char openCloseKey[] = "openClose";
|
||||
constexpr char optionsKey[] = "options";
|
||||
constexpr char overlappingTokenSupportKey[] = "overlappingTokenSupport";
|
||||
constexpr char overwriteKey[] = "overwrite";
|
||||
constexpr char parametersKey[] = "parameters";
|
||||
constexpr char paramsKey[] = "params";
|
||||
constexpr char patternKey[] = "pattern";
|
||||
@@ -147,6 +152,7 @@ constexpr char rangeFormattingKey[] = "rangeFormatting";
|
||||
constexpr char rangeKey[] = "range";
|
||||
constexpr char rangeLengthKey[] = "rangeLength";
|
||||
constexpr char reasonKey[] = "reason";
|
||||
constexpr char recursiveKey[] = "recursive";
|
||||
constexpr char redKey[] = "red";
|
||||
constexpr char referencesKey[] = "references";
|
||||
constexpr char referencesProviderKey[] = "referencesProvider";
|
||||
@@ -158,6 +164,7 @@ constexpr char renameKey[] = "rename";
|
||||
constexpr char renameProviderKey[] = "renameProvider";
|
||||
constexpr char requestsKey[] = "requests";
|
||||
constexpr char resolveProviderKey[] = "resolveProvider";
|
||||
constexpr char resourceOperationsKey[] = "resourceOperations";
|
||||
constexpr char resultIdKey[] = "resultId";
|
||||
constexpr char resultKey[] = "result";
|
||||
constexpr char retryKey[] = "retry";
|
||||
@@ -194,8 +201,8 @@ constexpr char textDocumentSyncKey[] = "textDocumentSync";
|
||||
constexpr char textEditKey[] = "textEdit";
|
||||
constexpr char textKey[] = "text";
|
||||
constexpr char titleKey[] = "title";
|
||||
constexpr char tokenKey[] = "token";
|
||||
constexpr char toKey[] = "to";
|
||||
constexpr char tokenKey[] = "token";
|
||||
constexpr char tokenModifiersKey[] = "tokenModifiers";
|
||||
constexpr char tokenTypesKey[] = "tokenTypes";
|
||||
constexpr char traceKey[] = "trace";
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "lsptypes.h"
|
||||
|
||||
#include "languageserverprotocoltr.h"
|
||||
#include "lsputils.h"
|
||||
|
||||
#include <utils/textutils.h>
|
||||
@@ -413,4 +415,76 @@ LanguageServerProtocol::MarkupKind::operator QJsonValue() const
|
||||
return {};
|
||||
}
|
||||
|
||||
DocumentChange::DocumentChange(const QJsonValue &value)
|
||||
{
|
||||
const QString kind = value["kind"].toString();
|
||||
if (kind == "create")
|
||||
emplace<CreateFile>(value);
|
||||
else if (kind == "rename")
|
||||
emplace<RenameFile>(value);
|
||||
else if (kind == "delete")
|
||||
emplace<DeleteFile>(value);
|
||||
else
|
||||
emplace<TextDocumentEdit>(value);
|
||||
}
|
||||
|
||||
using DocumentChangeBase = std::variant<TextDocumentEdit, CreateFile, RenameFile, DeleteFile>;
|
||||
|
||||
bool DocumentChange::isValid() const
|
||||
{
|
||||
return std::visit([](const auto &v) { return v.isValid(); }, DocumentChangeBase(*this));
|
||||
}
|
||||
|
||||
DocumentChange::operator const QJsonValue () const
|
||||
{
|
||||
return std::visit([](const auto &v) { return QJsonValue(v); }, DocumentChangeBase(*this));
|
||||
}
|
||||
|
||||
CreateFile::CreateFile()
|
||||
{
|
||||
insert(kindKey, "create");
|
||||
}
|
||||
|
||||
QString CreateFile::message(const DocumentUri::PathMapper &mapToHostPath) const
|
||||
{
|
||||
return Tr::tr("Create %1").arg(uri().toFilePath(mapToHostPath).toUserOutput());
|
||||
}
|
||||
|
||||
bool LanguageServerProtocol::CreateFile::isValid() const
|
||||
{
|
||||
return contains(uriKey) && value(kindKey) == "create";
|
||||
}
|
||||
|
||||
RenameFile::RenameFile()
|
||||
{
|
||||
insert(kindKey, "rename");
|
||||
}
|
||||
|
||||
QString RenameFile::message(const DocumentUri::PathMapper &mapToHostPath) const
|
||||
{
|
||||
return Tr::tr("Rename %1 to %2")
|
||||
.arg(oldUri().toFilePath(mapToHostPath).toUserOutput(),
|
||||
newUri().toFilePath(mapToHostPath).toUserOutput());
|
||||
}
|
||||
|
||||
bool RenameFile::isValid() const
|
||||
{
|
||||
return contains(oldUriKey) && contains(newUriKey) && value(kindKey) == "rename";
|
||||
}
|
||||
|
||||
DeleteFile::DeleteFile()
|
||||
{
|
||||
insert(kindKey, "delete");
|
||||
}
|
||||
|
||||
QString DeleteFile::message(const DocumentUri::PathMapper &mapToHostPath) const
|
||||
{
|
||||
return Tr::tr("Delete %1").arg(uri().toFilePath(mapToHostPath).toUserOutput());
|
||||
}
|
||||
|
||||
bool DeleteFile::isValid() const
|
||||
{
|
||||
return contains(uriKey) && value(kindKey) == "delete";
|
||||
}
|
||||
|
||||
} // namespace LanguageServerProtocol
|
||||
|
||||
@@ -285,6 +285,106 @@ public:
|
||||
bool isValid() const override { return contains(textDocumentKey) && contains(editsKey); }
|
||||
};
|
||||
|
||||
class CreateFileOptions : public JsonObject
|
||||
{
|
||||
public:
|
||||
using JsonObject::JsonObject;
|
||||
|
||||
std::optional<bool> overwrite() const { return optionalValue<bool>(overwriteKey); }
|
||||
void setOverwrite(bool overwrite) { insert(overwriteKey, overwrite); }
|
||||
void clearOverwrite() { remove(overwriteKey); }
|
||||
|
||||
std::optional<bool> ignoreIfExists() const { return optionalValue<bool>(ignoreIfExistsKey); }
|
||||
void setIgnoreIfExists(bool ignoreIfExists) { insert(ignoreIfExistsKey, ignoreIfExists); }
|
||||
void clearIgnoreIfExists() { remove(ignoreIfExistsKey); }
|
||||
};
|
||||
|
||||
class LANGUAGESERVERPROTOCOL_EXPORT CreateFile : public JsonObject
|
||||
{
|
||||
public:
|
||||
using JsonObject::JsonObject;
|
||||
CreateFile();
|
||||
|
||||
DocumentUri uri() const { return DocumentUri::fromProtocol(typedValue<QString>(uriKey)); }
|
||||
void setUri(const DocumentUri &uri) { insert(uriKey, uri); }
|
||||
|
||||
std::optional<CreateFileOptions> options() const
|
||||
{ return optionalValue<CreateFileOptions>(optionsKey); }
|
||||
void setOptions(const CreateFileOptions &options) { insert(optionsKey, options); }
|
||||
void clearOptions() { remove(optionsKey); }
|
||||
|
||||
QString message(const DocumentUri::PathMapper &mapToHostPath) const;
|
||||
|
||||
bool isValid() const override;
|
||||
};
|
||||
|
||||
class LANGUAGESERVERPROTOCOL_EXPORT RenameFile : public JsonObject
|
||||
{
|
||||
public:
|
||||
using JsonObject::JsonObject;
|
||||
RenameFile();
|
||||
|
||||
DocumentUri oldUri() const { return DocumentUri::fromProtocol(typedValue<QString>(oldUriKey)); }
|
||||
void setOldUri(const DocumentUri &oldUri) { insert(oldUriKey, oldUri); }
|
||||
|
||||
DocumentUri newUri() const { return DocumentUri::fromProtocol(typedValue<QString>(newUriKey)); }
|
||||
void setNewUri(const DocumentUri &newUri) { insert(newUriKey, newUri); }
|
||||
|
||||
std::optional<CreateFileOptions> options() const
|
||||
{ return optionalValue<CreateFileOptions>(optionsKey); }
|
||||
void setOptions(const CreateFileOptions &options) { insert(optionsKey, options); }
|
||||
void clearOptions() { remove(optionsKey); }
|
||||
|
||||
QString message(const DocumentUri::PathMapper &mapToHostPath) const;
|
||||
|
||||
bool isValid() const override;
|
||||
};
|
||||
|
||||
class DeleteFileOptions : public JsonObject
|
||||
{
|
||||
public:
|
||||
using JsonObject::JsonObject;
|
||||
|
||||
std::optional<bool> recursive() const { return optionalValue<bool>(recursiveKey); }
|
||||
void setRecursive(bool recursive) { insert(recursiveKey, recursive); }
|
||||
void clearRecursive() { remove(recursiveKey); }
|
||||
|
||||
std::optional<bool> ignoreIfNotExists() const { return optionalValue<bool>(ignoreIfNotExistsKey); }
|
||||
void setIgnoreIfNotExists(bool ignoreIfNotExists) { insert(ignoreIfNotExistsKey, ignoreIfNotExists); }
|
||||
void clearIgnoreIfNotExists() { remove(ignoreIfNotExistsKey); }
|
||||
};
|
||||
|
||||
class LANGUAGESERVERPROTOCOL_EXPORT DeleteFile : public JsonObject
|
||||
{
|
||||
public:
|
||||
using JsonObject::JsonObject;
|
||||
DeleteFile();
|
||||
|
||||
DocumentUri uri() const { return DocumentUri::fromProtocol(typedValue<QString>(uriKey)); }
|
||||
void setUri(const DocumentUri &uri) { insert(uriKey, uri); }
|
||||
|
||||
std::optional<DeleteFileOptions> options() const
|
||||
{ return optionalValue<DeleteFileOptions>(optionsKey); }
|
||||
void setOptions(const DeleteFileOptions &options) { insert(optionsKey, options); }
|
||||
void clearOptions() { remove(optionsKey); }
|
||||
|
||||
QString message(const DocumentUri::PathMapper &mapToHostPath) const;
|
||||
|
||||
bool isValid() const override;
|
||||
};
|
||||
|
||||
class LANGUAGESERVERPROTOCOL_EXPORT DocumentChange
|
||||
: public std::variant<TextDocumentEdit, CreateFile, RenameFile, DeleteFile>
|
||||
{
|
||||
public:
|
||||
using variant::variant;
|
||||
DocumentChange(const QJsonValue &value);
|
||||
|
||||
bool isValid() const;
|
||||
|
||||
operator const QJsonValue() const;
|
||||
};
|
||||
|
||||
class LANGUAGESERVERPROTOCOL_EXPORT WorkspaceEdit : public JsonObject
|
||||
{
|
||||
public:
|
||||
@@ -296,17 +396,23 @@ public:
|
||||
void setChanges(const Changes &changes);
|
||||
|
||||
/*
|
||||
* An array of `TextDocumentEdit`s to express changes to n different text documents
|
||||
* where each text document edit addresses a specific version of a text document.
|
||||
* Whether a client supports versioned document edits is expressed via
|
||||
* `WorkspaceClientCapabilities.workspaceEdit.documentChanges`.
|
||||
* Depending on the client capability
|
||||
* `workspace.workspaceEdit.resourceOperations` document changes are either
|
||||
* an array of `TextDocumentEdit`s to express changes to n different text
|
||||
* documents where each text document edit addresses a specific version of
|
||||
* a text document. Or it can contain above `TextDocumentEdit`s mixed with
|
||||
* create, rename and delete file / folder operations.
|
||||
*
|
||||
* Note: If the client can handle versioned document edits and if documentChanges are present,
|
||||
* the latter are preferred over changes.
|
||||
* Whether a client supports versioned document edits is expressed via
|
||||
* `workspace.workspaceEdit.documentChanges` client capability.
|
||||
*
|
||||
* If a client neither supports `documentChanges` nor
|
||||
* `workspace.workspaceEdit.resourceOperations` then only plain `TextEdit`s
|
||||
* using the `changes` property are supported.
|
||||
*/
|
||||
std::optional<QList<TextDocumentEdit>> documentChanges() const
|
||||
{ return optionalArray<TextDocumentEdit>(documentChangesKey); }
|
||||
void setDocumentChanges(const QList<TextDocumentEdit> &changes)
|
||||
std::optional<QList<DocumentChange>> documentChanges() const
|
||||
{ return optionalArray<DocumentChange>(documentChangesKey); }
|
||||
void setDocumentChanges(const QList<DocumentChange> &changes)
|
||||
{ insertArray(documentChangesKey, changes); }
|
||||
};
|
||||
|
||||
|
||||
@@ -375,6 +375,14 @@ static ClientCapabilities generateClientCapabilities()
|
||||
{
|
||||
ClientCapabilities capabilities;
|
||||
WorkspaceClientCapabilities workspaceCapabilities;
|
||||
WorkspaceClientCapabilities::WorkspaceEditCapabilities workspaceEditCapabilities;
|
||||
workspaceEditCapabilities.setDocumentChanges(true);
|
||||
using ResourceOperationKind
|
||||
= WorkspaceClientCapabilities::WorkspaceEditCapabilities::ResourceOperationKind;
|
||||
workspaceEditCapabilities.setResourceOperations({ResourceOperationKind::Create,
|
||||
ResourceOperationKind::Rename,
|
||||
ResourceOperationKind::Delete});
|
||||
workspaceCapabilities.setWorkspaceEdit(workspaceEditCapabilities);
|
||||
workspaceCapabilities.setWorkspaceFolders(true);
|
||||
workspaceCapabilities.setApplyEdit(true);
|
||||
DynamicRegistrationCapabilities allowDynamicRegistration;
|
||||
|
||||
@@ -539,24 +539,50 @@ Utils::SearchResultItems generateReplaceItems(const WorkspaceEdit &edits,
|
||||
bool limitToProjects,
|
||||
const DocumentUri::PathMapper &pathMapper)
|
||||
{
|
||||
Utils::SearchResultItems items;
|
||||
auto convertEdits = [](const QList<TextEdit> &edits) {
|
||||
return Utils::transform(edits, [](const TextEdit &edit) {
|
||||
return ItemData{SymbolSupport::convertRange(edit.range()), QVariant(edit)};
|
||||
});
|
||||
};
|
||||
QMap<Utils::FilePath, QList<ItemData>> rangesInDocument;
|
||||
auto documentChanges = edits.documentChanges().value_or(QList<TextDocumentEdit>());
|
||||
auto documentChanges = edits.documentChanges().value_or(QList<DocumentChange>());
|
||||
if (!documentChanges.isEmpty()) {
|
||||
for (const TextDocumentEdit &documentChange : std::as_const(documentChanges)) {
|
||||
rangesInDocument[documentChange.textDocument().uri().toFilePath(pathMapper)]
|
||||
= convertEdits(documentChange.edits());
|
||||
for (const DocumentChange &documentChange : std::as_const(documentChanges)) {
|
||||
if (std::holds_alternative<TextDocumentEdit>(documentChange)) {
|
||||
const TextDocumentEdit edit = std::get<TextDocumentEdit>(documentChange);
|
||||
rangesInDocument[edit.textDocument().uri().toFilePath(pathMapper)] = convertEdits(
|
||||
edit.edits());
|
||||
} else {
|
||||
Utils::SearchResultItem item;
|
||||
|
||||
if (std::holds_alternative<LanguageServerProtocol::CreateFile>(documentChange)) {
|
||||
auto op = std::get<LanguageServerProtocol::CreateFile>(documentChange);
|
||||
item.setLineText(op.message(pathMapper));
|
||||
item.setFilePath(op.uri().toFilePath(pathMapper));
|
||||
item.setUserData(QVariant(op));
|
||||
} else if (std::holds_alternative<RenameFile>(documentChange)) {
|
||||
auto op = std::get<RenameFile>(documentChange);
|
||||
item.setLineText(op.message(pathMapper));
|
||||
item.setFilePath(op.oldUri().toFilePath(pathMapper));
|
||||
item.setUserData(QVariant(op));
|
||||
} else if (std::holds_alternative<LanguageServerProtocol::DeleteFile>(documentChange)) {
|
||||
auto op = std::get<LanguageServerProtocol::DeleteFile>(documentChange);
|
||||
item.setLineText(op.message(pathMapper));
|
||||
item.setFilePath(op.uri().toFilePath(pathMapper));
|
||||
item.setUserData(QVariant(op));
|
||||
}
|
||||
|
||||
items << item;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
auto changes = edits.changes().value_or(WorkspaceEdit::Changes());
|
||||
for (auto it = changes.begin(), end = changes.end(); it != end; ++it)
|
||||
rangesInDocument[it.key().toFilePath(pathMapper)] = convertEdits(it.value());
|
||||
}
|
||||
return generateSearchResultItems(rangesInDocument, search, limitToProjects);
|
||||
items += generateSearchResultItems(rangesInDocument, search, limitToProjects);
|
||||
return items;
|
||||
}
|
||||
|
||||
Core::SearchResult *SymbolSupport::createSearch(const TextDocumentPositionParams &positionParams,
|
||||
@@ -659,15 +685,25 @@ void SymbolSupport::applyRename(const Utils::SearchResultItems &checkedItems,
|
||||
{
|
||||
QSet<Utils::FilePath> affectedNonOpenFilePaths;
|
||||
QMap<Utils::FilePath, QList<TextEdit>> editsForDocuments;
|
||||
QList<DocumentChange> changes;
|
||||
for (const Utils::SearchResultItem &item : checkedItems) {
|
||||
const auto filePath = Utils::FilePath::fromUserInput(item.path().value(0));
|
||||
if (!m_client->documentForFilePath(filePath))
|
||||
affectedNonOpenFilePaths << filePath;
|
||||
TextEdit edit(item.userData().toJsonObject());
|
||||
if (edit.isValid())
|
||||
const QJsonObject jsonObject = item.userData().toJsonObject();
|
||||
if (const TextEdit edit(jsonObject); edit.isValid())
|
||||
editsForDocuments[filePath] << edit;
|
||||
else if (const LanguageServerProtocol::CreateFile createFile(jsonObject); createFile.isValid())
|
||||
changes << createFile;
|
||||
else if (const RenameFile renameFile(jsonObject); renameFile.isValid())
|
||||
changes << renameFile;
|
||||
else if (const LanguageServerProtocol::DeleteFile deleteFile(jsonObject); deleteFile.isValid())
|
||||
changes << deleteFile;
|
||||
}
|
||||
|
||||
for (const DocumentChange &change : changes)
|
||||
applyDocumentChange(m_client, change);
|
||||
|
||||
for (auto it = editsForDocuments.begin(), end = editsForDocuments.end(); it != end; ++it)
|
||||
applyTextEdits(m_client, it.key(), it.value());
|
||||
|
||||
|
||||
@@ -120,11 +120,10 @@ void applyTextEdit(TextDocumentManipulatorInterface &manipulator,
|
||||
bool applyWorkspaceEdit(const Client *client, const WorkspaceEdit &edit)
|
||||
{
|
||||
bool result = true;
|
||||
const QList<TextDocumentEdit> &documentChanges
|
||||
= edit.documentChanges().value_or(QList<TextDocumentEdit>());
|
||||
const auto documentChanges = edit.documentChanges().value_or(QList<DocumentChange>());
|
||||
if (!documentChanges.isEmpty()) {
|
||||
for (const TextDocumentEdit &documentChange : documentChanges)
|
||||
result |= applyTextDocumentEdit(client, documentChange);
|
||||
for (const DocumentChange &documentChange : documentChanges)
|
||||
result |= applyDocumentChange(client, documentChange);
|
||||
} else {
|
||||
const WorkspaceEdit::Changes &changes = edit.changes().value_or(WorkspaceEdit::Changes());
|
||||
for (auto it = changes.cbegin(); it != changes.cend(); ++it)
|
||||
@@ -188,13 +187,13 @@ void updateCodeActionRefactoringMarker(Client *client,
|
||||
if (std::optional<WorkspaceEdit> edit = action.edit()) {
|
||||
if (diagnostics.isEmpty()) {
|
||||
QList<TextEdit> edits;
|
||||
if (std::optional<QList<TextDocumentEdit>> documentChanges = edit->documentChanges()) {
|
||||
QList<TextDocumentEdit> changesForUri = Utils::filtered(
|
||||
*documentChanges, [uri](const TextDocumentEdit &edit) {
|
||||
return edit.textDocument().uri() == uri;
|
||||
});
|
||||
for (const TextDocumentEdit &edit : changesForUri)
|
||||
edits << edit.edits();
|
||||
if (std::optional<QList<DocumentChange>> documentChanges = edit->documentChanges()) {
|
||||
for (const DocumentChange &change : *documentChanges) {
|
||||
if (auto edit = std::get_if<TextDocumentEdit>(&change)) {
|
||||
if (edit->textDocument().uri() == uri)
|
||||
edits << edit->edits();
|
||||
}
|
||||
}
|
||||
} else if (std::optional<WorkspaceEdit::Changes> localChanges = edit->changes()) {
|
||||
edits = (*localChanges)[uri];
|
||||
}
|
||||
@@ -345,4 +344,58 @@ const QIcon symbolIcon(int type)
|
||||
return icons[kind];
|
||||
}
|
||||
|
||||
bool applyDocumentChange(const Client *client, const DocumentChange &change)
|
||||
{
|
||||
if (!client)
|
||||
return false;
|
||||
|
||||
if (std::holds_alternative<TextDocumentEdit>(change)) {
|
||||
return applyTextDocumentEdit(client, std::get<TextDocumentEdit>(change));
|
||||
} else if (std::holds_alternative<LanguageServerProtocol::CreateFile>(change)) {
|
||||
const auto createOperation = std::get<LanguageServerProtocol::CreateFile>(change);
|
||||
const FilePath filePath = createOperation.uri().toFilePath(client->hostPathMapper());
|
||||
if (filePath.exists()) {
|
||||
if (const std::optional<CreateFileOptions> options = createOperation.options()) {
|
||||
if (options->overwrite().value_or(false)) {
|
||||
if (!filePath.removeFile())
|
||||
return false;
|
||||
} else if (options->ignoreIfExists().value_or(false)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return filePath.ensureExistingFile();
|
||||
} else if (std::holds_alternative<RenameFile>(change)) {
|
||||
const RenameFile renameOperation = std::get<RenameFile>(change);
|
||||
const FilePath oldPath = renameOperation.oldUri().toFilePath(client->hostPathMapper());
|
||||
if (!oldPath.exists())
|
||||
return false;
|
||||
const FilePath newPath = renameOperation.newUri().toFilePath(client->hostPathMapper());
|
||||
if (oldPath == newPath)
|
||||
return true;
|
||||
if (newPath.exists()) {
|
||||
if (const std::optional<CreateFileOptions> options = renameOperation.options()) {
|
||||
if (options->overwrite().value_or(false)) {
|
||||
if (!newPath.removeFile())
|
||||
return false;
|
||||
} else if (options->ignoreIfExists().value_or(false)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return oldPath.renameFile(newPath);
|
||||
} else if (std::holds_alternative<LanguageServerProtocol::DeleteFile>(change)) {
|
||||
const auto deleteOperation = std::get<LanguageServerProtocol::DeleteFile>(change);
|
||||
const FilePath filePath = deleteOperation.uri().toFilePath(client->hostPathMapper());
|
||||
if (const std::optional<DeleteFileOptions> options = deleteOperation.options()) {
|
||||
if (!filePath.exists())
|
||||
return options->ignoreIfNotExists().value_or(false);
|
||||
if (filePath.isDir() && options->recursive().value_or(false))
|
||||
return filePath.removeRecursively();
|
||||
}
|
||||
return filePath.removeFile();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace LanguageClient
|
||||
|
||||
@@ -35,6 +35,8 @@ bool LANGUAGECLIENT_EXPORT applyTextEdits(const Client *client,
|
||||
bool LANGUAGECLIENT_EXPORT applyTextEdits(const Client *client,
|
||||
const Utils::FilePath &filePath,
|
||||
const QList<LanguageServerProtocol::TextEdit> &edits);
|
||||
bool LANGUAGECLIENT_EXPORT applyDocumentChange(const Client *client,
|
||||
const LanguageServerProtocol::DocumentChange &change);
|
||||
void LANGUAGECLIENT_EXPORT applyTextEdit(TextEditor::TextDocumentManipulatorInterface &manipulator,
|
||||
const LanguageServerProtocol::TextEdit &edit,
|
||||
bool newTextIsSnippet = false);
|
||||
|
||||
Reference in New Issue
Block a user