[Lldb-commits] [lldb] 2c7c528 - [lldb-vscode] support the completion request

Walter Erquinigo via lldb-commits lldb-commits at lists.llvm.org
Fri Nov 15 17:38:06 PST 2019


Author: Walter Erquinigo
Date: 2019-11-15T17:37:55-08:00
New Revision: 2c7c528d7ac17230f1f239b629a02d407a74e1bf

URL: https://github.com/llvm/llvm-project/commit/2c7c528d7ac17230f1f239b629a02d407a74e1bf
DIFF: https://github.com/llvm/llvm-project/commit/2c7c528d7ac17230f1f239b629a02d407a74e1bf.diff

LOG: [lldb-vscode] support the completion request

Summary:
The DAP has a completion request that has been unimplemented. It allows showing autocompletion tokens inside the Debug Console.
I implemented it in a very simple fashion mimicking what the user would see when autocompleting an expression inside the CLI.
There are two cases: normal variables and commands. The latter occurs when a text is prepepended with ` in the Debug Console.
These two cases work well and have tests.

Reviewers: clayborg, aadsm

Subscribers: lldb-commits

Tags: #lldb

Differential Revision: https://reviews.llvm.org/D69873

Added: 
    lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/completions/Makefile
    lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/completions/TestVSCode_completions.py
    lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/completions/main.cpp

Modified: 
    lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py
    lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/variables/TestVSCode_variables.py
    lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py
    lldb/tools/lldb-vscode/lldb-vscode.cpp

Removed: 
    


################################################################################
diff  --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/completions/Makefile b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/completions/Makefile
new file mode 100644
index 000000000000..99998b20bcb0
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/completions/Makefile
@@ -0,0 +1,3 @@
+CXX_SOURCES := main.cpp
+
+include Makefile.rules

diff  --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/completions/TestVSCode_completions.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/completions/TestVSCode_completions.py
new file mode 100644
index 000000000000..4500a8093928
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/completions/TestVSCode_completions.py
@@ -0,0 +1,117 @@
+"""
+Test lldb-vscode completions request
+"""
+
+from __future__ import print_function
+
+import lldbvscode_testcase
+import unittest2
+import vscode
+from lldbsuite.test import lldbutil
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+
+
+class TestVSCode_variables(lldbvscode_testcase.VSCodeTestCaseBase):
+
+    mydir = TestBase.compute_mydir(__file__)
+
+    def verify_completions(self, actual_list, expected_list, not_expected_list=[]):
+        for expected_item in expected_list:
+            self.assertTrue(expected_item in actual_list)
+
+        for not_expected_item in not_expected_list:
+            self.assertFalse(not_expected_item in actual_list)
+
+    @skipIfWindows
+    @skipIfDarwin # Skip this test for now until we can figure out why tings aren't working on build bots
+    @no_debug_info_test
+    def test_completions(self):
+        """
+            Tests the completion request at 
diff erent breakpoints
+        """
+        program = self.getBuildArtifact("a.out")
+        self.build_and_launch(program)
+        source = "main.cpp"
+        breakpoint1_line = line_number(source, "// breakpoint 1")
+        breakpoint2_line = line_number(source, "// breakpoint 2")
+        breakpoint_ids = self.set_source_breakpoints(
+            source, [breakpoint1_line, breakpoint2_line]
+        )
+        self.continue_to_next_stop()
+
+        # shouldn't see variables inside main
+        self.verify_completions(
+            self.vscode.get_completions("var"),
+            [
+                {
+                    "text": "var",
+                    "label": "var -- vector<basic_string<char, char_traits<char>, allocator<char> >, allocator<basic_string<char, char_traits<char>, allocator<char> > > > &",
+                }
+            ],
+            [{"text": "var1", "label": "var1 -- int &"}],
+        )
+
+        # should see global keywords but not variables inside main
+        self.verify_completions(
+            self.vscode.get_completions("str"),
+            [{"text": "struct", "label": "struct"}],
+            [{"text": "str1", "label": "str1 -- std::string &"}],
+        )
+
+        self.continue_to_next_stop()
+
+        # should see variables from main but not from the other function
+        self.verify_completions(
+            self.vscode.get_completions("var"),
+            [
+                {"text": "var1", "label": "var1 -- int &"},
+                {"text": "var2", "label": "var2 -- int &"},
+            ],
+            [
+                {
+                    "text": "var",
+                    "label": "var -- vector<basic_string<char, char_traits<char>, allocator<char> >, allocator<basic_string<char, char_traits<char>, allocator<char> > > > &",
+                }
+            ],
+        )
+
+        self.verify_completions(
+            self.vscode.get_completions("str"),
+            [
+                {"text": "struct", "label": "struct"},
+                {"text": "str1", "label": "str1 -- string &"},
+            ],
+        )
+
+        # should complete arbitrary commands including word starts
+        self.verify_completions(
+            self.vscode.get_completions("`log enable  "),
+            [{"text": "gdb-remote", "label": "gdb-remote"}],
+        )
+
+        # should complete expressions with quotes inside
+        self.verify_completions(
+            self.vscode.get_completions('`expr " "; typed'),
+            [{"text": "typedef", "label": "typedef"}],
+        )
+
+        # should complete an incomplete quoted token
+        self.verify_completions(
+            self.vscode.get_completions('`setting "se'),
+            [
+                {
+                    "text": "set",
+                    "label": "set -- Set the value of the specified debugger setting.",
+                }
+            ],
+        )
+        self.verify_completions(
+            self.vscode.get_completions("`'comm"),
+            [
+                {
+                    "text": "command",
+                    "label": "command -- Commands for managing custom LLDB commands.",
+                }
+            ],
+        )

diff  --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/completions/main.cpp b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/completions/main.cpp
new file mode 100644
index 000000000000..14a8815a5244
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/completions/main.cpp
@@ -0,0 +1,16 @@
+#include <string>
+#include <vector>
+
+int fun(std::vector<std::string> var) {
+  return var.size(); // breakpoint 1
+}
+
+int main(int argc, char const *argv[]) {
+  int var1 = 0;
+  int var2 = 1;
+  std::string str1 = "a";
+  std::string str2 = "b";
+  std::vector<std::string> vec;
+  fun(vec);
+  return 0; // breakpoint 2
+}

diff  --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py
index 04ca8d9b7552..b0f69b283fd5 100644
--- a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py
@@ -254,7 +254,7 @@ def launch(self, program=None, args=None, cwd=None, env=None,
         '''Sending launch request to vscode
         '''
 
-        # Make sure we disconnet and terminate the VSCode debug adaptor,
+        # Make sure we disconnect and terminate the VSCode debug adapter,
         # if we throw an exception during the test case
         def cleanup():
             self.vscode.request_disconnect(terminateDebuggee=True)

diff  --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/variables/TestVSCode_variables.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/variables/TestVSCode_variables.py
index 6d29e742564f..770d58dc7159 100644
--- a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/variables/TestVSCode_variables.py
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/variables/TestVSCode_variables.py
@@ -85,7 +85,7 @@ def test_scopes_variables_setVariable_evaluate(self):
         source = 'main.cpp'
         breakpoint1_line = line_number(source, '// breakpoint 1')
         lines = [breakpoint1_line]
-        # Set breakoint in the thread function so we can step the threads
+        # Set breakpoint in the thread function so we can step the threads
         breakpoint_ids = self.set_source_breakpoints(source, lines)
         self.assertTrue(len(breakpoint_ids) == len(lines),
                         "expect correct number of breakpoints")

diff  --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py
index b4e21989463c..1110ad36c0fd 100644
--- a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py
@@ -348,6 +348,10 @@ def get_stackFrame(self, frameIndex=0, threadId=None):
         print('invalid response')
         return None
 
+    def get_completions(self, text):
+        response = self.request_completions(text)
+        return response['body']['targets']
+
     def get_scope_variables(self, scope_name, frameIndex=0, threadId=None):
         stackFrame = self.get_stackFrame(frameIndex=frameIndex,
                                          threadId=threadId)
@@ -715,6 +719,18 @@ def request_setFunctionBreakpoints(self, names, condition=None,
         }
         return self.send_recv(command_dict)
 
+    def request_completions(self, text):
+        args_dict = {
+            'text': text,
+            'column': len(text)
+        }
+        command_dict = {
+            'command': 'completions',
+            'type': 'request',
+            'arguments': args_dict
+        }
+        return self.send_recv(command_dict)
+
     def request_stackTrace(self, threadId=None, startFrame=None, levels=None,
                            dump=False):
         if threadId is None:

diff  --git a/lldb/tools/lldb-vscode/lldb-vscode.cpp b/lldb/tools/lldb-vscode/lldb-vscode.cpp
index f39dbbf65e1f..db734add9b0e 100644
--- a/lldb/tools/lldb-vscode/lldb-vscode.cpp
+++ b/lldb/tools/lldb-vscode/lldb-vscode.cpp
@@ -821,6 +821,152 @@ void request_exceptionInfo(const llvm::json::Object &request) {
   g_vsc.SendJSON(llvm::json::Value(std::move(response)));
 }
 
+// "CompletionsRequest": {
+//   "allOf": [ { "$ref": "#/definitions/Request" }, {
+//     "type": "object",
+//     "description": "Returns a list of possible completions for a given caret position and text.\nThe CompletionsRequest may only be called if the 'supportsCompletionsRequest' capability exists and is true.",
+//     "properties": {
+//       "command": {
+//         "type": "string",
+//         "enum": [ "completions" ]
+//       },
+//       "arguments": {
+//         "$ref": "#/definitions/CompletionsArguments"
+//       }
+//     },
+//     "required": [ "command", "arguments"  ]
+//   }]
+// },
+// "CompletionsArguments": {
+//   "type": "object",
+//   "description": "Arguments for 'completions' request.",
+//   "properties": {
+//     "frameId": {
+//       "type": "integer",
+//       "description": "Returns completions in the scope of this stack frame. If not specified, the completions are returned for the global scope."
+//     },
+//     "text": {
+//       "type": "string",
+//       "description": "One or more source lines. Typically this is the text a user has typed into the debug console before he asked for completion."
+//     },
+//     "column": {
+//       "type": "integer",
+//       "description": "The character position for which to determine the completion proposals."
+//     },
+//     "line": {
+//       "type": "integer",
+//       "description": "An optional line for which to determine the completion proposals. If missing the first line of the text is assumed."
+//     }
+//   },
+//   "required": [ "text", "column" ]
+// },
+// "CompletionsResponse": {
+//   "allOf": [ { "$ref": "#/definitions/Response" }, {
+//     "type": "object",
+//     "description": "Response to 'completions' request.",
+//     "properties": {
+//       "body": {
+//         "type": "object",
+//         "properties": {
+//           "targets": {
+//             "type": "array",
+//             "items": {
+//               "$ref": "#/definitions/CompletionItem"
+//             },
+//             "description": "The possible completions for ."
+//           }
+//         },
+//         "required": [ "targets" ]
+//       }
+//     },
+//     "required": [ "body" ]
+//   }]
+// },
+// "CompletionItem": {
+//   "type": "object",
+//   "description": "CompletionItems are the suggestions returned from the CompletionsRequest.",
+//   "properties": {
+//     "label": {
+//       "type": "string",
+//       "description": "The label of this completion item. By default this is also the text that is inserted when selecting this completion."
+//     },
+//     "text": {
+//       "type": "string",
+//       "description": "If text is not falsy then it is inserted instead of the label."
+//     },
+//     "sortText": {
+//       "type": "string",
+//       "description": "A string that should be used when comparing this item with other items. When `falsy` the label is used."
+//     },
+//     "type": {
+//       "$ref": "#/definitions/CompletionItemType",
+//       "description": "The item's type. Typically the client uses this information to render the item in the UI with an icon."
+//     },
+//     "start": {
+//       "type": "integer",
+//       "description": "This value determines the location (in the CompletionsRequest's 'text' attribute) where the completion text is added.\nIf missing the text is added at the location specified by the CompletionsRequest's 'column' attribute."
+//     },
+//     "length": {
+//       "type": "integer",
+//       "description": "This value determines how many characters are overwritten by the completion text.\nIf missing the value 0 is assumed which results in the completion text being inserted."
+//     }
+//   },
+//   "required": [ "label" ]
+// },
+// "CompletionItemType": {
+//   "type": "string",
+//   "description": "Some predefined types for the CompletionItem. Please note that not all clients have specific icons for all of them.",
+//   "enum": [ "method", "function", "constructor", "field", "variable", "class", "interface", "module", "property", "unit", "value", "enum", "keyword", "snippet", "text", "color", "file", "reference", "customcolor" ]
+// }
+void request_completions(const llvm::json::Object &request) {
+  llvm::json::Object response;
+  FillResponse(request, response);
+  llvm::json::Object body;
+  auto arguments = request.getObject("arguments");
+  std::string text = GetString(arguments, "text");
+  auto original_column = GetSigned(arguments, "column", text.size());
+  auto actual_column = original_column - 1;
+  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 {
+    text = "p " + text;
+    actual_column += 2;
+  }
+  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)50, 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;
+    EmplaceSafeString(item, "text", match);
+    if (description.empty())
+      EmplaceSafeString(item, "label", match);
+    else
+      EmplaceSafeString(item, "label", match + " -- " + description);
+
+    targets.emplace_back(std::move(item));
+  }
+
+  body.try_emplace("targets", std::move(targets));
+  response.try_emplace("body", std::move(body));
+  g_vsc.SendJSON(llvm::json::Value(std::move(response)));
+}
+
 //  "EvaluateRequest": {
 //    "allOf": [ { "$ref": "#/definitions/Request" }, {
 //      "type": "object",
@@ -1107,7 +1253,7 @@ void request_initialize(const llvm::json::Object &request) {
   // The debug adapter supports the stepInTargetsRequest.
   body.try_emplace("supportsStepInTargetsRequest", false);
   // The debug adapter supports the completionsRequest.
-  body.try_emplace("supportsCompletionsRequest", false);
+  body.try_emplace("supportsCompletionsRequest", true);
   // The debug adapter supports the modules request.
   body.try_emplace("supportsModulesRequest", false);
   // The set of additional module information exposed by the debug adapter.
@@ -2556,6 +2702,7 @@ const std::map<std::string, RequestCallback> &GetRequestHandlers() {
   static std::map<std::string, RequestCallback> g_request_handlers = {
       // VSCode Debug Adaptor requests
       REQUEST_CALLBACK(attach),
+      REQUEST_CALLBACK(completions),
       REQUEST_CALLBACK(continue),
       REQUEST_CALLBACK(configurationDone),
       REQUEST_CALLBACK(disconnect),


        


More information about the lldb-commits mailing list