1//===-- CompletionsHandler.cpp --------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "DAP.h"
10#include "JSONUtils.h"
11#include "RequestHandler.h"
12#include "lldb/API/SBStringList.h"
13
14namespace lldb_dap {
15
16// "CompletionsRequest": {
17// "allOf": [ { "$ref": "#/definitions/Request" }, {
18// "type": "object",
19// "description": "Returns a list of possible completions for a given caret
20// position and text.\nThe CompletionsRequest may only be called if the
21// 'supportsCompletionsRequest' capability exists and is true.",
22// "properties": {
23// "command": {
24// "type": "string",
25// "enum": [ "completions" ]
26// },
27// "arguments": {
28// "$ref": "#/definitions/CompletionsArguments"
29// }
30// },
31// "required": [ "command", "arguments" ]
32// }]
33// },
34// "CompletionsArguments": {
35// "type": "object",
36// "description": "Arguments for 'completions' request.",
37// "properties": {
38// "frameId": {
39// "type": "integer",
40// "description": "Returns completions in the scope of this stack frame.
41// If not specified, the completions are returned for the global scope."
42// },
43// "text": {
44// "type": "string",
45// "description": "One or more source lines. Typically this is the text a
46// user has typed into the debug console before he asked for completion."
47// },
48// "column": {
49// "type": "integer",
50// "description": "The character position for which to determine the
51// completion proposals."
52// },
53// "line": {
54// "type": "integer",
55// "description": "An optional line for which to determine the completion
56// proposals. If missing the first line of the text is assumed."
57// }
58// },
59// "required": [ "text", "column" ]
60// },
61// "CompletionsResponse": {
62// "allOf": [ { "$ref": "#/definitions/Response" }, {
63// "type": "object",
64// "description": "Response to 'completions' request.",
65// "properties": {
66// "body": {
67// "type": "object",
68// "properties": {
69// "targets": {
70// "type": "array",
71// "items": {
72// "$ref": "#/definitions/CompletionItem"
73// },
74// "description": "The possible completions for ."
75// }
76// },
77// "required": [ "targets" ]
78// }
79// },
80// "required": [ "body" ]
81// }]
82// },
83// "CompletionItem": {
84// "type": "object",
85// "description": "CompletionItems are the suggestions returned from the
86// CompletionsRequest.", "properties": {
87// "label": {
88// "type": "string",
89// "description": "The label of this completion item. By default this is
90// also the text that is inserted when selecting this completion."
91// },
92// "text": {
93// "type": "string",
94// "description": "If text is not falsy then it is inserted instead of the
95// label."
96// },
97// "sortText": {
98// "type": "string",
99// "description": "A string that should be used when comparing this item
100// with other items. When `falsy` the label is used."
101// },
102// "type": {
103// "$ref": "#/definitions/CompletionItemType",
104// "description": "The item's type. Typically the client uses this
105// information to render the item in the UI with an icon."
106// },
107// "start": {
108// "type": "integer",
109// "description": "This value determines the location (in the
110// CompletionsRequest's 'text' attribute) where the completion text is
111// added.\nIf missing the text is added at the location specified by the
112// CompletionsRequest's 'column' attribute."
113// },
114// "length": {
115// "type": "integer",
116// "description": "This value determines how many characters are
117// overwritten by the completion text.\nIf missing the value 0 is assumed
118// which results in the completion text being inserted."
119// }
120// },
121// "required": [ "label" ]
122// },
123// "CompletionItemType": {
124// "type": "string",
125// "description": "Some predefined types for the CompletionItem. Please note
126// that not all clients have specific icons for all of them.", "enum": [
127// "method", "function", "constructor", "field", "variable", "class",
128// "interface", "module", "property", "unit", "value", "enum", "keyword",
129// "snippet", "text", "color", "file", "reference", "customcolor" ]
130// }
131void CompletionsRequestHandler::operator()(
132 const llvm::json::Object &request) const {
133 llvm::json::Object response;
134 FillResponse(request, response);
135 llvm::json::Object body;
136 const auto *arguments = request.getObject(K: "arguments");
137
138 // If we have a frame, try to set the context for variable completions.
139 lldb::SBFrame frame = dap.GetLLDBFrame(arguments: *arguments);
140 if (frame.IsValid()) {
141 frame.GetThread().GetProcess().SetSelectedThread(frame.GetThread());
142 frame.GetThread().SetSelectedFrame(frame.GetFrameID());
143 }
144
145 std::string text = GetString(obj: arguments, key: "text").value_or(u: "").str();
146 auto original_column =
147 GetInteger<int64_t>(obj: arguments, key: "column").value_or(u: text.size());
148 auto original_line = GetInteger<int64_t>(obj: arguments, key: "line").value_or(u: 1);
149 auto offset = original_column - 1;
150 if (original_line > 1) {
151 llvm::SmallVector<::llvm::StringRef, 2> lines;
152 llvm::StringRef(text).split(A&: lines, Separator: '\n');
153 for (int i = 0; i < original_line - 1; i++) {
154 offset += lines[i].size();
155 }
156 }
157 llvm::json::Array targets;
158
159 bool had_escape_prefix =
160 llvm::StringRef(text).starts_with(Prefix: dap.configuration.commandEscapePrefix);
161 ReplMode completion_mode = dap.DetectReplMode(frame, expression&: text, partial_expression: true);
162
163 // Handle the offset change introduced by stripping out the
164 // `command_escape_prefix`.
165 if (had_escape_prefix) {
166 if (offset <
167 static_cast<int64_t>(dap.configuration.commandEscapePrefix.size())) {
168 body.try_emplace(K: "targets", Args: std::move(targets));
169 response.try_emplace(K: "body", Args: std::move(body));
170 dap.SendJSON(json: llvm::json::Value(std::move(response)));
171 return;
172 }
173 offset -= dap.configuration.commandEscapePrefix.size();
174 }
175
176 // While the user is typing then we likely have an incomplete input and cannot
177 // reliably determine the precise intent (command vs variable), try completing
178 // the text as both a command and variable expression, if applicable.
179 const std::string expr_prefix = "expression -- ";
180 std::array<std::tuple<ReplMode, std::string, uint64_t>, 2> exprs = {
181 ._M_elems: {std::make_tuple(args: ReplMode::Command, args&: text, args&: offset),
182 std::make_tuple(args: ReplMode::Variable, args: expr_prefix + text,
183 args: offset + expr_prefix.size())}};
184 for (const auto &[mode, line, cursor] : exprs) {
185 if (completion_mode != ReplMode::Auto && completion_mode != mode)
186 continue;
187
188 lldb::SBStringList matches;
189 lldb::SBStringList descriptions;
190 if (!dap.debugger.GetCommandInterpreter().HandleCompletionWithDescriptions(
191 current_line: line.c_str(), cursor_pos: cursor, match_start_point: 0, max_return_elements: 100, matches, descriptions))
192 continue;
193
194 // The first element is the common substring after the cursor position for
195 // all the matches. The rest of the elements are the matches so ignore the
196 // first result.
197 for (size_t i = 1; i < matches.GetSize(); i++) {
198 std::string match = matches.GetStringAtIndex(idx: i);
199 std::string description = descriptions.GetStringAtIndex(idx: i);
200
201 llvm::json::Object item;
202 llvm::StringRef match_ref = match;
203 for (llvm::StringRef commit_point : {".", "->"}) {
204 if (match_ref.contains(Other: commit_point)) {
205 match_ref = match_ref.rsplit(Separator: commit_point).second;
206 }
207 }
208 EmplaceSafeString(obj&: item, key: "text", str: match_ref);
209
210 if (description.empty())
211 EmplaceSafeString(obj&: item, key: "label", str: match);
212 else
213 EmplaceSafeString(obj&: item, key: "label", str: match + " -- " + description);
214
215 targets.emplace_back(A: std::move(item));
216 }
217 }
218
219 body.try_emplace(K: "targets", Args: std::move(targets));
220 response.try_emplace(K: "body", Args: std::move(body));
221 dap.SendJSON(json: llvm::json::Value(std::move(response)));
222}
223
224} // namespace lldb_dap
225

source code of lldb/tools/lldb-dap/Handler/CompletionsHandler.cpp