forked from OSchip/llvm-project
[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
This commit is contained in:
parent
979da9a4c3
commit
2c7c528d7a
lldb
packages/Python/lldbsuite/test/tools/lldb-vscode
tools/lldb-vscode
|
@ -0,0 +1,3 @@
|
||||||
|
CXX_SOURCES := main.cpp
|
||||||
|
|
||||||
|
include Makefile.rules
|
|
@ -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 different 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.",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
)
|
|
@ -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
|
||||||
|
}
|
|
@ -254,7 +254,7 @@ class VSCodeTestCaseBase(TestBase):
|
||||||
'''Sending launch request to vscode
|
'''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
|
# if we throw an exception during the test case
|
||||||
def cleanup():
|
def cleanup():
|
||||||
self.vscode.request_disconnect(terminateDebuggee=True)
|
self.vscode.request_disconnect(terminateDebuggee=True)
|
||||||
|
|
|
@ -85,7 +85,7 @@ class TestVSCode_variables(lldbvscode_testcase.VSCodeTestCaseBase):
|
||||||
source = 'main.cpp'
|
source = 'main.cpp'
|
||||||
breakpoint1_line = line_number(source, '// breakpoint 1')
|
breakpoint1_line = line_number(source, '// breakpoint 1')
|
||||||
lines = [breakpoint1_line]
|
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)
|
breakpoint_ids = self.set_source_breakpoints(source, lines)
|
||||||
self.assertTrue(len(breakpoint_ids) == len(lines),
|
self.assertTrue(len(breakpoint_ids) == len(lines),
|
||||||
"expect correct number of breakpoints")
|
"expect correct number of breakpoints")
|
||||||
|
|
|
@ -348,6 +348,10 @@ class DebugCommunication(object):
|
||||||
print('invalid response')
|
print('invalid response')
|
||||||
return None
|
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):
|
def get_scope_variables(self, scope_name, frameIndex=0, threadId=None):
|
||||||
stackFrame = self.get_stackFrame(frameIndex=frameIndex,
|
stackFrame = self.get_stackFrame(frameIndex=frameIndex,
|
||||||
threadId=threadId)
|
threadId=threadId)
|
||||||
|
@ -715,6 +719,18 @@ class DebugCommunication(object):
|
||||||
}
|
}
|
||||||
return self.send_recv(command_dict)
|
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,
|
def request_stackTrace(self, threadId=None, startFrame=None, levels=None,
|
||||||
dump=False):
|
dump=False):
|
||||||
if threadId is None:
|
if threadId is None:
|
||||||
|
|
|
@ -821,6 +821,152 @@ void request_exceptionInfo(const llvm::json::Object &request) {
|
||||||
g_vsc.SendJSON(llvm::json::Value(std::move(response)));
|
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": {
|
// "EvaluateRequest": {
|
||||||
// "allOf": [ { "$ref": "#/definitions/Request" }, {
|
// "allOf": [ { "$ref": "#/definitions/Request" }, {
|
||||||
// "type": "object",
|
// "type": "object",
|
||||||
|
@ -1107,7 +1253,7 @@ void request_initialize(const llvm::json::Object &request) {
|
||||||
// The debug adapter supports the stepInTargetsRequest.
|
// The debug adapter supports the stepInTargetsRequest.
|
||||||
body.try_emplace("supportsStepInTargetsRequest", false);
|
body.try_emplace("supportsStepInTargetsRequest", false);
|
||||||
// The debug adapter supports the completionsRequest.
|
// The debug adapter supports the completionsRequest.
|
||||||
body.try_emplace("supportsCompletionsRequest", false);
|
body.try_emplace("supportsCompletionsRequest", true);
|
||||||
// The debug adapter supports the modules request.
|
// The debug adapter supports the modules request.
|
||||||
body.try_emplace("supportsModulesRequest", false);
|
body.try_emplace("supportsModulesRequest", false);
|
||||||
// The set of additional module information exposed by the debug adapter.
|
// 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 = {
|
static std::map<std::string, RequestCallback> g_request_handlers = {
|
||||||
// VSCode Debug Adaptor requests
|
// VSCode Debug Adaptor requests
|
||||||
REQUEST_CALLBACK(attach),
|
REQUEST_CALLBACK(attach),
|
||||||
|
REQUEST_CALLBACK(completions),
|
||||||
REQUEST_CALLBACK(continue),
|
REQUEST_CALLBACK(continue),
|
||||||
REQUEST_CALLBACK(configurationDone),
|
REQUEST_CALLBACK(configurationDone),
|
||||||
REQUEST_CALLBACK(disconnect),
|
REQUEST_CALLBACK(disconnect),
|
||||||
|
|
Loading…
Reference in New Issue