hokein updated this revision to Diff 212347.
hokein added a comment.

update the diff


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D65263/new/

https://reviews.llvm.org/D65263

Files:
  clang-tools-extra/clangd/ClangdLSPServer.cpp
  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
@@ -365,7 +365,43 @@
   // replace expression with variable name
   if (auto Err = Result.add(Target->replaceWithVar(Range, 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);
+  Step Apply;
+  Apply.ApplyKind = Step::ApplyEdit;
+  Apply.ApplyEditParams = std::move(Result);
+  Step Rename;
+  Rename.ApplyKind = Step::Rename;
+  Rename.RenameParams = offsetToPosition(*NewCode, ExtractVarOffset);
+  // TODO: We should not emit Rename if the client doesn't support it.
+  return Effect::applySteps({std::move(Apply), std::move(Rename)});
 }
 
 // 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
@@ -64,12 +64,44 @@
     /// Provide information to the user.
     Info,
   };
+
+  // The class represents a small and focus refactoring. The purpose is to form
+  // a chain of different refactorings, so that we could perform a large and
+  // complicate refactoring.
+  //
+  // Steps are applied in sequence. In LSP layer, each step is transformed to a
+  // call request to the LSP client, LSPServer waits for the client response and
+  // continues to apply next step when the previous step is finished
+  // successfully.
+  //
+  // clangd doesn't track changes of the source files when apply a step, so
+  // the following steps are not aware of the changes, Tweak is corresponding to
+  // pre-calculate the changes for all steps.
+  // FIXME: we should have a mechanism to track the changes for each step.
+  struct Step {
+    enum Kind {
+      ApplyEdit,
+      Rename,
+    };
+    Kind ApplyKind;
+
+    llvm::Optional<tooling::Replacements> ApplyEditParams; // For ApplyEdit
+    llvm::Optional<Position> RenameParams;                 // For Rename
+  };
+
   struct Effect {
     /// A message to be displayed to the user.
     llvm::Optional<std::string> ShowMessage;
     /// An edit to apply to the input file.
     llvm::Optional<tooling::Replacements> ApplyEdit;
+    /// A chain of steps being executed in order when applying the tweak.
+    llvm::Optional<std::vector<Step>> ApplySteps;
 
+    static Effect applySteps(std::vector<Step> Steps) {
+      Effect E;
+      E.ApplySteps = std::move(Steps);
+      return E;
+    }
     static Effect applyEdit(tooling::Replacements R) {
       Effect E;
       E.ApplyEdit = std::move(R);
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
@@ -128,6 +128,36 @@
                 status.clear();
             }
         })
+
+    clangdClient.onReady().then(
+        () => {clangdClient.onRequest(
+            "clangd.triggerRename", async (result: any) => {
+              if (result.arguments) {
+                return await vscode.commands
+                    .executeCommand(
+                        'editor.action.rename',
+                        [
+                          vscode.Uri.file(
+                              vscode.window.activeTextEditor.document.fileName),
+                          result.arguments[0]
+                        ])
+                    .then((value) => { return Promise.resolve(true); },
+                          (err) => {
+                            if (err.name == 'Canceled') {
+                              // VSCode will cancel the request automatically
+                              // when it detects there is a change of the file
+                              // (rename) during the process of handling the
+                              // triggerRename request. but we don't want client
+                              // send back a cancel error to clangd, so consume
+                              // the cancel here.
+                              return Promise.resolve(true);
+                            }
+
+                            return Promise.resolve(err);
+                          });
+              }
+              return Promise.reject(new Error("Invalid commend arguments"));
+            })});
     // An empty place holder for the activate command, otherwise we'll get an
     // "command is not registered" error.
     context.subscriptions.push(vscode.commands.registerCommand(
Index: clang-tools-extra/clangd/ClangdLSPServer.cpp
===================================================================
--- clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -551,9 +551,87 @@
     auto Action = [this, ApplyEdit](decltype(Reply) Reply, URIForFile File,
                                     std::string Code,
                                     llvm::Expected<Tweak::Effect> R) {
+      // Function object to apply steps for a tweak.
+      // Steps are executed in order: for each step, clangd sends a new request
+      // to the LSP client, waits for the response and continues next step; if
+      // there is any error during the apply process, clangd stops applying the
+      // following steps.
+      //
+      // Example steps [applyEdit, Rename]
+      //  - apply the first step:  send call(applyEdit, Workspace) request
+      //  - ..wait for client response
+      //  - apply the second step: send call(rename, trigger)
+      //  - ..wait for the reply, and finish.
+      struct CallInSequence {
+      public:
+        CallInSequence(ClangdLSPServer *Server, std::vector<Tweak::Step> Steps,
+                       std::string Code, URIForFile MainFile)
+            : Server(Server), Steps(std::move(Steps)), nextStepIndex(0),
+              Code(std::move(Code)), MainFile(std::move(MainFile)) {}
+
+        void operator()(llvm::Expected<llvm::json::Value> Result) {
+          if (!Result) {
+            // FIXME: should send a failed reply to client.
+            log("receive error: {0}", llvm::toString(Result.takeError()));
+            return;
+          }
+          if (nextStepIndex >= Steps.size()) {
+            // FIXME: should send a successful reply to client.
+            log("applied tweak successfully");
+            return;
+          }
+          auto RequestToClient = takeNextStep();
+          nextCall(Server, RequestToClient.first, RequestToClient.second);
+        }
+
+        std::pair<std::string, llvm::json::Value> takeNextStep() {
+          assert(nextStepIndex < Steps.size() && "index out of boundary!");
+          return transform(Steps[nextStepIndex++]);
+        }
+
+      private:
+        // Transform the step into a LSP request.
+        std::pair</* Method */ std::string,
+                  /* MethodParams */ llvm::json::Value>
+        transform(const Tweak::Step &Step) {
+          if (Step.ApplyKind == Tweak::Step::ApplyEdit) {
+            ApplyWorkspaceEditParams Edit;
+
+            Edit.edit.changes.emplace();
+            (*Edit.edit.changes)[MainFile.uri()] =
+                replacementsToEdits(Code, *Step.ApplyEditParams);
+            return {"workspace/applyEdit", std::move(Edit)};
+          } else if (Step.ApplyKind == Tweak::Step::Rename) {
+            // Clangd extension.
+            llvm::json::Object Result;
+            Result["arguments"] = {*Step.RenameParams};
+            return {"clangd.triggerRename", std::move(Result)};
+          }
+          llvm_unreachable("unhandled apply kind");
+        }
+        void nextCall(ClangdLSPServer *LSPServer, llvm::StringRef Method,
+                      llvm::json::Value Params) {
+          LSPServer->call(Method, std::move(Params), std::move(*this));
+        }
+
+        ClangdLSPServer *Server;
+        std::vector<Tweak::Step> Steps;
+        size_t nextStepIndex;
+
+        // datas for transforming step.
+        std::string Code;
+        URIForFile MainFile;
+      };
+
       if (!R)
         return Reply(R.takeError());
 
+      if (R->ApplySteps) {
+        CallInSequence Calls(this, *R->ApplySteps, Code, File);
+        auto Request = Calls.takeNextStep();
+        // Starts the call chain.
+        call(Request.first, std::move(Request.second), std::move(Calls));
+      }
       if (R->ApplyEdit) {
         WorkspaceEdit WE;
         WE.changes.emplace();
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to