hokein created this revision.
Herald added subscribers: cfe-commits, kadircet, arphaman, jkorous, MaskRay, 
ilya-biryukov.
Herald added a project: clang.

This is a prototype patch.

LSP doesn't have support for asking user input currently. Other language
servers (Typescript, Java) will trigger rename after running extract
action, we do the same in clangd (to provide consistent behavior across
languages)

but this requires some LSP extensions:

- ClientCommand which will be executed in LSP client side;
- we add a client command to the result of `applyEdit` request;
- LSP client support;


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D65263

Files:
  clang-tools-extra/clangd/ClangdLSPServer.cpp
  clang-tools-extra/clangd/Protocol.cpp
  clang-tools-extra/clangd/Protocol.h
  clang-tools-extra/clangd/clients/clangd-vscode/src/extension.ts
  clang-tools-extra/clangd/refactor/Tweak.h
  clang-tools-extra/clangd/refactor/tweaks/ExtractVariable.cpp

Index: clang-tools-extra/clangd/refactor/tweaks/ExtractVariable.cpp
===================================================================
--- clang-tools-extra/clangd/refactor/tweaks/ExtractVariable.cpp
+++ 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
Index: clang-tools-extra/clangd/refactor/Tweak.h
===================================================================
--- clang-tools-extra/clangd/refactor/Tweak.h
+++ clang-tools-extra/clangd/refactor/Tweak.h
@@ -69,10 +69,15 @@
     llvm::Optional<std::string> ShowMessage;
     /// An edit to apply to the input file.
     llvm::Optional<tooling::Replacements> ApplyEdit;
+    /// A command that will be executed in client side after applying edits.
+    llvm::Optional<ClientCommand> Command;
 
-    static Effect applyEdit(tooling::Replacements R) {
+    static Effect
+    applyEdit(tooling::Replacements R,
+              llvm::Optional<ClientCommand> Command = llvm::None) {
       Effect E;
       E.ApplyEdit = std::move(R);
+      E.Command = std::move(Command);
       return E;
     }
     static Effect showMessage(StringRef S) {
Index: clang-tools-extra/clangd/clients/clangd-vscode/src/extension.ts
===================================================================
--- clang-tools-extra/clangd/clients/clangd-vscode/src/extension.ts
+++ 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 () => {
Index: clang-tools-extra/clangd/Protocol.h
===================================================================
--- clang-tools-extra/clangd/Protocol.h
+++ 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<Position> 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> command;
+
+  /// A command that will be executed in the LSP client.
+  llvm::Optional<ClientCommand> 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<ClientCommand> command;
 };
 llvm::json::Value toJSON(const ApplyWorkspaceEditParams &);
 
Index: clang-tools-extra/clangd/Protocol.cpp
===================================================================
--- clang-tools-extra/clangd/Protocol.cpp
+++ 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) {
Index: clang-tools-extra/clangd/ClangdLSPServer.cpp
===================================================================
--- clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -496,9 +496,11 @@
 
 void ClangdLSPServer::onCommand(const ExecuteCommandParams &Params,
                                 Callback<llvm::json::Value> Reply) {
-  auto ApplyEdit = [this](WorkspaceEdit WE) {
+  auto ApplyEdit = [this](WorkspaceEdit WE,
+                          llvm::Optional<ClientCommand> 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;
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to