I noticed recently that CompletionsHandler.cpp and BreakpointLocationsHandler.cpp do not follow the same naming pattern as the rest of the request handlers. Renaming to include `Request` for consistency.
172 lines
5.7 KiB
C++
172 lines
5.7 KiB
C++
//===----------------------------------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "DAP.h"
|
|
#include "LLDBUtils.h"
|
|
#include "Protocol/ProtocolRequests.h"
|
|
#include "Protocol/ProtocolTypes.h"
|
|
#include "RequestHandler.h"
|
|
#include "lldb/API/SBStringList.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
#include "llvm/Support/ConvertUTF.h"
|
|
|
|
using namespace llvm;
|
|
using namespace lldb_dap;
|
|
using namespace lldb;
|
|
using namespace lldb_dap::protocol;
|
|
|
|
namespace lldb_dap {
|
|
/// Gets the position in the UTF8 string where the specified line started.
|
|
static size_t GetLineStartPos(StringRef text, uint32_t line) {
|
|
if (line == 0) // Invalid line.
|
|
return StringRef::npos;
|
|
|
|
if (line == 1)
|
|
return 0;
|
|
|
|
uint32_t cur_line = 1;
|
|
size_t pos = 0;
|
|
|
|
while (cur_line < line) {
|
|
const size_t new_line_pos = text.find('\n', pos);
|
|
|
|
if (new_line_pos == StringRef::npos)
|
|
return new_line_pos;
|
|
|
|
pos = new_line_pos + 1;
|
|
// text may end with a new line
|
|
if (pos >= text.size())
|
|
return StringRef::npos;
|
|
|
|
cur_line++;
|
|
}
|
|
|
|
assert(pos < text.size());
|
|
return pos;
|
|
}
|
|
|
|
static std::optional<size_t> GetCursorPos(StringRef text, uint32_t line,
|
|
uint32_t utf16_codeunits) {
|
|
if (text.empty())
|
|
return std::nullopt;
|
|
|
|
const size_t line_start_pos = GetLineStartPos(text, line);
|
|
if (line_start_pos == StringRef::npos)
|
|
return std::nullopt;
|
|
|
|
const StringRef completion_line =
|
|
text.substr(line_start_pos, text.find('\n', line_start_pos));
|
|
if (completion_line.empty())
|
|
return std::nullopt;
|
|
|
|
const std::optional<size_t> cursor_pos_opt =
|
|
UTF16CodeunitToBytes(completion_line, utf16_codeunits);
|
|
if (!cursor_pos_opt)
|
|
return std::nullopt;
|
|
|
|
const size_t cursor_pos = line_start_pos + *cursor_pos_opt;
|
|
return cursor_pos;
|
|
}
|
|
|
|
static size_t GetPartialTokenCodeUnits(StringRef line, size_t cursor_pos) {
|
|
line = line.substr(0, cursor_pos);
|
|
const size_t idx = line.rfind(' ');
|
|
|
|
const size_t byte_offset = (idx == StringRef::npos) ? 0 : idx + 1;
|
|
const StringRef byte_token = line.substr(byte_offset);
|
|
SmallVector<UTF16, 20> utf16_token;
|
|
|
|
if (convertUTF8ToUTF16String(byte_token, utf16_token))
|
|
return utf16_token.size();
|
|
return byte_token.size(); // fallback back to byte offset.
|
|
}
|
|
|
|
/// Returns a list of possible completions for a given caret position and text.
|
|
///
|
|
/// Clients should only call this request if the corresponding capability
|
|
/// `supportsCompletionsRequest` is true.
|
|
Expected<CompletionsResponseBody>
|
|
CompletionsRequestHandler::Run(const CompletionsArguments &args) const {
|
|
std::string text = args.text;
|
|
const uint32_t line = args.line;
|
|
// column starts at 1.
|
|
const uint32_t utf16_codeunits = args.column - 1;
|
|
|
|
const auto cursor_pos_opt = GetCursorPos(text, line, utf16_codeunits);
|
|
if (!cursor_pos_opt)
|
|
return CompletionsResponseBody{};
|
|
|
|
size_t cursor_pos = *cursor_pos_opt;
|
|
|
|
// If we have a frame, try to set the context for variable completions.
|
|
lldb::SBFrame frame = dap.GetLLDBFrame(args.frameId);
|
|
if (frame.IsValid()) {
|
|
lldb::SBThread frame_thread = frame.GetThread();
|
|
frame_thread.GetProcess().SetSelectedThread(frame_thread);
|
|
frame_thread.SetSelectedFrame(frame.GetFrameID());
|
|
}
|
|
|
|
const StringRef escape_prefix = dap.configuration.commandEscapePrefix;
|
|
const bool had_escape_prefix = StringRef(text).starts_with(escape_prefix);
|
|
const ReplMode repl_mode = dap.DetectReplMode(frame, text, true);
|
|
// Handle the cursor_pos change introduced by stripping out the
|
|
// `command_escape_prefix`.
|
|
if (had_escape_prefix) {
|
|
if (cursor_pos < escape_prefix.size())
|
|
return CompletionsResponseBody{};
|
|
|
|
cursor_pos -= escape_prefix.size();
|
|
}
|
|
|
|
const size_t partial_token_cu = GetPartialTokenCodeUnits(text, cursor_pos);
|
|
// While the user is typing then we likely have an incomplete input and cannot
|
|
// reliably determine the precise intent (command vs variable), try completing
|
|
// the text as both a command and variable expression, if applicable.
|
|
const std::string expr_prefix = "expression -- ";
|
|
const std::array<std::tuple<ReplMode, std::string, uint64_t>, 2> exprs = {
|
|
{std::make_tuple(ReplMode::Command, text, cursor_pos),
|
|
std::make_tuple(ReplMode::Variable, expr_prefix + text,
|
|
cursor_pos + expr_prefix.size())}};
|
|
|
|
CompletionsResponseBody response;
|
|
std::vector<CompletionItem> &targets = response.targets;
|
|
lldb::SBCommandInterpreter interpreter = dap.debugger.GetCommandInterpreter();
|
|
for (const auto &[mode, line, cursor] : exprs) {
|
|
if (repl_mode != ReplMode::Auto && repl_mode != mode)
|
|
continue;
|
|
|
|
lldb::SBStringList matches;
|
|
lldb::SBStringList descriptions;
|
|
if (!interpreter.HandleCompletionWithDescriptions(
|
|
line.c_str(), cursor, 0, 50, matches, descriptions))
|
|
continue;
|
|
|
|
// The first element is the common substring after the cursor position for
|
|
// all the matches. The rest of the elements are the matches so ignore the
|
|
// first result.
|
|
for (uint32_t i = 1; i < matches.GetSize(); i++) {
|
|
const StringRef match = matches.GetStringAtIndex(i);
|
|
const StringRef description = descriptions.GetStringAtIndex(i);
|
|
|
|
CompletionItem item;
|
|
item.label = match;
|
|
if (!description.empty())
|
|
item.detail = description;
|
|
// lldb returned completions includes the partial typed token
|
|
// overwrite it.
|
|
item.length = partial_token_cu;
|
|
|
|
targets.emplace_back(std::move(item));
|
|
}
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
} // namespace lldb_dap
|