| 1 | // Copyright (C) 2023 The Qt Company Ltd. | 
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only | 
| 3 |  | 
| 4 | #include "qqmllsutils_p.h" | 
| 5 | #include "qqmlrenamesymbolsupport_p.h" | 
| 6 | #include <utility> | 
| 7 |  | 
| 8 | QT_BEGIN_NAMESPACE | 
| 9 |  | 
| 10 | using namespace Qt::StringLiterals; | 
| 11 | QQmlRenameSymbolSupport::QQmlRenameSymbolSupport(QmlLsp::QQmlCodeModel *model) : BaseT(model) { } | 
| 12 |  | 
| 13 | QString QQmlRenameSymbolSupport::name() const | 
| 14 | { | 
| 15 |     return u"QmlRenameSymbolSupport"_s ; | 
| 16 | } | 
| 17 |  | 
| 18 | void QQmlRenameSymbolSupport::setupCapabilities( | 
| 19 |         const QLspSpecification::InitializeParams &, | 
| 20 |         QLspSpecification::InitializeResult &serverCapabilities) | 
| 21 | { | 
| 22 |     // use a bool for now. Alternatively, if the client supports "prepareSupport", one could | 
| 23 |     // use a RenameOptions here. See following page for more information about prepareSupport: | 
| 24 |     // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_prepareRename | 
| 25 |     serverCapabilities.capabilities.renameProvider = true; | 
| 26 | } | 
| 27 |  | 
| 28 | void QQmlRenameSymbolSupport::registerHandlers(QLanguageServer *, QLanguageServerProtocol *protocol) | 
| 29 | { | 
| 30 |     protocol->registerRenameRequestHandler(handler: getRequestHandler()); | 
| 31 | } | 
| 32 |  | 
| 33 | void QQmlRenameSymbolSupport::process(QQmlRenameSymbolSupport::RequestPointerArgument request) | 
| 34 | { | 
| 35 |     QLspSpecification::WorkspaceEdit result; | 
| 36 |     ResponseScopeGuard guard(result, request->m_response); | 
| 37 |  | 
| 38 |     auto itemsFound = itemsForRequest(request); | 
| 39 |     if (guard.setErrorFrom(itemsFound)) | 
| 40 |         return; | 
| 41 |  | 
| 42 |     QQmlLSUtils::ItemLocation &front = | 
| 43 |             std::get<QList<QQmlLSUtils::ItemLocation>>(v&: itemsFound).front(); | 
| 44 |  | 
| 45 |     const QString newName = QString::fromUtf8(ba: request->m_parameters.newName); | 
| 46 |     auto expressionType = | 
| 47 |             QQmlLSUtils::resolveExpressionType(item: front.domItem, QQmlLSUtils::ResolveOwnerType); | 
| 48 |  | 
| 49 |     if (!expressionType) { | 
| 50 |         guard.setError(QQmlLSUtils::ErrorMessage{ .code: 0, .message: u"Cannot rename the requested object"_s  }); | 
| 51 |         return; | 
| 52 |     } | 
| 53 |  | 
| 54 |     if (guard.setErrorFrom(QQmlLSUtils::checkNameForRename(item: front.domItem, newName, targetType: expressionType))) | 
| 55 |         return; | 
| 56 |  | 
| 57 |     auto &editsByFileForResult = result.documentChanges.emplace(); | 
| 58 |  | 
| 59 |     // The QLspSpecification::WorkspaceEdit requires the changes to be grouped by files, so | 
| 60 |     // collect them into editsByFileUris. | 
| 61 |     QMap<QUrl, QList<QLspSpecification::TextEdit>> editsByFileUris; | 
| 62 |  | 
| 63 |     const auto renames = QQmlLSUtils::renameUsagesOf(item: front.domItem, newName, targetType: expressionType); | 
| 64 |     for (const auto &rename : renames.renameInFile()) { | 
| 65 |         QLspSpecification::TextEdit edit; | 
| 66 |  | 
| 67 |         const QUrl uri = QUrl::fromLocalFile(localfile: rename.location.filename()); | 
| 68 |         edit.range = QQmlLSUtils::qmlLocationToLspLocation(qmlLocation: rename.location); | 
| 69 |         edit.newText = rename.replacement.toUtf8(); | 
| 70 |  | 
| 71 |         editsByFileUris[uri].append(t: edit); | 
| 72 |     } | 
| 73 |  | 
| 74 |     for (auto it = editsByFileUris.keyValueBegin(); it != editsByFileUris.keyValueEnd(); ++it) { | 
| 75 |         QLspSpecification::TextDocumentEdit editsForCurrentFile; | 
| 76 |         editsForCurrentFile.textDocument.uri = it->first.toEncoded(); | 
| 77 |  | 
| 78 |         // TODO: do we need to take care of the optional versioning in | 
| 79 |         // editsForCurrentFile.textDocument.version? see | 
| 80 |         // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#optionalVersionedTextDocumentIdentifier | 
| 81 |         // for more details | 
| 82 |  | 
| 83 |         for (const auto &x : std::as_const(t&: it->second)) { | 
| 84 |             editsForCurrentFile.edits.append(t: x); | 
| 85 |         } | 
| 86 |         editsByFileForResult.append(t: editsForCurrentFile); | 
| 87 |     } | 
| 88 |  | 
| 89 |     // if files need to be renamed, then do it after the text edits | 
| 90 |     for (const auto &rename : renames.renameInFilename()) { | 
| 91 |         QLspSpecification::RenameFile currentRenameFile; | 
| 92 |         currentRenameFile.kind = "rename" ; | 
| 93 |         currentRenameFile.oldUri = QUrl::fromLocalFile(localfile: rename.oldFilename).toEncoded(); | 
| 94 |         currentRenameFile.newUri = QUrl::fromLocalFile(localfile: rename.newFilename).toEncoded(); | 
| 95 |         editsByFileForResult.append(t: currentRenameFile); | 
| 96 |     } | 
| 97 | } | 
| 98 |  | 
| 99 | QT_END_NAMESPACE | 
| 100 |  |