
Summary: We still have some leftovers of the old completion API in the internals of LLDB that haven't been replaced by the new CompletionRequest. These leftovers are: * The return values (int/size_t) in all completion functions. * Our result array that starts indexing at 1. * `WordComplete` mode. I didn't replace them back then because it's tricky to figure out what exactly they are used for and the completion code is relatively untested. I finally got around to writing more tests for the API and understanding the semantics, so I think it's a good time to get rid of them. A few words why those things should be removed/replaced: * The return values are really cryptic, partly redundant and rarely documented. They are also completely ignored by Xcode, so whatever information they contain will end up breaking Xcode's completion mechanism. They are also partly impossible to even implement as we assign negative values special meaning and our completion API sometimes returns size_t. Completion functions are supposed to return -2 to rewrite the current line. We seem to use this in some untested code path to expand the history repeat character to the full command, but I haven't figured out why that doesn't work at the moment. Completion functions return -1 to 'insert the completion character', but that isn't implemented (even though we seem to activate this feature in LLDB sometimes). All positive values have to match the number of results. This is obviously just redundant information as the user can just look at the result list to get that information (which is what Xcode does). * The result array that starts indexing at 1 is obviously unexpected. The first element of the array is reserved for the common prefix of all completions (e.g. "foobar" and "footar" -> "foo"). The idea is that we calculate this to make the life of the API caller easier, but obviously forcing people to have 1-based indices is not helpful (or even worse, forces them to manually copy the results to make it 0-based like Xcode has to do). * The `WordComplete` mode indicates that LLDB should enter a space behind the completion. The idea is that we let the top-level API know that we just provided a full completion. Interestingly we `WordComplete` is just a single bool that somehow represents all N completions. And we always provide full completions in LLDB, so in theory it should always be true. The only use it currently serves is providing redundant information about whether we have a single definitive completion or not (which we already know from the number of results we get). This patch essentially removes `WordComplete` mode and makes the result array indexed from 0. It also removes all return values from all internal completion functions. The only non-redundant information they contain is about rewriting the current line (which is broken), so that functionality was moved to the CompletionRequest API. So you can now do `addCompletion("blub", "description", CompletionMode::RewriteLine)` to do the same. For the SB API we emulate the old behaviour by making the array indexed from 1 again with the common prefix at index 0. I didn't keep the special negative return codes as we either never sent them before (e.g. -2) or we didn't even implement them in the Editline handler (e.g. -1). I tried to keep this patch minimal and I'm aware we can probably now even further simplify a bunch of related code, but I would prefer doing this in follow-up NFC commits Reviewers: JDevlieghere Reviewed By: JDevlieghere Subscribers: arphaman, abidh, lldb-commits Tags: #lldb Differential Revision: https://reviews.llvm.org/D66536 llvm-svn: 369624
225 lines
8.7 KiB
C++
225 lines
8.7 KiB
C++
//===-- CommandObjectHelp.cpp -----------------------------------*- 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 "CommandObjectHelp.h"
|
|
#include "lldb/Interpreter/CommandInterpreter.h"
|
|
#include "lldb/Interpreter/CommandObjectMultiword.h"
|
|
#include "lldb/Interpreter/CommandReturnObject.h"
|
|
#include "lldb/Interpreter/Options.h"
|
|
|
|
using namespace lldb;
|
|
using namespace lldb_private;
|
|
|
|
// CommandObjectHelp
|
|
|
|
void CommandObjectHelp::GenerateAdditionalHelpAvenuesMessage(
|
|
Stream *s, llvm::StringRef command, llvm::StringRef prefix,
|
|
llvm::StringRef subcommand, bool include_upropos,
|
|
bool include_type_lookup) {
|
|
if (!s || command.empty())
|
|
return;
|
|
|
|
std::string command_str = command.str();
|
|
std::string prefix_str = prefix.str();
|
|
std::string subcommand_str = subcommand.str();
|
|
const std::string &lookup_str = !subcommand_str.empty() ? subcommand_str : command_str;
|
|
s->Printf("'%s' is not a known command.\n", command_str.c_str());
|
|
s->Printf("Try '%shelp' to see a current list of commands.\n",
|
|
prefix.str().c_str());
|
|
if (include_upropos) {
|
|
s->Printf("Try '%sapropos %s' for a list of related commands.\n",
|
|
prefix_str.c_str(), lookup_str.c_str());
|
|
}
|
|
if (include_type_lookup) {
|
|
s->Printf("Try '%stype lookup %s' for information on types, methods, "
|
|
"functions, modules, etc.",
|
|
prefix_str.c_str(), lookup_str.c_str());
|
|
}
|
|
}
|
|
|
|
CommandObjectHelp::CommandObjectHelp(CommandInterpreter &interpreter)
|
|
: CommandObjectParsed(interpreter, "help", "Show a list of all debugger "
|
|
"commands, or give details "
|
|
"about a specific command.",
|
|
"help [<cmd-name>]"),
|
|
m_options() {
|
|
CommandArgumentEntry arg;
|
|
CommandArgumentData command_arg;
|
|
|
|
// Define the first (and only) variant of this arg.
|
|
command_arg.arg_type = eArgTypeCommandName;
|
|
command_arg.arg_repetition = eArgRepeatStar;
|
|
|
|
// There is only one variant this argument could be; put it into the argument
|
|
// entry.
|
|
arg.push_back(command_arg);
|
|
|
|
// Push the data for the first argument into the m_arguments vector.
|
|
m_arguments.push_back(arg);
|
|
}
|
|
|
|
CommandObjectHelp::~CommandObjectHelp() = default;
|
|
|
|
#define LLDB_OPTIONS_help
|
|
#include "CommandOptions.inc"
|
|
|
|
llvm::ArrayRef<OptionDefinition>
|
|
CommandObjectHelp::CommandOptions::GetDefinitions() {
|
|
return llvm::makeArrayRef(g_help_options);
|
|
}
|
|
|
|
bool CommandObjectHelp::DoExecute(Args &command, CommandReturnObject &result) {
|
|
CommandObject::CommandMap::iterator pos;
|
|
CommandObject *cmd_obj;
|
|
const size_t argc = command.GetArgumentCount();
|
|
|
|
// 'help' doesn't take any arguments, other than command names. If argc is
|
|
// 0, we show the user all commands (aliases and user commands if asked for).
|
|
// Otherwise every argument must be the name of a command or a sub-command.
|
|
if (argc == 0) {
|
|
uint32_t cmd_types = CommandInterpreter::eCommandTypesBuiltin;
|
|
if (m_options.m_show_aliases)
|
|
cmd_types |= CommandInterpreter::eCommandTypesAliases;
|
|
if (m_options.m_show_user_defined)
|
|
cmd_types |= CommandInterpreter::eCommandTypesUserDef;
|
|
if (m_options.m_show_hidden)
|
|
cmd_types |= CommandInterpreter::eCommandTypesHidden;
|
|
|
|
result.SetStatus(eReturnStatusSuccessFinishNoResult);
|
|
m_interpreter.GetHelp(result, cmd_types); // General help
|
|
} else {
|
|
// Get command object for the first command argument. Only search built-in
|
|
// command dictionary.
|
|
StringList matches;
|
|
auto command_name = command[0].ref;
|
|
cmd_obj = m_interpreter.GetCommandObject(command_name, &matches);
|
|
|
|
if (cmd_obj != nullptr) {
|
|
StringList matches;
|
|
bool all_okay = true;
|
|
CommandObject *sub_cmd_obj = cmd_obj;
|
|
// Loop down through sub_command dictionaries until we find the command
|
|
// object that corresponds to the help command entered.
|
|
std::string sub_command;
|
|
for (auto &entry : command.entries().drop_front()) {
|
|
sub_command = entry.ref;
|
|
matches.Clear();
|
|
if (sub_cmd_obj->IsAlias())
|
|
sub_cmd_obj =
|
|
((CommandAlias *)sub_cmd_obj)->GetUnderlyingCommand().get();
|
|
if (!sub_cmd_obj->IsMultiwordObject()) {
|
|
all_okay = false;
|
|
break;
|
|
} else {
|
|
CommandObject *found_cmd;
|
|
found_cmd =
|
|
sub_cmd_obj->GetSubcommandObject(sub_command.c_str(), &matches);
|
|
if (found_cmd == nullptr || matches.GetSize() > 1) {
|
|
all_okay = false;
|
|
break;
|
|
} else
|
|
sub_cmd_obj = found_cmd;
|
|
}
|
|
}
|
|
|
|
if (!all_okay || (sub_cmd_obj == nullptr)) {
|
|
std::string cmd_string;
|
|
command.GetCommandString(cmd_string);
|
|
if (matches.GetSize() >= 2) {
|
|
StreamString s;
|
|
s.Printf("ambiguous command %s", cmd_string.c_str());
|
|
size_t num_matches = matches.GetSize();
|
|
for (size_t match_idx = 0; match_idx < num_matches; match_idx++) {
|
|
s.Printf("\n\t%s", matches.GetStringAtIndex(match_idx));
|
|
}
|
|
s.Printf("\n");
|
|
result.AppendError(s.GetString());
|
|
result.SetStatus(eReturnStatusFailed);
|
|
return false;
|
|
} else if (!sub_cmd_obj) {
|
|
StreamString error_msg_stream;
|
|
GenerateAdditionalHelpAvenuesMessage(
|
|
&error_msg_stream, cmd_string.c_str(),
|
|
m_interpreter.GetCommandPrefix(), sub_command.c_str());
|
|
result.AppendError(error_msg_stream.GetString());
|
|
result.SetStatus(eReturnStatusFailed);
|
|
return false;
|
|
} else {
|
|
GenerateAdditionalHelpAvenuesMessage(
|
|
&result.GetOutputStream(), cmd_string.c_str(),
|
|
m_interpreter.GetCommandPrefix(), sub_command.c_str());
|
|
result.GetOutputStream().Printf(
|
|
"\nThe closest match is '%s'. Help on it follows.\n\n",
|
|
sub_cmd_obj->GetCommandName().str().c_str());
|
|
}
|
|
}
|
|
|
|
sub_cmd_obj->GenerateHelpText(result);
|
|
std::string alias_full_name;
|
|
// Don't use AliasExists here, that only checks exact name matches. If
|
|
// the user typed a shorter unique alias name, we should still tell them
|
|
// it was an alias.
|
|
if (m_interpreter.GetAliasFullName(command_name, alias_full_name)) {
|
|
StreamString sstr;
|
|
m_interpreter.GetAlias(alias_full_name)->GetAliasExpansion(sstr);
|
|
result.GetOutputStream().Printf("\n'%s' is an abbreviation for %s\n",
|
|
command[0].c_str(), sstr.GetData());
|
|
}
|
|
} else if (matches.GetSize() > 0) {
|
|
Stream &output_strm = result.GetOutputStream();
|
|
output_strm.Printf("Help requested with ambiguous command name, possible "
|
|
"completions:\n");
|
|
const size_t match_count = matches.GetSize();
|
|
for (size_t i = 0; i < match_count; i++) {
|
|
output_strm.Printf("\t%s\n", matches.GetStringAtIndex(i));
|
|
}
|
|
} else {
|
|
// Maybe the user is asking for help about a command argument rather than
|
|
// a command.
|
|
const CommandArgumentType arg_type =
|
|
CommandObject::LookupArgumentName(command_name);
|
|
if (arg_type != eArgTypeLastArg) {
|
|
Stream &output_strm = result.GetOutputStream();
|
|
CommandObject::GetArgumentHelp(output_strm, arg_type, m_interpreter);
|
|
result.SetStatus(eReturnStatusSuccessFinishNoResult);
|
|
} else {
|
|
StreamString error_msg_stream;
|
|
GenerateAdditionalHelpAvenuesMessage(&error_msg_stream, command_name,
|
|
m_interpreter.GetCommandPrefix(),
|
|
"");
|
|
result.AppendError(error_msg_stream.GetString());
|
|
result.SetStatus(eReturnStatusFailed);
|
|
}
|
|
}
|
|
}
|
|
|
|
return result.Succeeded();
|
|
}
|
|
|
|
void CommandObjectHelp::HandleCompletion(CompletionRequest &request) {
|
|
// Return the completions of the commands in the help system:
|
|
if (request.GetCursorIndex() == 0) {
|
|
m_interpreter.HandleCompletionMatches(request);
|
|
} else {
|
|
CommandObject *cmd_obj =
|
|
m_interpreter.GetCommandObject(request.GetParsedLine()[0].ref);
|
|
|
|
// The command that they are getting help on might be ambiguous, in which
|
|
// case we should complete that, otherwise complete with the command the
|
|
// user is getting help on...
|
|
|
|
if (cmd_obj) {
|
|
request.GetParsedLine().Shift();
|
|
request.SetCursorIndex(request.GetCursorIndex() - 1);
|
|
cmd_obj->HandleCompletion(request);
|
|
} else {
|
|
m_interpreter.HandleCompletionMatches(request);
|
|
}
|
|
}
|
|
}
|