|
7 | 7 | //===----------------------------------------------------------------------===// |
8 | 8 |
|
9 | 9 | #include "DAP.h" |
10 | | -#include "JSONUtils.h" |
| 10 | +#include "LLDBUtils.h" |
11 | 11 | #include "Protocol/ProtocolRequests.h" |
12 | 12 | #include "Protocol/ProtocolTypes.h" |
13 | 13 | #include "RequestHandler.h" |
14 | 14 | #include "lldb/API/SBStringList.h" |
| 15 | +#include "llvm/Support/ConvertUTF.h" |
15 | 16 |
|
16 | 17 | using namespace llvm; |
17 | 18 | using namespace lldb_dap; |
| 19 | +using namespace lldb; |
18 | 20 | using namespace lldb_dap::protocol; |
19 | 21 |
|
20 | 22 | namespace lldb_dap { |
| 23 | +/// Gets the position in the UTF8 string where the specified line started. |
| 24 | +static size_t GetLineStartPos(StringRef text, uint32_t line) { |
| 25 | + if (line == 0) // Invalid line. |
| 26 | + return StringRef::npos; |
| 27 | + |
| 28 | + if (line == 1) |
| 29 | + return 0; |
| 30 | + |
| 31 | + uint32_t cur_line = 1; |
| 32 | + size_t pos = 0; |
| 33 | + |
| 34 | + while (cur_line < line) { |
| 35 | + const size_t new_line_pos = text.find('\n', pos); |
| 36 | + |
| 37 | + if (new_line_pos == StringRef::npos) |
| 38 | + return new_line_pos; |
| 39 | + |
| 40 | + pos = new_line_pos + 1; |
| 41 | + // text may end with a new line |
| 42 | + if (pos >= text.size()) |
| 43 | + return StringRef::npos; |
| 44 | + |
| 45 | + cur_line++; |
| 46 | + } |
| 47 | + |
| 48 | + assert(pos < text.size()); |
| 49 | + return pos; |
| 50 | +} |
| 51 | + |
| 52 | +static std::optional<size_t> GetCursorPos(StringRef text, uint32_t line, |
| 53 | + uint32_t utf16_codeunits) { |
| 54 | + if (text.empty()) |
| 55 | + return std::nullopt; |
| 56 | + |
| 57 | + const size_t line_start_pos = GetLineStartPos(text, line); |
| 58 | + if (line_start_pos == StringRef::npos) |
| 59 | + return std::nullopt; |
| 60 | + |
| 61 | + const StringRef completion_line = |
| 62 | + text.substr(line_start_pos, text.find('\n', line_start_pos)); |
| 63 | + if (completion_line.empty()) |
| 64 | + return std::nullopt; |
| 65 | + |
| 66 | + const std::optional<size_t> cursor_pos_opt = |
| 67 | + UTF16CodeunitToBytes(completion_line, utf16_codeunits); |
| 68 | + if (!cursor_pos_opt) |
| 69 | + return std::nullopt; |
| 70 | + |
| 71 | + const size_t cursor_pos = line_start_pos + *cursor_pos_opt; |
| 72 | + return cursor_pos; |
| 73 | +} |
21 | 74 |
|
22 | 75 | /// Returns a list of possible completions for a given caret position and text. |
23 | 76 | /// |
24 | 77 | /// Clients should only call this request if the corresponding capability |
25 | 78 | /// `supportsCompletionsRequest` is true. |
26 | 79 | Expected<CompletionsResponseBody> |
27 | 80 | CompletionsRequestHandler::Run(const CompletionsArguments &args) const { |
| 81 | + std::string text = args.text; |
| 82 | + const uint32_t line = args.line; |
| 83 | + // column starts at 1. |
| 84 | + const uint32_t utf16_codeunits = args.column - 1; |
| 85 | + |
| 86 | + const auto cursor_pos_opt = GetCursorPos(text, line, utf16_codeunits); |
| 87 | + if (!cursor_pos_opt) |
| 88 | + return CompletionsResponseBody{}; |
| 89 | + |
| 90 | + size_t cursor_pos = *cursor_pos_opt; |
| 91 | + |
28 | 92 | // If we have a frame, try to set the context for variable completions. |
29 | 93 | lldb::SBFrame frame = dap.GetLLDBFrame(args.frameId); |
30 | 94 | if (frame.IsValid()) { |
31 | | - frame.GetThread().GetProcess().SetSelectedThread(frame.GetThread()); |
32 | | - frame.GetThread().SetSelectedFrame(frame.GetFrameID()); |
33 | | - } |
34 | | - |
35 | | - std::string text = args.text; |
36 | | - auto original_column = args.column; |
37 | | - auto original_line = args.line; |
38 | | - auto offset = original_column - 1; |
39 | | - if (original_line > 1) { |
40 | | - SmallVector<StringRef, 2> lines; |
41 | | - StringRef(text).split(lines, '\n'); |
42 | | - for (int i = 0; i < original_line - 1; i++) { |
43 | | - offset += lines[i].size(); |
44 | | - } |
| 95 | + lldb::SBThread frame_thread = frame.GetThread(); |
| 96 | + frame_thread.GetProcess().SetSelectedThread(frame_thread); |
| 97 | + frame_thread.SetSelectedFrame(frame.GetFrameID()); |
45 | 98 | } |
46 | 99 |
|
47 | | - std::vector<CompletionItem> targets; |
48 | | - |
49 | | - bool had_escape_prefix = |
50 | | - StringRef(text).starts_with(dap.configuration.commandEscapePrefix); |
51 | | - ReplMode completion_mode = dap.DetectReplMode(frame, text, true); |
52 | | - |
53 | | - // Handle the offset change introduced by stripping out the |
| 100 | + const StringRef escape_prefix = dap.configuration.commandEscapePrefix; |
| 101 | + const bool had_escape_prefix = StringRef(text).starts_with(escape_prefix); |
| 102 | + const ReplMode repl_mode = dap.DetectReplMode(frame, text, true); |
| 103 | + // Handle the cursor_pos change introduced by stripping out the |
54 | 104 | // `command_escape_prefix`. |
55 | 105 | if (had_escape_prefix) { |
56 | | - if (offset < |
57 | | - static_cast<int64_t>(dap.configuration.commandEscapePrefix.size())) { |
58 | | - return CompletionsResponseBody{std::move(targets)}; |
59 | | - } |
60 | | - offset -= dap.configuration.commandEscapePrefix.size(); |
| 106 | + if (cursor_pos < escape_prefix.size()) |
| 107 | + return CompletionsResponseBody{}; |
| 108 | + |
| 109 | + cursor_pos -= escape_prefix.size(); |
61 | 110 | } |
62 | 111 |
|
63 | 112 | // While the user is typing then we likely have an incomplete input and cannot |
64 | 113 | // reliably determine the precise intent (command vs variable), try completing |
65 | 114 | // the text as both a command and variable expression, if applicable. |
66 | 115 | const std::string expr_prefix = "expression -- "; |
67 | | - std::array<std::tuple<ReplMode, std::string, uint64_t>, 2> exprs = { |
68 | | - {std::make_tuple(ReplMode::Command, text, offset), |
| 116 | + const std::array<std::tuple<ReplMode, std::string, uint64_t>, 2> exprs = { |
| 117 | + {std::make_tuple(ReplMode::Command, text, cursor_pos), |
69 | 118 | std::make_tuple(ReplMode::Variable, expr_prefix + text, |
70 | | - offset + expr_prefix.size())}}; |
| 119 | + cursor_pos + expr_prefix.size())}}; |
| 120 | + |
| 121 | + CompletionsResponseBody response; |
| 122 | + std::vector<CompletionItem> &targets = response.targets; |
| 123 | + lldb::SBCommandInterpreter interpreter = dap.debugger.GetCommandInterpreter(); |
71 | 124 | for (const auto &[mode, line, cursor] : exprs) { |
72 | | - if (completion_mode != ReplMode::Auto && completion_mode != mode) |
| 125 | + if (repl_mode != ReplMode::Auto && repl_mode != mode) |
73 | 126 | continue; |
74 | 127 |
|
75 | 128 | lldb::SBStringList matches; |
76 | 129 | lldb::SBStringList descriptions; |
77 | | - if (!dap.debugger.GetCommandInterpreter().HandleCompletionWithDescriptions( |
78 | | - line.c_str(), cursor, 0, 100, matches, descriptions)) |
| 130 | + if (!interpreter.HandleCompletionWithDescriptions( |
| 131 | + line.c_str(), cursor, 0, 50, matches, descriptions)) |
79 | 132 | continue; |
80 | 133 |
|
81 | 134 | // The first element is the common substring after the cursor position for |
82 | 135 | // all the matches. The rest of the elements are the matches so ignore the |
83 | 136 | // first result. |
84 | | - for (size_t i = 1; i < matches.GetSize(); i++) { |
85 | | - std::string match = matches.GetStringAtIndex(i); |
86 | | - std::string description = descriptions.GetStringAtIndex(i); |
| 137 | + for (uint32_t i = 1; i < matches.GetSize(); i++) { |
| 138 | + const StringRef match = matches.GetStringAtIndex(i); |
| 139 | + const StringRef description = descriptions.GetStringAtIndex(i); |
87 | 140 |
|
88 | | - CompletionItem item; |
89 | 141 | StringRef match_ref = match; |
90 | | - for (StringRef commit_point : {".", "->"}) { |
91 | | - if (match_ref.contains(commit_point)) { |
92 | | - match_ref = match_ref.rsplit(commit_point).second; |
| 142 | + for (const StringRef commit_point : {".", "->"}) { |
| 143 | + if (const size_t pos = match_ref.rfind(commit_point); |
| 144 | + pos != StringRef::npos) { |
| 145 | + match_ref = match_ref.substr(pos + commit_point.size()); |
93 | 146 | } |
94 | 147 | } |
95 | | - item.text = match_ref; |
96 | 148 |
|
97 | | - if (description.empty()) |
98 | | - item.label = match; |
99 | | - else |
100 | | - item.label = match + " -- " + description; |
| 149 | + CompletionItem item; |
| 150 | + item.text = match_ref; |
| 151 | + item.label = match; |
| 152 | + if (!description.empty()) |
| 153 | + item.detail = description; |
101 | 154 |
|
102 | 155 | targets.emplace_back(std::move(item)); |
103 | 156 | } |
104 | 157 | } |
105 | 158 |
|
106 | | - return CompletionsResponseBody{std::move(targets)}; |
| 159 | + return response; |
107 | 160 | } |
108 | 161 |
|
109 | 162 | } // namespace lldb_dap |
0 commit comments