ashgti updated this revision to Diff 535590.
ashgti added a comment.

Another tweak to the auto mode behavior.


Repository:
  rG LLVM Github Monorepo

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

https://reviews.llvm.org/D154030

Files:
  lldb/tools/lldb-vscode/JSONUtils.cpp
  lldb/tools/lldb-vscode/JSONUtils.h
  lldb/tools/lldb-vscode/Options.td
  lldb/tools/lldb-vscode/README.md
  lldb/tools/lldb-vscode/SourceBreakpoint.cpp
  lldb/tools/lldb-vscode/VSCode.cpp
  lldb/tools/lldb-vscode/VSCode.h
  lldb/tools/lldb-vscode/lldb-vscode.cpp

Index: lldb/tools/lldb-vscode/lldb-vscode.cpp
===================================================================
--- lldb/tools/lldb-vscode/lldb-vscode.cpp
+++ lldb/tools/lldb-vscode/lldb-vscode.cpp
@@ -252,7 +252,7 @@
         }
       }
 
-      // We will have cleared g_vsc.focus_tid if he focus thread doesn't have
+      // We will have cleared g_vsc.focus_tid if the focus thread doesn't have
       // a stop reason, so if it was cleared, or wasn't set, or doesn't exist,
       // then set the focus thread to the first thread with a stop reason.
       if (!focus_thread_exists || g_vsc.focus_tid == LLDB_INVALID_THREAD_ID)
@@ -1065,50 +1065,62 @@
   FillResponse(request, response);
   llvm::json::Object body;
   auto arguments = request.getObject("arguments");
+
+  // If we have a frame, try to set the context for variable completions.
+  lldb::SBFrame frame = g_vsc.GetLLDBFrame(*arguments);
+  if (frame.IsValid()) {
+    frame.GetThread().GetProcess().SetSelectedThread(frame.GetThread());
+    frame.GetThread().SetSelectedFrame(frame.GetFrameID());
+  }
+
   std::string text = std::string(GetString(arguments, "text"));
   auto original_column = GetSigned(arguments, "column", text.size());
-  auto actual_column = original_column - 1;
+  auto original_line = GetSigned(arguments, "line", 1);
+  auto offset = original_column - 1;
+  if (original_line > 1) {
+    llvm::StringRef text_ref{text};
+    ::llvm::SmallVector<::llvm::StringRef, 2> lines;
+    text_ref.split(lines, '\n');
+    for (int i = 0; i < original_line - 1; i++) {
+      offset += lines[i].size();
+    }
+  }
   llvm::json::Array targets;
-  // NOTE: the 'line' argument is not needed, as multiline expressions
-  // work well already
-  // TODO: support frameID. Currently
-  // g_vsc.debugger.GetCommandInterpreter().HandleCompletionWithDescriptions
-  // is frame-unaware.
-
-  if (!text.empty() && text[0] == '`') {
-    text = text.substr(1);
-    actual_column--;
-  } else {
-    char command[] = "expression -- ";
+
+  if (g_vsc.DetectExpressionContext(text) == ExpressionContext::Variable) {
+    char command[] = "frame variable ";
     text = command + text;
-    actual_column += strlen(command);
+    offset += strlen(command);
   }
   lldb::SBStringList matches;
   lldb::SBStringList descriptions;
-  g_vsc.debugger.GetCommandInterpreter().HandleCompletionWithDescriptions(
-      text.c_str(), actual_column, 0, -1, matches, descriptions);
-  size_t count = std::min((uint32_t)100, matches.GetSize());
-  targets.reserve(count);
-  for (size_t i = 0; i < count; i++) {
-    std::string match = matches.GetStringAtIndex(i);
-    std::string description = descriptions.GetStringAtIndex(i);
-
-    llvm::json::Object item;
-
-    llvm::StringRef match_ref = match;
-    for (llvm::StringRef commit_point : {".", "->"}) {
-      if (match_ref.contains(commit_point)) {
-        match_ref = match_ref.rsplit(commit_point).second;
+
+  if (g_vsc.debugger.GetCommandInterpreter().HandleCompletionWithDescriptions(
+          text.c_str(), offset, 0, 100, matches, descriptions)) {
+    // The first element is the common substring after the cursor position for
+    // all the matches. The rest of the elements are the matches.
+    targets.reserve(matches.GetSize() - 1);
+    std::string common_pattern = matches.GetStringAtIndex(0);
+    for (size_t i = 1; i < matches.GetSize(); i++) {
+      std::string match = matches.GetStringAtIndex(i);
+      std::string description = descriptions.GetStringAtIndex(i);
+
+      llvm::json::Object item;
+      llvm::StringRef match_ref = match;
+      for (llvm::StringRef commit_point : {".", "->"}) {
+        if (match_ref.contains(commit_point)) {
+          match_ref = match_ref.rsplit(commit_point).second;
+        }
       }
-    }
-    EmplaceSafeString(item, "text", match_ref);
+      EmplaceSafeString(item, "text", match_ref);
 
-    if (description.empty())
-      EmplaceSafeString(item, "label", match);
-    else
-      EmplaceSafeString(item, "label", match + " -- " + description);
+      if (description.empty())
+        EmplaceSafeString(item, "label", match);
+      else
+        EmplaceSafeString(item, "label", match + " -- " + description);
 
-    targets.emplace_back(std::move(item));
+      targets.emplace_back(std::move(item));
+    }
   }
 
   body.try_emplace("targets", std::move(targets));
@@ -1223,12 +1235,17 @@
   llvm::json::Object body;
   auto arguments = request.getObject("arguments");
   lldb::SBFrame frame = g_vsc.GetLLDBFrame(*arguments);
-  const auto expression = GetString(arguments, "expression");
+  std::string expression = GetString(arguments, "expression").str();
   llvm::StringRef context = GetString(arguments, "context");
 
-  if (!expression.empty() && expression[0] == '`') {
-    auto result =
-        RunLLDBCommands(llvm::StringRef(), {std::string(expression.substr(1))});
+  if (context == "repl" &&
+      g_vsc.DetectExpressionContext(expression) == ExpressionContext::Command) {
+    // If we're evaluating a command relative to the current frame, set the
+    // focus_tid to prevent steps from changing focus.
+    if (frame.IsValid()) {
+      g_vsc.focus_tid = frame.GetThread().GetThreadID();
+    }
+    auto result = RunLLDBCommands(llvm::StringRef(), {std::string(expression)});
     EmplaceSafeString(body, "result", result);
     body.try_emplace("variablesReference", (int64_t)0);
   } else {
@@ -1472,11 +1489,14 @@
   g_vsc.debugger =
       lldb::SBDebugger::Create(source_init_file, log_cb, nullptr);
   auto cmd = g_vsc.debugger.GetCommandInterpreter().AddMultiwordCommand(
-      "lldb-vscode", nullptr);
+      "lldb-vscode", "Commands for managing lldb-vscode.");
   cmd.AddCommand(
       "startDebugging", &g_vsc.start_debugging_request_handler,
       "Sends a startDebugging request from the debug adapter to the client to "
       "start a child debug session of the same type as the caller.");
+  cmd.AddCommand(
+      "repl-mode", &g_vsc.repl_mode_request_handler,
+      "Get or set the repl behavior of vscode-lldb evaluation requests.");
 
   g_vsc.progress_event_thread = std::thread(ProgressEventThreadFunction);
 
@@ -1534,7 +1554,15 @@
   // the user types very slowly... We need to address this specific issue, or
   // at least trigger completion only when the user explicitly wants it, which
   // is the behavior of LLDB CLI, that expects a TAB.
-  body.try_emplace("supportsCompletionsRequest", false);
+  body.try_emplace("supportsCompletionsRequest", true);
+
+  llvm::json::Array completion_characters;
+  completion_characters.emplace_back(".");
+  completion_characters.emplace_back(" ");
+  completion_characters.emplace_back("\t");
+  body.try_emplace("completionTriggerCharacters",
+                   std::move(completion_characters));
+
   // The debug adapter supports the modules request.
   body.try_emplace("supportsModulesRequest", true);
   // The set of additional module information exposed by the debug adapter.
@@ -1695,7 +1723,7 @@
     g_vsc.target.Launch(launch_info, error);
     g_vsc.debugger.SetAsync(true);
   } else {
-    // Set the launch info so that run commands can access the configured 
+    // Set the launch info so that run commands can access the configured
     // launch details.
     g_vsc.target.SetLaunchInfo(launch_info);
     g_vsc.RunLLDBCommands("Running launchCommands:", launchCommands);
@@ -3406,6 +3434,23 @@
     return EXIT_SUCCESS;
   }
 
+  if (input_args.hasArg(OPT_repl_mode)) {
+    llvm::opt::Arg *repl_mode = input_args.getLastArg(OPT_repl_mode);
+    llvm::StringRef repl_mode_value = repl_mode->getValue();
+    if (repl_mode_value == "auto") {
+      g_vsc.repl_mode = ReplMode::Auto;
+    } else if (repl_mode_value == "variable") {
+      g_vsc.repl_mode = ReplMode::Variable;
+    } else if (repl_mode_value == "command") {
+      g_vsc.repl_mode = ReplMode::Command;
+    } else {
+      llvm::errs()
+          << "'" << repl_mode_value
+          << "' is not a valid option, use 'variable', 'command' or 'auto'.\n";
+      return EXIT_FAILURE;
+    }
+  }
+
   if (llvm::opt::Arg *target_arg = input_args.getLastArg(OPT_launch_target)) {
     if (llvm::opt::Arg *comm_file = input_args.getLastArg(OPT_comm_file)) {
       lldb::pid_t pid = LLDB_INVALID_PROCESS_ID;
Index: lldb/tools/lldb-vscode/VSCode.h
===================================================================
--- lldb/tools/lldb-vscode/VSCode.h
+++ lldb/tools/lldb-vscode/VSCode.h
@@ -84,6 +84,14 @@
   JSONNotObject
 };
 
+enum class ReplMode { Variable = 0, Command, Auto };
+
+/// A huersitic for determining the context of an evaluation.
+enum class ExpressionContext {
+  Variable = 0,
+  Command,
+};
+
 struct Variables {
   /// Variable_reference start index of permanent expandable variable.
   static constexpr int64_t PermanentVariableStartIndex = (1ll << 32);
@@ -109,7 +117,7 @@
   /// \return a new variableReference.
   /// Specify is_permanent as true for variable that should persist entire
   /// debug session.
-  int64_t GetNewVariableRefence(bool is_permanent);
+  int64_t GetNewVariableReference(bool is_permanent);
 
   /// \return the expandable variable corresponding with variableReference
   /// value of \p value.
@@ -129,6 +137,11 @@
                  lldb::SBCommandReturnObject &result) override;
 };
 
+struct ReplModeRequestHandler : public lldb::SBCommandPluginInterface {
+  bool DoExecute(lldb::SBDebugger debugger, char **command,
+                 lldb::SBCommandReturnObject &result) override;
+};
+
 struct VSCode {
   std::string debug_adaptor_path;
   InputStream input;
@@ -173,6 +186,8 @@
   std::map<int /* request_seq */, ResponseCallback /* reply handler */>
       inflight_reverse_requests;
   StartDebuggingRequestHandler start_debugging_request_handler;
+  ReplModeRequestHandler repl_mode_request_handler;
+  ReplMode repl_mode;
 
   VSCode();
   ~VSCode();
@@ -206,6 +221,8 @@
 
   llvm::json::Value CreateTopLevelScopes();
 
+  ExpressionContext DetectExpressionContext(std::string &text);
+
   void RunLLDBCommands(llvm::StringRef prefix,
                        const std::vector<std::string> &commands);
 
Index: lldb/tools/lldb-vscode/VSCode.cpp
===================================================================
--- lldb/tools/lldb-vscode/VSCode.cpp
+++ lldb/tools/lldb-vscode/VSCode.cpp
@@ -44,7 +44,7 @@
       configuration_done_sent(false), waiting_for_run_in_terminal(false),
       progress_event_reporter(
           [&](const ProgressEvent &event) { SendJSON(event.ToJSON()); }),
-      reverse_request_seq(0) {
+      reverse_request_seq(0), repl_mode(ReplMode::Auto) {
   const char *log_file_path = getenv("LLDBVSCODE_LOG");
 #if defined(_WIN32)
   // Windows opens stdout and stdin in text mode which converts \n to 13,10
@@ -392,6 +392,33 @@
   return llvm::json::Value(std::move(scopes));
 }
 
+ExpressionContext VSCode::DetectExpressionContext(std::string &text) {
+  switch (repl_mode) {
+  case ReplMode::Variable:
+    if (!text.empty() && text[0] == '`') {
+      text = text.substr(1);
+      return ExpressionContext::Command;
+    }
+
+    return ExpressionContext::Variable;
+  case ReplMode::Command:
+    return ExpressionContext::Command;
+  case ReplMode::Auto:
+lldb::SBCommandReturnObject result;
+g_vsc.debugger.GetCommandInterpreter().ResolveCommand(text.data(), result);
+if (result.GetStatus() != lldb::eReturnStatusSuccessFinishResult) {
+  return ExpressionContext::Variable;
+}
+
+llvm::StringRef output = result.GetOutput();
+if (output.starts_with("frame variable") || output.starts_with("expression") || output.starts_with("dwim-print")) {
+  return ExpressionContext::Variable;
+}
+
+    return ExpressionContext::Command;
+  }
+}
+
 void VSCode::RunLLDBCommands(llvm::StringRef prefix,
                              const std::vector<std::string> &commands) {
   SendOutput(OutputType::Console,
@@ -636,7 +663,7 @@
   expandable_variables.clear();
 }
 
-int64_t Variables::GetNewVariableRefence(bool is_permanent) {
+int64_t Variables::GetNewVariableReference(bool is_permanent) {
   if (is_permanent)
     return next_permanent_var_ref++;
   return next_temporary_var_ref++;
@@ -661,7 +688,7 @@
 
 int64_t Variables::InsertExpandableVariable(lldb::SBValue variable,
                                             bool is_permanent) {
-  int64_t var_ref = GetNewVariableRefence(is_permanent);
+  int64_t var_ref = GetNewVariableReference(is_permanent);
   if (is_permanent)
     expandable_permanent_variables.insert(std::make_pair(var_ref, variable));
   else
@@ -728,4 +755,50 @@
   return true;
 }
 
+bool ReplModeRequestHandler::DoExecute(lldb::SBDebugger debugger,
+                                       char **command,
+                                       lldb::SBCommandReturnObject &result) {
+  // Command format like: `repl-mode <variable|command|auto>?`
+  if (!command || llvm::StringRef(command[0]).empty()) {
+    std::string mode = "lldb-vscode repl-mode ";
+    switch (g_vsc.repl_mode) {
+    case ReplMode::Variable:
+      mode += "variable";
+      break;
+    case ReplMode::Command:
+      mode += "command";
+      break;
+    case ReplMode::Auto:
+      mode += "auto";
+      break;
+    }
+
+    result.AppendMessage(mode.c_str());
+    result.SetStatus(lldb::eReturnStatusSuccessFinishResult);
+
+    return true;
+  }
+
+  llvm::StringRef new_mode{command[0]};
+
+  if (new_mode == "variable") {
+    g_vsc.repl_mode = ReplMode::Variable;
+    result.AppendMessage("lldb-vscode repl-mode variable set.");
+  } else if (new_mode == "command") {
+    g_vsc.repl_mode = ReplMode::Command;
+    result.AppendMessage("lldb-vscode repl-mode command set.");
+  } else if (new_mode == "auto") {
+    g_vsc.repl_mode = ReplMode::Auto;
+    result.AppendMessage("lldb-vscode repl-mode auto set.");
+  } else {
+    std::string error = "Unknown repl-mode '" + new_mode.str() + "'.";
+    result.SetError(error.c_str());
+    result.SetStatus(lldb::eReturnStatusFailed);
+    return false;
+  }
+
+  result.SetStatus(lldb::eReturnStatusSuccessFinishNoResult);
+  return true;
+}
+
 } // namespace lldb_vscode
Index: lldb/tools/lldb-vscode/SourceBreakpoint.cpp
===================================================================
--- lldb/tools/lldb-vscode/SourceBreakpoint.cpp
+++ lldb/tools/lldb-vscode/SourceBreakpoint.cpp
@@ -17,7 +17,8 @@
 
 void SourceBreakpoint::SetBreakpoint(const llvm::StringRef source_path) {
   lldb::SBFileSpecList module_list;
-  bp = g_vsc.target.BreakpointCreateByLocation(source_path.str().c_str(), line, column, 0, module_list);
+  bp = g_vsc.target.BreakpointCreateByLocation(source_path.str().c_str(), line,
+                                               column, 0, module_list);
   // See comments in BreakpointBase::GetBreakpointLabel() for details of why
   // we add a label to our breakpoints.
   bp.AddName(GetBreakpointLabel());
Index: lldb/tools/lldb-vscode/README.md
===================================================================
--- lldb/tools/lldb-vscode/README.md
+++ lldb/tools/lldb-vscode/README.md
@@ -236,3 +236,24 @@
   ]
 }
 ```
+
+## repl-mode
+
+Inspect or adjust the behavior of lldb-vscode repl evaluation requests. The
+supported modes are `variable`, `command` and `auto`.
+
+*  `variable` - Variable mode expressions are evaluated in the context of the
+   current frame. Use a `\`` prefix on the command to run an lldb command.
+*  `command` - Command mode expressions are evaluated as lldb commands, as a
+   result, values printed by lldb are always stringified representations of the
+   expression output.
+*  `auto` - Auto mode will attempt to infer if the expression represents an lldb
+   command or a variable expression. Due to the heuristic for determining the
+   expression, variables that have the same name as an lldb command will only be
+   accessible via an explicit expression command. For example, if the current
+   frame contains a variable `help` and a repl evaluation expression is only the
+   value `"help"` then the lldb help command will be invoked. To access the
+   local variable use `p help`, `po help` or `var help`.
+
+The initial repl-mode can be configured with the cli flag `--repl-mode=<mode>`
+and may also be adjusted at runtime using `lldb-vscode repl-mode <mode>`.
Index: lldb/tools/lldb-vscode/Options.td
===================================================================
--- lldb/tools/lldb-vscode/Options.td
+++ lldb/tools/lldb-vscode/Options.td
@@ -17,25 +17,29 @@
   Alias<wait_for_debugger>,
   HelpText<"Alias for --wait-for-debugger">;
 
-def port: Separate<["--", "-"], "port">,
+def port: S<"port">,
   MetaVarName<"<port>">,
   HelpText<"Communicate with the lldb-vscode tool over the defined port.">;
 def: Separate<["-"], "p">,
   Alias<port>,
   HelpText<"Alias for --port">;
 
-def launch_target: Separate<["--", "-"], "launch-target">,
+def launch_target: S<"launch-target">,
   MetaVarName<"<target>">,
   HelpText<"Launch a target for the launchInTerminal request. Any argument "
     "provided after this one will be passed to the target. The parameter "
     "--comm-file must also be specified.">;
 
-def comm_file: Separate<["--", "-"], "comm-file">,
+def comm_file: S<"comm-file">,
   MetaVarName<"<file>">,
   HelpText<"The fifo file used to communicate the with the debug adaptor "
     "when using --launch-target.">;
 
-def debugger_pid: Separate<["--", "-"], "debugger-pid">,
+def debugger_pid: S<"debugger-pid">,
   MetaVarName<"<pid>">,
   HelpText<"The PID of the lldb-vscode instance that sent the launchInTerminal "
     "request when using --launch-target.">;
+
+def repl_mode: S<"repl-mode">,
+  MetaVarName<"<mode>">,
+  HelpText<"The mode for handling repl evaluation requests, supported modes: variable, command, auto.">;
Index: lldb/tools/lldb-vscode/JSONUtils.h
===================================================================
--- lldb/tools/lldb-vscode/JSONUtils.h
+++ lldb/tools/lldb-vscode/JSONUtils.h
@@ -233,11 +233,10 @@
 ///     fallback.
 ///
 /// \param[in] request_column
-///     An optional column to use when creating the resulting "Breakpoint" object.
-///     It is used if the breakpoint has no valid locations.
-///     It is useful to ensure the same column
-///     provided by the setBreakpoints request are returned to the IDE as a
-///     fallback.
+///     An optional column to use when creating the resulting "Breakpoint"
+///     object. It is used if the breakpoint has no valid locations. It is
+///     useful to ensure the same column provided by the setBreakpoints request
+///     are returned to the IDE as a fallback.
 ///
 /// \return
 ///     A "Breakpoint" JSON object with that follows the formal JSON
Index: lldb/tools/lldb-vscode/JSONUtils.cpp
===================================================================
--- lldb/tools/lldb-vscode/JSONUtils.cpp
+++ lldb/tools/lldb-vscode/JSONUtils.cpp
@@ -907,6 +907,8 @@
       uint64_t bp_loc_id = thread.GetStopReasonDataAtIndex(1);
       snprintf(desc_str, sizeof(desc_str), "breakpoint %" PRIu64 ".%" PRIu64,
                bp_id, bp_loc_id);
+      body.try_emplace("hitBreakpointIds",
+                       llvm::json::Array{llvm::json::Value(bp_id)});
       EmplaceSafeString(body, "description", desc_str);
     }
   } break;
@@ -951,9 +953,6 @@
       EmplaceSafeString(body, "description", std::string(description));
     }
   }
-  if (tid == g_vsc.focus_tid) {
-    body.try_emplace("threadCausedFocus", true);
-  }
   body.try_emplace("preserveFocusHint", tid != g_vsc.focus_tid);
   body.try_emplace("allThreadsStopped", true);
   event.try_emplace("body", std::move(body));
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to