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
|
@ -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
|
||||
'''
|
||||
|
||||
# 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)
|
||||
|
|
|
@ -85,7 +85,7 @@ class TestVSCode_variables(lldbvscode_testcase.VSCodeTestCaseBase):
|
|||
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")
|
||||
|
|
|
@ -348,6 +348,10 @@ class DebugCommunication(object):
|
|||
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 @@ class DebugCommunication(object):
|
|||
}
|
||||
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:
|
||||
|
|
|
@ -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),
|
||||
|
|
Loading…
Reference in New Issue