diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -496,9 +496,11 @@ void ClangdLSPServer::onCommand(const ExecuteCommandParams &Params, Callback Reply) { - auto ApplyEdit = [this](WorkspaceEdit WE) { + auto ApplyEdit = [this](WorkspaceEdit WE, + llvm::Optional Command) { ApplyWorkspaceEditParams Edit; Edit.edit = std::move(WE); + Edit.command = std::move(Command); // Ideally, we would wait for the response and if there is no error, we // would reply success/failure to the original RPC. call("workspace/applyEdit", Edit); @@ -515,7 +517,7 @@ // we ignore it) Reply("Fix applied."); - ApplyEdit(*Params.workspaceEdit); + ApplyEdit(*Params.workspaceEdit, llvm::None); } else if (Params.command == ExecuteCommandParams::CLANGD_APPLY_TWEAK && Params.tweakArgs) { auto Code = DraftMgr.getDraft(Params.tweakArgs->file.file()); @@ -534,7 +536,7 @@ WorkspaceEdit WE; WE.changes.emplace(); (*WE.changes)[File.uri()] = replacementsToEdits(Code, *R->ApplyEdit); - ApplyEdit(std::move(WE)); + ApplyEdit(std::move(WE), R->Command); } if (R->ShowMessage) { ShowMessageParams Msg; diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h --- a/clang-tools-extra/clangd/Protocol.h +++ b/clang-tools-extra/clangd/Protocol.h @@ -762,6 +762,22 @@ }; llvm::json::Value toJSON(const Command &C); +// Similar to Command, but this command is executed in the LSP client (clangd +// extension). +// +// interface ClientCommand { +// command: string +// arguments?: any[] +// } +struct ClientCommand { + const static llvm::StringLiteral LSP_RENAME; // trigger LSP rename. + std::string command; // command identifier + + // each command has a certain llvm::Optional structure for its arguments. + llvm::Optional renamePosition; // for LSP_RENAME +}; +llvm::json::Value toJSON(const ClientCommand &C); + /// A code action represents a change that can be performed in code, e.g. to fix /// a problem or to refactor code. /// @@ -787,6 +803,9 @@ /// A command this code action executes. If a code action provides an edit /// and a command, first the edit is executed and then the command. llvm::Optional command; + + /// A command that will be executed in the LSP client. + llvm::Optional clientCommand; }; llvm::json::Value toJSON(const CodeAction &); @@ -870,6 +889,9 @@ struct ApplyWorkspaceEditParams { WorkspaceEdit edit; + /// A command that will be executed in the LSP client after applying edit + /// (clangd extension). + llvm::Optional command; }; llvm::json::Value toJSON(const ApplyWorkspaceEditParams &); diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp --- a/clang-tools-extra/clangd/Protocol.cpp +++ b/clang-tools-extra/clangd/Protocol.cpp @@ -541,6 +541,15 @@ return false; // Unrecognized command. } +const llvm::StringLiteral ClientCommand::LSP_RENAME = "lsp.rename"; + +llvm::json::Value toJSON(const ClientCommand &C) { + auto Result = llvm::json::Object{{"command", C.command}}; + if (C.renamePosition) + Result["arguments"] = {*C.renamePosition}; + return Result; +} + llvm::json::Value toJSON(const SymbolInformation &P) { return llvm::json::Object{ {"name", P.name}, @@ -667,7 +676,10 @@ } llvm::json::Value toJSON(const ApplyWorkspaceEditParams &Params) { - return llvm::json::Object{{"edit", Params.edit}}; + auto Result = llvm::json::Object{{"edit", Params.edit}}; + if (Params.command) + Result["clientCommand"] = *Params.command; + return Result; } bool fromJSON(const llvm::json::Value &Params, TextDocumentPositionParams &R) { diff --git a/clang-tools-extra/clangd/clients/clangd-vscode/src/extension.ts b/clang-tools-extra/clangd/clients/clangd-vscode/src/extension.ts --- a/clang-tools-extra/clangd/clients/clangd-vscode/src/extension.ts +++ b/clang-tools-extra/clangd/clients/clangd-vscode/src/extension.ts @@ -93,6 +93,28 @@ const clangdClient = new vscodelc.LanguageClient('Clang Language Server',serverOptions, clientOptions); console.log('Clang Language Server is now active!'); + + vscode.commands.registerCommand("lsp.rename", async (position: vscode.Position) => { + await vscode.commands.executeCommand('editor.action.rename', [ + vscode.Uri.file(vscode.window.activeTextEditor.document.fileName), + position + ]); + }); + clangdClient.onReady().then(async () => { + clangdClient.onRequest("workspace/applyEdit", async (result: any) => { + if (result.edit) { + await vscode.workspace.applyEdit(clangdClient.protocol2CodeConverter.asWorkspaceEdit(result.edit)); + } + const command = result.clientCommand; + if (command) { + if (command.arguments) + await vscode.commands.executeCommand(command.command, ...command.arguments); + else + await vscode.commands.executeCommand(command.command); + + } + }) + }); context.subscriptions.push(clangdClient.start()); context.subscriptions.push(vscode.commands.registerCommand( 'clangd-vscode.switchheadersource', async () => { diff --git a/clang-tools-extra/clangd/refactor/Tweak.h b/clang-tools-extra/clangd/refactor/Tweak.h --- a/clang-tools-extra/clangd/refactor/Tweak.h +++ b/clang-tools-extra/clangd/refactor/Tweak.h @@ -69,10 +69,15 @@ llvm::Optional ShowMessage; /// An edit to apply to the input file. llvm::Optional ApplyEdit; + /// A command that will be executed in client side after applying edits. + llvm::Optional Command; - static Effect applyEdit(tooling::Replacements R) { + static Effect + applyEdit(tooling::Replacements R, + llvm::Optional Command = llvm::None) { Effect E; E.ApplyEdit = std::move(R); + E.Command = std::move(Command); return E; } static Effect showMessage(StringRef S) { diff --git a/clang-tools-extra/clangd/refactor/tweaks/ExtractVariable.cpp b/clang-tools-extra/clangd/refactor/tweaks/ExtractVariable.cpp --- a/clang-tools-extra/clangd/refactor/tweaks/ExtractVariable.cpp +++ b/clang-tools-extra/clangd/refactor/tweaks/ExtractVariable.cpp @@ -224,7 +224,41 @@ // replace expression with variable name if (auto Err = Result.add(Target->replaceWithVar(VarName))) return std::move(Err); - return Effect::applyEdit(Result); + + // TODO: refine this. + // We do the format internally in order to keep the position of the extracted + // variable not being changed from the caller. + auto &SM = Inputs.AST.getASTContext().getSourceManager(); + auto *FS = &SM.getFileManager().getVirtualFileSystem(); + auto Style = getFormatStyleForFile( + SM.getFileEntryForID(SM.getMainFileID())->tryGetRealPathName(), + Inputs.Code, FS); + auto Formatted = cleanupAndFormat(Inputs.Code, Result, Style); + if (!Formatted) + return Formatted.takeError(); + Result = *Formatted; + auto NewCode = tooling::applyAllReplacements(Inputs.Code, Result); + if (!NewCode) + return NewCode.takeError(); + + assert(!Result.empty()); + // Calculate the offset of the dummy variable after applying replacements. + size_t ExtractVarOffset = Result.begin()->getOffset(); + for (auto &R : Result) { + auto OffsetInReplacement = R.getReplacementText().find(VarName); + if (OffsetInReplacement != llvm::StringRef::npos) { + ExtractVarOffset += OffsetInReplacement; + break; + } + ExtractVarOffset += R.getReplacementText().size() - R.getLength(); + } + + assert(ExtractVarOffset != llvm::StringRef::npos); + + ClientCommand ExecuteRename; + ExecuteRename.command = ClientCommand::LSP_RENAME; + ExecuteRename.renamePosition = offsetToPosition(*NewCode, ExtractVarOffset); + return Effect::applyEdit(Result, std::move(ExecuteRename)); } // Find the CallExpr whose callee is an ancestor of the DeclRef