//===----------------------------------------------------------------------===// // // 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 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 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_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 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, 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 &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