mirror of
https://github.com/intel/llvm.git
synced 2026-01-13 11:02:04 +08:00
Add a "command container" hierarchy to allow users to add container nodes.
The point is to allow users with a related set of script based commands to organize their commands in a hierarchy in the command set, rather than having to have only top-level commands. Differential Revision: https://reviews.llvm.org/D110298
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
|
||||
#include "lldb/Core/FileSpecList.h"
|
||||
#include "lldb/Core/SearchFilter.h"
|
||||
#include "lldb/Interpreter/Options.h"
|
||||
#include "lldb/Utility/CompletionRequest.h"
|
||||
#include "lldb/Utility/RegularExpression.h"
|
||||
#include "lldb/lldb-private.h"
|
||||
@@ -151,6 +152,15 @@ public:
|
||||
static void TypeCategoryNames(CommandInterpreter &interpreter,
|
||||
CompletionRequest &request,
|
||||
SearchFilter *searcher);
|
||||
|
||||
/// This completer works for commands whose only arguments are a command path.
|
||||
/// It isn't tied to an argument type because it completes not on a single
|
||||
/// argument but on the sequence of arguments, so you have to invoke it by
|
||||
/// hand.
|
||||
static void
|
||||
CompleteModifiableCmdPathArgs(CommandInterpreter &interpreter,
|
||||
CompletionRequest &request,
|
||||
OptionElementVector &opt_element_vector);
|
||||
};
|
||||
|
||||
} // namespace lldb_private
|
||||
|
||||
@@ -231,11 +231,12 @@ public:
|
||||
};
|
||||
|
||||
enum CommandTypes {
|
||||
eCommandTypesBuiltin = 0x0001, // native commands such as "frame"
|
||||
eCommandTypesUserDef = 0x0002, // scripted commands
|
||||
eCommandTypesAliases = 0x0004, // aliases such as "po"
|
||||
eCommandTypesHidden = 0x0008, // commands prefixed with an underscore
|
||||
eCommandTypesAllThem = 0xFFFF // all commands
|
||||
eCommandTypesBuiltin = 0x0001, //< native commands such as "frame"
|
||||
eCommandTypesUserDef = 0x0002, //< scripted commands
|
||||
eCommandTypesUserMW = 0x0004, //< multiword commands (command containers)
|
||||
eCommandTypesAliases = 0x0008, //< aliases such as "po"
|
||||
eCommandTypesHidden = 0x0010, //< commands prefixed with an underscore
|
||||
eCommandTypesAllThem = 0xFFFF //< all commands
|
||||
};
|
||||
|
||||
CommandInterpreter(Debugger &debugger, bool synchronous_execution);
|
||||
@@ -256,8 +257,8 @@ public:
|
||||
bool AddCommand(llvm::StringRef name, const lldb::CommandObjectSP &cmd_sp,
|
||||
bool can_replace);
|
||||
|
||||
bool AddUserCommand(llvm::StringRef name, const lldb::CommandObjectSP &cmd_sp,
|
||||
bool can_replace);
|
||||
Status AddUserCommand(llvm::StringRef name,
|
||||
const lldb::CommandObjectSP &cmd_sp, bool can_replace);
|
||||
|
||||
lldb::CommandObjectSP GetCommandSPExact(llvm::StringRef cmd,
|
||||
bool include_aliases = false) const;
|
||||
@@ -266,12 +267,49 @@ public:
|
||||
StringList *matches = nullptr,
|
||||
StringList *descriptions = nullptr) const;
|
||||
|
||||
CommandObject *GetUserCommandObject(llvm::StringRef cmd,
|
||||
StringList *matches = nullptr,
|
||||
StringList *descriptions = nullptr) const;
|
||||
|
||||
/// Determine whether a root level, built-in command with this name exists.
|
||||
bool CommandExists(llvm::StringRef cmd) const;
|
||||
|
||||
/// Determine whether an alias command with this name exists
|
||||
bool AliasExists(llvm::StringRef cmd) const;
|
||||
|
||||
/// Determine whether a root-level user command with this name exists.
|
||||
bool UserCommandExists(llvm::StringRef cmd) const;
|
||||
|
||||
/// Determine whether a root-level user multiword command with this name
|
||||
/// exists.
|
||||
bool UserMultiwordCommandExists(llvm::StringRef cmd) const;
|
||||
|
||||
/// Look up the command pointed to by path encoded in the arguments of
|
||||
/// the incoming command object. If all the path components exist
|
||||
/// and are all actual commands - not aliases, and the leaf command is a
|
||||
/// multiword command, return the command. Otherwise return nullptr, and put
|
||||
/// a useful diagnostic in the Status object.
|
||||
///
|
||||
/// \param[in] path
|
||||
/// An Args object holding the path in its arguments
|
||||
/// \param[in] leaf_is_command
|
||||
/// If true, return the container of the leaf name rather than looking up
|
||||
/// the whole path as a leaf command. The leaf needn't exist in this case.
|
||||
/// \param[in,out] result
|
||||
/// If the path is not found, this error shows where we got off track.
|
||||
/// \return
|
||||
/// If found, a pointer to the CommandObjectMultiword pointed to by path,
|
||||
/// or to the container of the leaf element is is_leaf_command.
|
||||
/// Returns nullptr under two circumstances:
|
||||
/// 1) The command in not found (check error.Fail)
|
||||
/// 2) is_leaf is true and the path has only a leaf. We don't have a
|
||||
/// dummy "contains everything MWC, so we return null here, but
|
||||
/// in this case error.Success is true.
|
||||
|
||||
CommandObjectMultiword *VerifyUserMultiwordCmdPath(Args &path,
|
||||
bool leaf_is_command,
|
||||
Status &result);
|
||||
|
||||
CommandAlias *AddAlias(llvm::StringRef alias_name,
|
||||
lldb::CommandObjectSP &command_obj_sp,
|
||||
llvm::StringRef args_string = llvm::StringRef());
|
||||
@@ -283,6 +321,11 @@ public:
|
||||
|
||||
bool GetAliasFullName(llvm::StringRef cmd, std::string &full_name) const;
|
||||
|
||||
bool RemoveUserMultiword(llvm::StringRef multiword_name);
|
||||
|
||||
// Do we want to allow top-level user multiword commands to be deleted?
|
||||
void RemoveAllUserMultiword() { m_user_mw_dict.clear(); }
|
||||
|
||||
bool RemoveUser(llvm::StringRef alias_name);
|
||||
|
||||
void RemoveAllUser() { m_user_dict.clear(); }
|
||||
@@ -414,6 +457,8 @@ public:
|
||||
|
||||
bool HasUserCommands() const;
|
||||
|
||||
bool HasUserMultiwordCommands() const;
|
||||
|
||||
bool HasAliasOptions() const;
|
||||
|
||||
void BuildAliasCommandArgs(CommandObject *alias_cmd_obj,
|
||||
@@ -421,6 +466,7 @@ public:
|
||||
std::string &raw_input_string,
|
||||
CommandReturnObject &result);
|
||||
|
||||
/// Picks the number out of a string of the form "%NNN", otherwise return 0.
|
||||
int GetOptionArgumentPosition(const char *in_string);
|
||||
|
||||
void SkipLLDBInitFiles(bool skip_lldbinit_files) {
|
||||
@@ -437,7 +483,8 @@ public:
|
||||
StringList &commands_help,
|
||||
bool search_builtin_commands,
|
||||
bool search_user_commands,
|
||||
bool search_alias_commands);
|
||||
bool search_alias_commands,
|
||||
bool search_user_mw_commands);
|
||||
|
||||
bool GetBatchCommandMode() { return m_batch_command_mode; }
|
||||
|
||||
@@ -506,6 +553,10 @@ public:
|
||||
return m_user_dict;
|
||||
}
|
||||
|
||||
const CommandObject::CommandMap &GetUserMultiwordCommands() const {
|
||||
return m_user_mw_dict;
|
||||
}
|
||||
|
||||
const CommandObject::CommandMap &GetCommands() const {
|
||||
return m_command_dict;
|
||||
}
|
||||
@@ -636,6 +687,8 @@ private:
|
||||
CommandObject::CommandMap
|
||||
m_alias_dict; // Stores user aliases/abbreviations for commands
|
||||
CommandObject::CommandMap m_user_dict; // Stores user-defined commands
|
||||
CommandObject::CommandMap
|
||||
m_user_mw_dict; // Stores user-defined multiword commands
|
||||
CommandHistory m_command_history;
|
||||
std::string m_repeat_command; // Stores the command that will be executed for
|
||||
// an empty command string.
|
||||
|
||||
@@ -145,6 +145,10 @@ public:
|
||||
|
||||
virtual bool IsMultiwordObject() { return false; }
|
||||
|
||||
bool IsUserCommand() { return m_is_user_command; }
|
||||
|
||||
void SetIsUserCommand(bool is_user) { m_is_user_command = is_user; }
|
||||
|
||||
virtual CommandObjectMultiword *GetAsMultiwordCommand() { return nullptr; }
|
||||
|
||||
virtual bool IsAlias() { return false; }
|
||||
@@ -159,6 +163,10 @@ public:
|
||||
return lldb::CommandObjectSP();
|
||||
}
|
||||
|
||||
virtual lldb::CommandObjectSP GetSubcommandSPExact(llvm::StringRef sub_cmd) {
|
||||
return lldb::CommandObjectSP();
|
||||
}
|
||||
|
||||
virtual CommandObject *GetSubcommandObject(llvm::StringRef sub_cmd,
|
||||
StringList *matches = nullptr) {
|
||||
return nullptr;
|
||||
@@ -183,6 +191,13 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual llvm::Error LoadUserSubcommand(llvm::StringRef cmd_name,
|
||||
const lldb::CommandObjectSP &command_obj,
|
||||
bool can_replace) {
|
||||
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
||||
"can only add commands to container commands");
|
||||
}
|
||||
|
||||
virtual bool WantsRawCommandString() = 0;
|
||||
|
||||
// By default, WantsCompletion = !WantsRawCommandString. Subclasses who want
|
||||
@@ -367,6 +382,7 @@ protected:
|
||||
lldb::CommandOverrideCallback m_deprecated_command_override_callback;
|
||||
lldb::CommandOverrideCallbackWithResult m_command_override_callback;
|
||||
void *m_command_override_baton;
|
||||
bool m_is_user_command = false;
|
||||
|
||||
// Helper function to populate IDs or ID ranges as the command argument data
|
||||
// to the specified command argument entry.
|
||||
|
||||
@@ -35,11 +35,19 @@ public:
|
||||
bool LoadSubCommand(llvm::StringRef cmd_name,
|
||||
const lldb::CommandObjectSP &command_obj) override;
|
||||
|
||||
llvm::Error LoadUserSubcommand(llvm::StringRef cmd_name,
|
||||
const lldb::CommandObjectSP &command_obj,
|
||||
bool can_replace) override;
|
||||
|
||||
llvm::Error RemoveUserSubcommand(llvm::StringRef cmd_name, bool multiword_okay);
|
||||
|
||||
void GenerateHelpText(Stream &output_stream) override;
|
||||
|
||||
lldb::CommandObjectSP GetSubcommandSP(llvm::StringRef sub_cmd,
|
||||
StringList *matches = nullptr) override;
|
||||
|
||||
lldb::CommandObjectSP GetSubcommandSPExact(llvm::StringRef sub_cmd) override;
|
||||
|
||||
CommandObject *GetSubcommandObject(llvm::StringRef sub_cmd,
|
||||
StringList *matches = nullptr) override;
|
||||
|
||||
|
||||
@@ -574,12 +574,11 @@ lldb::SBCommand SBCommandInterpreter::AddMultiwordCommand(const char *name,
|
||||
LLDB_RECORD_METHOD(lldb::SBCommand, SBCommandInterpreter, AddMultiwordCommand,
|
||||
(const char *, const char *), name, help);
|
||||
|
||||
CommandObjectMultiword *new_command =
|
||||
new CommandObjectMultiword(*m_opaque_ptr, name, help);
|
||||
new_command->SetRemovable(true);
|
||||
lldb::CommandObjectSP new_command_sp(new_command);
|
||||
if (new_command_sp &&
|
||||
m_opaque_ptr->AddUserCommand(name, new_command_sp, true))
|
||||
lldb::CommandObjectSP new_command_sp(
|
||||
new CommandObjectMultiword(*m_opaque_ptr, name, help));
|
||||
new_command_sp->GetAsMultiwordCommand()->SetRemovable(true);
|
||||
Status add_error = m_opaque_ptr->AddUserCommand(name, new_command_sp, true);
|
||||
if (add_error.Success())
|
||||
return LLDB_RECORD_RESULT(lldb::SBCommand(new_command_sp));
|
||||
return LLDB_RECORD_RESULT(lldb::SBCommand());
|
||||
}
|
||||
@@ -620,8 +619,8 @@ lldb::SBCommand SBCommandInterpreter::AddCommand(
|
||||
*m_opaque_ptr, name, impl, help, syntax, /*flags=*/0,
|
||||
auto_repeat_command);
|
||||
|
||||
if (new_command_sp &&
|
||||
m_opaque_ptr->AddUserCommand(name, new_command_sp, true))
|
||||
Status add_error = m_opaque_ptr->AddUserCommand(name, new_command_sp, true);
|
||||
if (add_error.Success())
|
||||
return LLDB_RECORD_RESULT(lldb::SBCommand(new_command_sp));
|
||||
return LLDB_RECORD_RESULT(lldb::SBCommand());
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
#include "lldb/Host/FileSystem.h"
|
||||
#include "lldb/Interpreter/CommandCompletions.h"
|
||||
#include "lldb/Interpreter/CommandInterpreter.h"
|
||||
#include "lldb/Interpreter/CommandObject.h"
|
||||
#include "lldb/Interpreter/CommandObjectMultiword.h"
|
||||
#include "lldb/Interpreter/OptionValueProperties.h"
|
||||
#include "lldb/Symbol/CompileUnit.h"
|
||||
#include "lldb/Symbol/Variable.h"
|
||||
@@ -792,3 +794,60 @@ void CommandCompletions::TypeCategoryNames(CommandInterpreter &interpreter,
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void CommandCompletions::CompleteModifiableCmdPathArgs(
|
||||
CommandInterpreter &interpreter, CompletionRequest &request,
|
||||
OptionElementVector &opt_element_vector) {
|
||||
// The only arguments constitute a command path, however, there might be
|
||||
// options interspersed among the arguments, and we need to skip those. Do that
|
||||
// by copying the args vector, and just dropping all the option bits:
|
||||
Args args = request.GetParsedLine();
|
||||
std::vector<size_t> to_delete;
|
||||
for (auto &elem : opt_element_vector) {
|
||||
to_delete.push_back(elem.opt_pos);
|
||||
if (elem.opt_arg_pos != 0)
|
||||
to_delete.push_back(elem.opt_arg_pos);
|
||||
}
|
||||
sort(to_delete.begin(), to_delete.end(), std::greater<size_t>());
|
||||
for (size_t idx : to_delete)
|
||||
args.DeleteArgumentAtIndex(idx);
|
||||
|
||||
// At this point, we should only have args, so now lookup the command up to
|
||||
// the cursor element.
|
||||
|
||||
// There's nothing here but options. It doesn't seem very useful here to
|
||||
// dump all the commands, so just return.
|
||||
size_t num_args = args.GetArgumentCount();
|
||||
if (num_args == 0)
|
||||
return;
|
||||
|
||||
// There's just one argument, so we should complete its name:
|
||||
StringList matches;
|
||||
if (num_args == 1) {
|
||||
interpreter.GetUserCommandObject(args.GetArgumentAtIndex(0), &matches,
|
||||
nullptr);
|
||||
request.AddCompletions(matches);
|
||||
return;
|
||||
}
|
||||
|
||||
// There was more than one path element, lets find the containing command:
|
||||
Status error;
|
||||
CommandObjectMultiword *mwc =
|
||||
interpreter.VerifyUserMultiwordCmdPath(args, true, error);
|
||||
|
||||
// Something was wrong somewhere along the path, but I don't think there's
|
||||
// a good way to go back and fill in the missing elements:
|
||||
if (error.Fail())
|
||||
return;
|
||||
|
||||
// This should never happen. We already handled the case of one argument
|
||||
// above, and we can only get Success & nullptr back if there's a one-word
|
||||
// leaf.
|
||||
assert(mwc != nullptr);
|
||||
|
||||
mwc->GetSubcommandObject(args.GetArgumentAtIndex(num_args - 1), &matches);
|
||||
if (matches.GetSize() == 0)
|
||||
return;
|
||||
|
||||
request.AddCompletions(matches);
|
||||
}
|
||||
|
||||
@@ -49,8 +49,8 @@ bool CommandObjectApropos::DoExecute(Args &args, CommandReturnObject &result) {
|
||||
StringList commands_found;
|
||||
StringList commands_help;
|
||||
|
||||
m_interpreter.FindCommandsForApropos(search_word, commands_found,
|
||||
commands_help, true, true, true);
|
||||
m_interpreter.FindCommandsForApropos(
|
||||
search_word, commands_found, commands_help, true, true, true, true);
|
||||
|
||||
if (commands_found.GetSize() == 0) {
|
||||
result.AppendMessageWithFormat("No commands found pertaining to '%s'. "
|
||||
|
||||
@@ -443,6 +443,14 @@ protected:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_interpreter.UserMultiwordCommandExists(alias_command)) {
|
||||
result.AppendErrorWithFormat(
|
||||
"'%s' is a user container command and cannot be overwritten.\n"
|
||||
"Delete it first with 'command container delete'\n",
|
||||
args[0].c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get CommandObject that is being aliased. The command name is read from
|
||||
// the front of raw_command_string. raw_command_string is returned with the
|
||||
// name of the command object stripped off the front.
|
||||
@@ -528,6 +536,14 @@ protected:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_interpreter.UserMultiwordCommandExists(alias_command)) {
|
||||
result.AppendErrorWithFormat(
|
||||
"'%s' is user container command and cannot be overwritten.\n"
|
||||
"Delete it first with 'command container delete'",
|
||||
alias_command.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
CommandObjectSP command_obj_sp(
|
||||
m_interpreter.GetCommandSPExact(actual_command, true));
|
||||
CommandObjectSP subcommand_obj_sp;
|
||||
@@ -1371,14 +1387,21 @@ public:
|
||||
CommandObjectCommandsScriptAdd(CommandInterpreter &interpreter)
|
||||
: CommandObjectParsed(interpreter, "command script add",
|
||||
"Add a scripted function as an LLDB command.",
|
||||
nullptr),
|
||||
"Add a scripted function as an lldb command. "
|
||||
"If you provide a single argument, the command "
|
||||
"will be added at the root level of the command "
|
||||
"hierarchy. If there are more arguments they "
|
||||
"must be a path to a user-added container "
|
||||
"command, and the last element will be the new "
|
||||
"command name."),
|
||||
IOHandlerDelegateMultiline("DONE"), m_options() {
|
||||
CommandArgumentEntry arg1;
|
||||
CommandArgumentData cmd_arg;
|
||||
|
||||
// Define the first (and only) variant of this arg.
|
||||
cmd_arg.arg_type = eArgTypeCommandName;
|
||||
cmd_arg.arg_repetition = eArgRepeatPlain;
|
||||
// This is one or more command names, which form the path to the command
|
||||
// you want to add.
|
||||
cmd_arg.arg_type = eArgTypeCommand;
|
||||
cmd_arg.arg_repetition = eArgRepeatPlus;
|
||||
|
||||
// There is only one variant this argument could be; put it into the
|
||||
// argument entry.
|
||||
@@ -1392,6 +1415,13 @@ public:
|
||||
|
||||
Options *GetOptions() override { return &m_options; }
|
||||
|
||||
void
|
||||
HandleArgumentCompletion(CompletionRequest &request,
|
||||
OptionElementVector &opt_element_vector) override {
|
||||
CommandCompletions::CompleteModifiableCmdPathArgs(m_interpreter, request,
|
||||
opt_element_vector);
|
||||
}
|
||||
|
||||
protected:
|
||||
class CommandOptions : public Options {
|
||||
public:
|
||||
@@ -1418,6 +1448,9 @@ protected:
|
||||
if (!option_arg.empty())
|
||||
m_short_help = std::string(option_arg);
|
||||
break;
|
||||
case 'o':
|
||||
m_overwrite = true;
|
||||
break;
|
||||
case 's':
|
||||
m_synchronicity =
|
||||
(ScriptedCommandSynchronicity)OptionArgParser::ToOptionEnum(
|
||||
@@ -1438,6 +1471,7 @@ protected:
|
||||
m_class_name.clear();
|
||||
m_funct_name.clear();
|
||||
m_short_help.clear();
|
||||
m_overwrite = false;
|
||||
m_synchronicity = eScriptedCommandSynchronicitySynchronous;
|
||||
}
|
||||
|
||||
@@ -1450,6 +1484,7 @@ protected:
|
||||
std::string m_class_name;
|
||||
std::string m_funct_name;
|
||||
std::string m_short_help;
|
||||
bool m_overwrite;
|
||||
ScriptedCommandSynchronicity m_synchronicity =
|
||||
eScriptedCommandSynchronicitySynchronous;
|
||||
};
|
||||
@@ -1484,26 +1519,36 @@ protected:
|
||||
CommandObjectSP command_obj_sp(new CommandObjectPythonFunction(
|
||||
m_interpreter, m_cmd_name, funct_name_str, m_short_help,
|
||||
m_synchronicity));
|
||||
|
||||
if (!m_interpreter.AddUserCommand(m_cmd_name, command_obj_sp,
|
||||
true)) {
|
||||
error_sp->Printf("error: unable to add selected command, didn't "
|
||||
"add python command.\n");
|
||||
error_sp->Flush();
|
||||
if (!m_container) {
|
||||
Status error = m_interpreter.AddUserCommand(
|
||||
m_cmd_name, command_obj_sp, m_overwrite);
|
||||
if (error.Fail()) {
|
||||
error_sp->Printf("error: unable to add selected command: '%s'",
|
||||
error.AsCString());
|
||||
error_sp->Flush();
|
||||
}
|
||||
} else {
|
||||
llvm::Error llvm_error = m_container->LoadUserSubcommand(
|
||||
m_cmd_name, command_obj_sp, m_overwrite);
|
||||
if (llvm_error) {
|
||||
error_sp->Printf("error: unable to add selected command: '%s'",
|
||||
llvm::toString(std::move(llvm_error)).c_str());
|
||||
error_sp->Flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error_sp->Printf(
|
||||
"error: unable to create function, didn't add python command.\n");
|
||||
"error: unable to create function, didn't add python command\n");
|
||||
error_sp->Flush();
|
||||
}
|
||||
} else {
|
||||
error_sp->Printf("error: empty function, didn't add python command.\n");
|
||||
error_sp->Printf("error: empty function, didn't add python command\n");
|
||||
error_sp->Flush();
|
||||
}
|
||||
} else {
|
||||
error_sp->Printf(
|
||||
"error: script interpreter missing, didn't add python command.\n");
|
||||
"error: script interpreter missing, didn't add python command\n");
|
||||
error_sp->Flush();
|
||||
}
|
||||
|
||||
@@ -1517,31 +1562,45 @@ protected:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (command.GetArgumentCount() != 1) {
|
||||
result.AppendError("'command script add' requires one argument");
|
||||
if (command.GetArgumentCount() == 0) {
|
||||
result.AppendError("'command script add' requires at least one argument");
|
||||
return false;
|
||||
}
|
||||
// Store the options in case we get multi-line input
|
||||
m_overwrite = m_options.m_overwrite;
|
||||
Status path_error;
|
||||
m_container = GetCommandInterpreter().VerifyUserMultiwordCmdPath(
|
||||
command, true, path_error);
|
||||
|
||||
if (path_error.Fail()) {
|
||||
result.AppendErrorWithFormat("error in command path: %s",
|
||||
path_error.AsCString());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Store the options in case we get multi-line input
|
||||
m_cmd_name = std::string(command[0].ref());
|
||||
if (!m_container) {
|
||||
// This is getting inserted into the root of the interpreter.
|
||||
m_cmd_name = std::string(command[0].ref());
|
||||
} else {
|
||||
size_t num_args = command.GetArgumentCount();
|
||||
m_cmd_name = std::string(command[num_args - 1].ref());
|
||||
}
|
||||
|
||||
m_short_help.assign(m_options.m_short_help);
|
||||
m_synchronicity = m_options.m_synchronicity;
|
||||
|
||||
// Handle the case where we prompt for the script code first:
|
||||
if (m_options.m_class_name.empty() && m_options.m_funct_name.empty()) {
|
||||
m_interpreter.GetPythonCommandsFromIOHandler(" ", // Prompt
|
||||
*this); // IOHandlerDelegate
|
||||
return result.Succeeded();
|
||||
}
|
||||
|
||||
CommandObjectSP new_cmd_sp;
|
||||
if (m_options.m_class_name.empty()) {
|
||||
if (m_options.m_funct_name.empty()) {
|
||||
m_interpreter.GetPythonCommandsFromIOHandler(
|
||||
" ", // Prompt
|
||||
*this); // IOHandlerDelegate
|
||||
} else {
|
||||
CommandObjectSP new_cmd(new CommandObjectPythonFunction(
|
||||
m_interpreter, m_cmd_name, m_options.m_funct_name,
|
||||
m_options.m_short_help, m_synchronicity));
|
||||
if (m_interpreter.AddUserCommand(m_cmd_name, new_cmd, true)) {
|
||||
result.SetStatus(eReturnStatusSuccessFinishNoResult);
|
||||
} else {
|
||||
result.AppendError("cannot add command");
|
||||
}
|
||||
}
|
||||
new_cmd_sp.reset(new CommandObjectPythonFunction(
|
||||
m_interpreter, m_cmd_name, m_options.m_funct_name,
|
||||
m_options.m_short_help, m_synchronicity));
|
||||
} else {
|
||||
ScriptInterpreter *interpreter = GetDebugger().GetScriptInterpreter();
|
||||
if (!interpreter) {
|
||||
@@ -1556,21 +1615,33 @@ protected:
|
||||
return false;
|
||||
}
|
||||
|
||||
CommandObjectSP new_cmd(new CommandObjectScriptingObject(
|
||||
new_cmd_sp.reset(new CommandObjectScriptingObject(
|
||||
m_interpreter, m_cmd_name, cmd_obj_sp, m_synchronicity));
|
||||
if (m_interpreter.AddUserCommand(m_cmd_name, new_cmd, true)) {
|
||||
result.SetStatus(eReturnStatusSuccessFinishNoResult);
|
||||
} else {
|
||||
result.AppendError("cannot add command");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Assume we're going to succeed...
|
||||
result.SetStatus(eReturnStatusSuccessFinishNoResult);
|
||||
if (!m_container) {
|
||||
Status add_error =
|
||||
m_interpreter.AddUserCommand(m_cmd_name, new_cmd_sp, m_overwrite);
|
||||
if (add_error.Fail())
|
||||
result.AppendErrorWithFormat("cannot add command: %s",
|
||||
add_error.AsCString());
|
||||
} else {
|
||||
llvm::Error llvm_error =
|
||||
m_container->LoadUserSubcommand(m_cmd_name, new_cmd_sp, m_overwrite);
|
||||
if (llvm_error)
|
||||
result.AppendErrorWithFormat("cannot add command: %s",
|
||||
llvm::toString(std::move(llvm_error)).c_str());
|
||||
}
|
||||
return result.Succeeded();
|
||||
}
|
||||
|
||||
CommandOptions m_options;
|
||||
std::string m_cmd_name;
|
||||
CommandObjectMultiword *m_container = nullptr;
|
||||
std::string m_short_help;
|
||||
bool m_overwrite;
|
||||
ScriptedCommandSynchronicity m_synchronicity;
|
||||
};
|
||||
|
||||
@@ -1580,7 +1651,8 @@ class CommandObjectCommandsScriptList : public CommandObjectParsed {
|
||||
public:
|
||||
CommandObjectCommandsScriptList(CommandInterpreter &interpreter)
|
||||
: CommandObjectParsed(interpreter, "command script list",
|
||||
"List defined scripted commands.", nullptr) {}
|
||||
"List defined top-level scripted commands.",
|
||||
nullptr) {}
|
||||
|
||||
~CommandObjectCommandsScriptList() override = default;
|
||||
|
||||
@@ -1628,14 +1700,17 @@ protected:
|
||||
class CommandObjectCommandsScriptDelete : public CommandObjectParsed {
|
||||
public:
|
||||
CommandObjectCommandsScriptDelete(CommandInterpreter &interpreter)
|
||||
: CommandObjectParsed(interpreter, "command script delete",
|
||||
"Delete a scripted command.", nullptr) {
|
||||
: CommandObjectParsed(
|
||||
interpreter, "command script delete",
|
||||
"Delete a scripted command by specifying the path to the command.",
|
||||
nullptr) {
|
||||
CommandArgumentEntry arg1;
|
||||
CommandArgumentData cmd_arg;
|
||||
|
||||
// Define the first (and only) variant of this arg.
|
||||
cmd_arg.arg_type = eArgTypeCommandName;
|
||||
cmd_arg.arg_repetition = eArgRepeatPlain;
|
||||
// This is a list of command names forming the path to the command
|
||||
// to be deleted.
|
||||
cmd_arg.arg_type = eArgTypeCommand;
|
||||
cmd_arg.arg_repetition = eArgRepeatPlus;
|
||||
|
||||
// There is only one variant this argument could be; put it into the
|
||||
// argument entry.
|
||||
@@ -1650,30 +1725,86 @@ public:
|
||||
void
|
||||
HandleArgumentCompletion(CompletionRequest &request,
|
||||
OptionElementVector &opt_element_vector) override {
|
||||
if (!m_interpreter.HasCommands() || request.GetCursorIndex() != 0)
|
||||
return;
|
||||
|
||||
for (const auto &c : m_interpreter.GetUserCommands())
|
||||
request.TryCompleteCurrentArg(c.first, c.second->GetHelp());
|
||||
CommandCompletions::CompleteModifiableCmdPathArgs(m_interpreter, request,
|
||||
opt_element_vector);
|
||||
}
|
||||
|
||||
protected:
|
||||
bool DoExecute(Args &command, CommandReturnObject &result) override {
|
||||
|
||||
if (command.GetArgumentCount() != 1) {
|
||||
result.AppendError("'command script delete' requires one argument");
|
||||
llvm::StringRef root_cmd = command[0].ref();
|
||||
size_t num_args = command.GetArgumentCount();
|
||||
|
||||
if (root_cmd.empty()) {
|
||||
result.AppendErrorWithFormat("empty root command name");
|
||||
return false;
|
||||
}
|
||||
if (!m_interpreter.HasUserCommands() &&
|
||||
!m_interpreter.HasUserMultiwordCommands()) {
|
||||
result.AppendErrorWithFormat("can only delete user defined commands, "
|
||||
"but no user defined commands found");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto cmd_name = command[0].ref();
|
||||
|
||||
if (cmd_name.empty() || !m_interpreter.HasUserCommands() ||
|
||||
!m_interpreter.UserCommandExists(cmd_name)) {
|
||||
result.AppendErrorWithFormat("command %s not found", command[0].c_str());
|
||||
CommandObjectSP cmd_sp = m_interpreter.GetCommandSPExact(root_cmd);
|
||||
if (!cmd_sp) {
|
||||
result.AppendErrorWithFormat("command '%s' not found.",
|
||||
command[0].c_str());
|
||||
return false;
|
||||
}
|
||||
if (!cmd_sp->IsUserCommand()) {
|
||||
result.AppendErrorWithFormat("command '%s' is not a user command.",
|
||||
command[0].c_str());
|
||||
return false;
|
||||
}
|
||||
if (cmd_sp->GetAsMultiwordCommand() && num_args == 1) {
|
||||
result.AppendErrorWithFormat("command '%s' is a multi-word command.\n "
|
||||
"Delete with \"command container delete\"",
|
||||
command[0].c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
m_interpreter.RemoveUser(cmd_name);
|
||||
if (command.GetArgumentCount() == 1) {
|
||||
m_interpreter.RemoveUser(root_cmd);
|
||||
result.SetStatus(eReturnStatusSuccessFinishResult);
|
||||
return true;
|
||||
}
|
||||
// We're deleting a command from a multiword command. Verify the command
|
||||
// path:
|
||||
Status error;
|
||||
CommandObjectMultiword *container =
|
||||
GetCommandInterpreter().VerifyUserMultiwordCmdPath(command, true,
|
||||
error);
|
||||
if (error.Fail()) {
|
||||
result.AppendErrorWithFormat("could not resolve command path: %s",
|
||||
error.AsCString());
|
||||
return false;
|
||||
}
|
||||
if (!container) {
|
||||
// This means that command only had a leaf command, so the container is
|
||||
// the root. That should have been handled above.
|
||||
result.AppendErrorWithFormat("could not find a container for '%s'",
|
||||
command[0].c_str());
|
||||
return false;
|
||||
}
|
||||
const char *leaf_cmd = command[num_args - 1].c_str();
|
||||
llvm::Error llvm_error = container->RemoveUserSubcommand(leaf_cmd,
|
||||
/* multiword not okay */ false);
|
||||
if (llvm_error) {
|
||||
result.AppendErrorWithFormat("could not delete command '%s': %s",
|
||||
leaf_cmd,
|
||||
llvm::toString(std::move(llvm_error)).c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
Stream &out_stream = result.GetOutputStream();
|
||||
|
||||
out_stream << "Deleted command:";
|
||||
for (size_t idx = 0; idx < num_args; idx++) {
|
||||
out_stream << ' ';
|
||||
out_stream << command[idx].c_str();
|
||||
}
|
||||
out_stream << '\n';
|
||||
result.SetStatus(eReturnStatusSuccessFinishResult);
|
||||
return true;
|
||||
}
|
||||
@@ -1710,6 +1841,271 @@ public:
|
||||
~CommandObjectMultiwordCommandsScript() override = default;
|
||||
};
|
||||
|
||||
#pragma mark CommandObjectCommandContainer
|
||||
#define LLDB_OPTIONS_container_add
|
||||
#include "CommandOptions.inc"
|
||||
|
||||
class CommandObjectCommandsContainerAdd : public CommandObjectParsed {
|
||||
public:
|
||||
CommandObjectCommandsContainerAdd(CommandInterpreter &interpreter)
|
||||
: CommandObjectParsed(
|
||||
interpreter, "command container add",
|
||||
"Add a container command to lldb. Adding to built-"
|
||||
"in container commands is not allowed.",
|
||||
"command container add [[path1]...] container-name") {
|
||||
CommandArgumentEntry arg1;
|
||||
CommandArgumentData cmd_arg;
|
||||
|
||||
// This is one or more command names, which form the path to the command
|
||||
// you want to add.
|
||||
cmd_arg.arg_type = eArgTypeCommand;
|
||||
cmd_arg.arg_repetition = eArgRepeatPlus;
|
||||
|
||||
// There is only one variant this argument could be; put it into the
|
||||
// argument entry.
|
||||
arg1.push_back(cmd_arg);
|
||||
|
||||
// Push the data for the first argument into the m_arguments vector.
|
||||
m_arguments.push_back(arg1);
|
||||
}
|
||||
|
||||
~CommandObjectCommandsContainerAdd() override = default;
|
||||
|
||||
Options *GetOptions() override { return &m_options; }
|
||||
|
||||
void
|
||||
HandleArgumentCompletion(CompletionRequest &request,
|
||||
OptionElementVector &opt_element_vector) override {
|
||||
CommandCompletions::CompleteModifiableCmdPathArgs(m_interpreter, request,
|
||||
opt_element_vector);
|
||||
}
|
||||
|
||||
protected:
|
||||
class CommandOptions : public Options {
|
||||
public:
|
||||
CommandOptions() : Options(), m_short_help(), m_long_help() {}
|
||||
|
||||
~CommandOptions() override = default;
|
||||
|
||||
Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
|
||||
ExecutionContext *execution_context) override {
|
||||
Status error;
|
||||
const int short_option = m_getopt_table[option_idx].val;
|
||||
|
||||
switch (short_option) {
|
||||
case 'h':
|
||||
if (!option_arg.empty())
|
||||
m_short_help = std::string(option_arg);
|
||||
break;
|
||||
case 'o':
|
||||
m_overwrite = true;
|
||||
break;
|
||||
case 'H':
|
||||
if (!option_arg.empty())
|
||||
m_long_help = std::string(option_arg);
|
||||
break;
|
||||
default:
|
||||
llvm_unreachable("Unimplemented option");
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
void OptionParsingStarting(ExecutionContext *execution_context) override {
|
||||
m_short_help.clear();
|
||||
m_long_help.clear();
|
||||
m_overwrite = false;
|
||||
}
|
||||
|
||||
llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
|
||||
return llvm::makeArrayRef(g_container_add_options);
|
||||
}
|
||||
|
||||
// Instance variables to hold the values for command options.
|
||||
|
||||
std::string m_short_help;
|
||||
std::string m_long_help;
|
||||
bool m_overwrite = false;
|
||||
};
|
||||
bool DoExecute(Args &command, CommandReturnObject &result) override {
|
||||
size_t num_args = command.GetArgumentCount();
|
||||
|
||||
if (num_args == 0) {
|
||||
result.AppendError("no command was specified");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (num_args == 1) {
|
||||
// We're adding this as a root command, so use the interpreter.
|
||||
const char *cmd_name = command.GetArgumentAtIndex(0);
|
||||
auto cmd_sp = CommandObjectSP(new CommandObjectMultiword(
|
||||
GetCommandInterpreter(), cmd_name, m_options.m_short_help.c_str(),
|
||||
m_options.m_long_help.c_str()));
|
||||
cmd_sp->GetAsMultiwordCommand()->SetRemovable(true);
|
||||
Status add_error = GetCommandInterpreter().AddUserCommand(
|
||||
cmd_name, cmd_sp, m_options.m_overwrite);
|
||||
if (add_error.Fail()) {
|
||||
result.AppendErrorWithFormat("error adding command: %s",
|
||||
add_error.AsCString());
|
||||
return false;
|
||||
}
|
||||
result.SetStatus(eReturnStatusSuccessFinishNoResult);
|
||||
return true;
|
||||
}
|
||||
|
||||
// We're adding this to a subcommand, first find the subcommand:
|
||||
Status path_error;
|
||||
CommandObjectMultiword *add_to_me =
|
||||
GetCommandInterpreter().VerifyUserMultiwordCmdPath(command, true,
|
||||
path_error);
|
||||
|
||||
if (!add_to_me) {
|
||||
result.AppendErrorWithFormat("error adding command: %s",
|
||||
path_error.AsCString());
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *cmd_name = command.GetArgumentAtIndex(num_args - 1);
|
||||
auto cmd_sp = CommandObjectSP(new CommandObjectMultiword(
|
||||
GetCommandInterpreter(), cmd_name, m_options.m_short_help.c_str(),
|
||||
m_options.m_long_help.c_str()));
|
||||
llvm::Error llvm_error =
|
||||
add_to_me->LoadUserSubcommand(cmd_name, cmd_sp, m_options.m_overwrite);
|
||||
if (llvm_error) {
|
||||
result.AppendErrorWithFormat("error adding subcommand: %s",
|
||||
llvm::toString(std::move(llvm_error)).c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
result.SetStatus(eReturnStatusSuccessFinishNoResult);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
CommandOptions m_options;
|
||||
};
|
||||
|
||||
#define LLDB_OPTIONS_multiword_delete
|
||||
#include "CommandOptions.inc"
|
||||
class CommandObjectCommandsContainerDelete : public CommandObjectParsed {
|
||||
public:
|
||||
CommandObjectCommandsContainerDelete(CommandInterpreter &interpreter)
|
||||
: CommandObjectParsed(
|
||||
interpreter, "command container delete",
|
||||
"Delete a container command previously added to "
|
||||
"lldb.",
|
||||
"command container delete [[path1] ...] container-cmd") {
|
||||
CommandArgumentEntry arg1;
|
||||
CommandArgumentData cmd_arg;
|
||||
|
||||
// This is one or more command names, which form the path to the command
|
||||
// you want to add.
|
||||
cmd_arg.arg_type = eArgTypeCommand;
|
||||
cmd_arg.arg_repetition = eArgRepeatPlus;
|
||||
|
||||
// There is only one variant this argument could be; put it into the
|
||||
// argument entry.
|
||||
arg1.push_back(cmd_arg);
|
||||
|
||||
// Push the data for the first argument into the m_arguments vector.
|
||||
m_arguments.push_back(arg1);
|
||||
}
|
||||
|
||||
~CommandObjectCommandsContainerDelete() override = default;
|
||||
|
||||
void
|
||||
HandleArgumentCompletion(CompletionRequest &request,
|
||||
OptionElementVector &opt_element_vector) override {
|
||||
CommandCompletions::CompleteModifiableCmdPathArgs(m_interpreter, request,
|
||||
opt_element_vector);
|
||||
}
|
||||
|
||||
protected:
|
||||
bool DoExecute(Args &command, CommandReturnObject &result) override {
|
||||
size_t num_args = command.GetArgumentCount();
|
||||
|
||||
if (num_args == 0) {
|
||||
result.AppendError("No command was specified.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (num_args == 1) {
|
||||
// We're removing a root command, so we need to delete it from the
|
||||
// interpreter.
|
||||
const char *cmd_name = command.GetArgumentAtIndex(0);
|
||||
// Let's do a little more work here so we can do better error reporting.
|
||||
CommandInterpreter &interp = GetCommandInterpreter();
|
||||
CommandObjectSP cmd_sp = interp.GetCommandSPExact(cmd_name);
|
||||
if (!cmd_sp) {
|
||||
result.AppendErrorWithFormat("container command %s doesn't exist.",
|
||||
cmd_name);
|
||||
return false;
|
||||
}
|
||||
if (!cmd_sp->IsUserCommand()) {
|
||||
result.AppendErrorWithFormat(
|
||||
"container command %s is not a user command", cmd_name);
|
||||
return false;
|
||||
}
|
||||
if (!cmd_sp->GetAsMultiwordCommand()) {
|
||||
result.AppendErrorWithFormat("command %s is not a container command",
|
||||
cmd_name);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool did_remove = GetCommandInterpreter().RemoveUserMultiword(cmd_name);
|
||||
if (!did_remove) {
|
||||
result.AppendErrorWithFormat("error removing command %s.", cmd_name);
|
||||
return false;
|
||||
}
|
||||
|
||||
result.SetStatus(eReturnStatusSuccessFinishNoResult);
|
||||
return true;
|
||||
}
|
||||
|
||||
// We're removing a subcommand, first find the subcommand's owner:
|
||||
Status path_error;
|
||||
CommandObjectMultiword *container =
|
||||
GetCommandInterpreter().VerifyUserMultiwordCmdPath(command, true,
|
||||
path_error);
|
||||
|
||||
if (!container) {
|
||||
result.AppendErrorWithFormat("error removing container command: %s",
|
||||
path_error.AsCString());
|
||||
return false;
|
||||
}
|
||||
const char *leaf = command.GetArgumentAtIndex(num_args - 1);
|
||||
llvm::Error llvm_error =
|
||||
container->RemoveUserSubcommand(leaf, /* multiword okay */ true);
|
||||
if (llvm_error) {
|
||||
result.AppendErrorWithFormat("error removing container command: %s",
|
||||
llvm::toString(std::move(llvm_error)).c_str());
|
||||
return false;
|
||||
}
|
||||
result.SetStatus(eReturnStatusSuccessFinishNoResult);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class CommandObjectCommandContainer : public CommandObjectMultiword {
|
||||
public:
|
||||
CommandObjectCommandContainer(CommandInterpreter &interpreter)
|
||||
: CommandObjectMultiword(
|
||||
interpreter, "command container",
|
||||
"Commands for adding container commands to lldb. "
|
||||
"Container commands are containers for other commands. You can"
|
||||
"add nested container commands by specifying a command path, but "
|
||||
"but you can't add commands into the built-in command hierarchy.",
|
||||
"command container <subcommand> [<subcommand-options>]") {
|
||||
LoadSubCommand("add", CommandObjectSP(new CommandObjectCommandsContainerAdd(
|
||||
interpreter)));
|
||||
LoadSubCommand(
|
||||
"delete",
|
||||
CommandObjectSP(new CommandObjectCommandsContainerDelete(interpreter)));
|
||||
}
|
||||
|
||||
~CommandObjectCommandContainer() override = default;
|
||||
};
|
||||
|
||||
#pragma mark CommandObjectMultiwordCommands
|
||||
|
||||
// CommandObjectMultiwordCommands
|
||||
@@ -1727,6 +2123,8 @@ CommandObjectMultiwordCommands::CommandObjectMultiwordCommands(
|
||||
new CommandObjectCommandsUnalias(interpreter)));
|
||||
LoadSubCommand("delete",
|
||||
CommandObjectSP(new CommandObjectCommandsDelete(interpreter)));
|
||||
LoadSubCommand("container", CommandObjectSP(new CommandObjectCommandContainer(
|
||||
interpreter)));
|
||||
LoadSubCommand(
|
||||
"regex", CommandObjectSP(new CommandObjectCommandsAddRegex(interpreter)));
|
||||
LoadSubCommand(
|
||||
|
||||
@@ -51,8 +51,9 @@ CommandObjectHelp::CommandObjectHelp(CommandInterpreter &interpreter)
|
||||
CommandArgumentEntry arg;
|
||||
CommandArgumentData command_arg;
|
||||
|
||||
// Define the first (and only) variant of this arg.
|
||||
command_arg.arg_type = eArgTypeCommandName;
|
||||
// A list of command names forming a path to the command we want help on.
|
||||
// No names is allowed - in which case we dump the top-level help.
|
||||
command_arg.arg_type = eArgTypeCommand;
|
||||
command_arg.arg_repetition = eArgRepeatStar;
|
||||
|
||||
// There is only one variant this argument could be; put it into the argument
|
||||
@@ -85,8 +86,10 @@ bool CommandObjectHelp::DoExecute(Args &command, CommandReturnObject &result) {
|
||||
uint32_t cmd_types = CommandInterpreter::eCommandTypesBuiltin;
|
||||
if (m_options.m_show_aliases)
|
||||
cmd_types |= CommandInterpreter::eCommandTypesAliases;
|
||||
if (m_options.m_show_user_defined)
|
||||
if (m_options.m_show_user_defined) {
|
||||
cmd_types |= CommandInterpreter::eCommandTypesUserDef;
|
||||
cmd_types |= CommandInterpreter::eCommandTypesUserMW;
|
||||
}
|
||||
if (m_options.m_show_hidden)
|
||||
cmd_types |= CommandInterpreter::eCommandTypesHidden;
|
||||
|
||||
|
||||
@@ -26,36 +26,48 @@ CommandObjectMultiword::CommandObjectMultiword(CommandInterpreter &interpreter,
|
||||
|
||||
CommandObjectMultiword::~CommandObjectMultiword() = default;
|
||||
|
||||
CommandObjectSP
|
||||
CommandObjectMultiword::GetSubcommandSPExact(llvm::StringRef sub_cmd) {
|
||||
if (m_subcommand_dict.empty())
|
||||
return {};
|
||||
|
||||
auto pos = m_subcommand_dict.find(std::string(sub_cmd));
|
||||
if (pos == m_subcommand_dict.end())
|
||||
return {};
|
||||
|
||||
return pos->second;
|
||||
}
|
||||
|
||||
CommandObjectSP CommandObjectMultiword::GetSubcommandSP(llvm::StringRef sub_cmd,
|
||||
StringList *matches) {
|
||||
CommandObjectSP return_cmd_sp;
|
||||
if (m_subcommand_dict.empty())
|
||||
return {};
|
||||
|
||||
CommandObjectSP return_cmd_sp = GetSubcommandSPExact(sub_cmd);
|
||||
if (return_cmd_sp) {
|
||||
if (matches)
|
||||
matches->AppendString(sub_cmd);
|
||||
return return_cmd_sp;
|
||||
}
|
||||
|
||||
CommandObject::CommandMap::iterator pos;
|
||||
|
||||
if (!m_subcommand_dict.empty()) {
|
||||
StringList local_matches;
|
||||
if (matches == nullptr)
|
||||
matches = &local_matches;
|
||||
int num_matches =
|
||||
AddNamesMatchingPartialString(m_subcommand_dict, sub_cmd, *matches);
|
||||
|
||||
if (num_matches == 1) {
|
||||
// Cleaner, but slightly less efficient would be to call back into this
|
||||
// function, since I now know I have an exact match...
|
||||
|
||||
sub_cmd = matches->GetStringAtIndex(0);
|
||||
pos = m_subcommand_dict.find(std::string(sub_cmd));
|
||||
if (pos != m_subcommand_dict.end()) {
|
||||
// An exact match; append the sub_cmd to the 'matches' string list.
|
||||
if (matches)
|
||||
matches->AppendString(sub_cmd);
|
||||
if (pos != m_subcommand_dict.end())
|
||||
return_cmd_sp = pos->second;
|
||||
} else {
|
||||
StringList local_matches;
|
||||
if (matches == nullptr)
|
||||
matches = &local_matches;
|
||||
int num_matches =
|
||||
AddNamesMatchingPartialString(m_subcommand_dict, sub_cmd, *matches);
|
||||
|
||||
if (num_matches == 1) {
|
||||
// Cleaner, but slightly less efficient would be to call back into this
|
||||
// function, since I now know I have an exact match...
|
||||
|
||||
sub_cmd = matches->GetStringAtIndex(0);
|
||||
pos = m_subcommand_dict.find(std::string(sub_cmd));
|
||||
if (pos != m_subcommand_dict.end())
|
||||
return_cmd_sp = pos->second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return return_cmd_sp;
|
||||
}
|
||||
|
||||
@@ -66,9 +78,9 @@ CommandObjectMultiword::GetSubcommandObject(llvm::StringRef sub_cmd,
|
||||
}
|
||||
|
||||
bool CommandObjectMultiword::LoadSubCommand(llvm::StringRef name,
|
||||
const CommandObjectSP &cmd_obj) {
|
||||
if (cmd_obj)
|
||||
assert((&GetCommandInterpreter() == &cmd_obj->GetCommandInterpreter()) &&
|
||||
const CommandObjectSP &cmd_obj_sp) {
|
||||
if (cmd_obj_sp)
|
||||
lldbassert((&GetCommandInterpreter() == &cmd_obj_sp->GetCommandInterpreter()) &&
|
||||
"tried to add a CommandObject from a different interpreter");
|
||||
|
||||
CommandMap::iterator pos;
|
||||
@@ -76,13 +88,76 @@ bool CommandObjectMultiword::LoadSubCommand(llvm::StringRef name,
|
||||
|
||||
pos = m_subcommand_dict.find(std::string(name));
|
||||
if (pos == m_subcommand_dict.end()) {
|
||||
m_subcommand_dict[std::string(name)] = cmd_obj;
|
||||
m_subcommand_dict[std::string(name)] = cmd_obj_sp;
|
||||
} else
|
||||
success = false;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
llvm::Error CommandObjectMultiword::LoadUserSubcommand(
|
||||
llvm::StringRef name, const CommandObjectSP &cmd_obj_sp, bool can_replace) {
|
||||
Status result;
|
||||
if (cmd_obj_sp)
|
||||
lldbassert((&GetCommandInterpreter() == &cmd_obj_sp->GetCommandInterpreter()) &&
|
||||
"tried to add a CommandObject from a different interpreter");
|
||||
if (!IsUserCommand()) {
|
||||
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
||||
"can't add a user subcommand to a builtin container command.");
|
||||
}
|
||||
// Make sure this a user command if it isn't already:
|
||||
cmd_obj_sp->SetIsUserCommand(true);
|
||||
|
||||
std::string str_name(name);
|
||||
|
||||
auto pos = m_subcommand_dict.find(str_name);
|
||||
if (pos == m_subcommand_dict.end()) {
|
||||
m_subcommand_dict[str_name] = cmd_obj_sp;
|
||||
return llvm::Error::success();
|
||||
}
|
||||
|
||||
const char *error_str = nullptr;
|
||||
if (!can_replace)
|
||||
error_str = "sub-command already exists";
|
||||
if (!(*pos).second->IsUserCommand())
|
||||
error_str = "can't replace a builtin subcommand";
|
||||
|
||||
if (error_str) {
|
||||
return llvm::createStringError(llvm::inconvertibleErrorCode(), error_str);
|
||||
}
|
||||
m_subcommand_dict[str_name] = cmd_obj_sp;
|
||||
return llvm::Error::success();
|
||||
}
|
||||
|
||||
llvm::Error CommandObjectMultiword::RemoveUserSubcommand(llvm::StringRef cmd_name,
|
||||
bool must_be_multiword) {
|
||||
CommandMap::iterator pos;
|
||||
std::string str_name(cmd_name);
|
||||
|
||||
pos = m_subcommand_dict.find(str_name);
|
||||
if (pos == m_subcommand_dict.end()) {
|
||||
return llvm::createStringError(llvm::inconvertibleErrorCode(),"subcommand '%s' not found.",
|
||||
str_name.c_str());
|
||||
}
|
||||
if (!(*pos).second->IsUserCommand()) {
|
||||
return llvm::createStringError(llvm::inconvertibleErrorCode(),"subcommand '%s' not a user command.",
|
||||
str_name.c_str());
|
||||
}
|
||||
|
||||
if (must_be_multiword && !(*pos).second->IsMultiwordObject()) {
|
||||
return llvm::createStringError(llvm::inconvertibleErrorCode(),"subcommand '%s' is not a container command",
|
||||
str_name.c_str());
|
||||
}
|
||||
if (!must_be_multiword && (*pos).second->IsMultiwordObject()) {
|
||||
return llvm::createStringError(llvm::inconvertibleErrorCode(),"subcommand '%s' is not a user command",
|
||||
str_name.c_str());
|
||||
}
|
||||
|
||||
m_subcommand_dict.erase(pos);
|
||||
|
||||
return llvm::Error::success();
|
||||
}
|
||||
|
||||
bool CommandObjectMultiword::Execute(const char *args_string,
|
||||
CommandReturnObject &result) {
|
||||
Args args(args_string);
|
||||
|
||||
@@ -787,12 +787,23 @@ let Command = "script add" in {
|
||||
Desc<"Name of the Python class to bind to this command name.">;
|
||||
def script_add_help : Option<"help", "h">, Group<1>, Arg<"HelpText">,
|
||||
Desc<"The help text to display for this command.">;
|
||||
def script_add_overwrite : Option<"overwrite", "o">, Groups<[1,2]>,
|
||||
Desc<"Overwrite an existing command at this node.">;
|
||||
def script_add_synchronicity : Option<"synchronicity", "s">,
|
||||
EnumArg<"ScriptedCommandSynchronicity", "ScriptSynchroType()">,
|
||||
Desc<"Set the synchronicity of this command's executions with regard to "
|
||||
"LLDB event system.">;
|
||||
}
|
||||
|
||||
let Command = "container add" in {
|
||||
def container_add_help : Option<"help", "h">, Arg<"HelpText">,
|
||||
Desc<"Help text for this command">;
|
||||
def container_add_long_help : Option<"long-help", "H">, Arg<"HelpText">,
|
||||
Desc<"Long help text for this command">;
|
||||
def container_add_overwrite : Option<"overwrite", "o">, Group<1>,
|
||||
Desc<"Overwrite an existing command at this node.">;
|
||||
}
|
||||
|
||||
let Command = "script" in {
|
||||
def script_language : Option<"language", "l">,
|
||||
EnumArg<"ScriptLang", "ScriptOptionEnum()">, Desc<"Specify the scripting "
|
||||
|
||||
@@ -897,6 +897,63 @@ int CommandInterpreter::GetCommandNamesMatchingPartialString(
|
||||
return matches.GetSize();
|
||||
}
|
||||
|
||||
CommandObjectMultiword *CommandInterpreter::VerifyUserMultiwordCmdPath(
|
||||
Args &path, bool leaf_is_command, Status &result) {
|
||||
result.Clear();
|
||||
|
||||
auto get_multi_or_report_error =
|
||||
[&result](CommandObjectSP cmd_sp,
|
||||
const char *name) -> CommandObjectMultiword * {
|
||||
if (!cmd_sp) {
|
||||
result.SetErrorStringWithFormat("Path component: '%s' not found", name);
|
||||
return nullptr;
|
||||
}
|
||||
if (!cmd_sp->IsUserCommand()) {
|
||||
result.SetErrorStringWithFormat("Path component: '%s' is not a user "
|
||||
"command",
|
||||
name);
|
||||
return nullptr;
|
||||
}
|
||||
CommandObjectMultiword *cmd_as_multi = cmd_sp->GetAsMultiwordCommand();
|
||||
if (!cmd_as_multi) {
|
||||
result.SetErrorStringWithFormat("Path component: '%s' is not a container "
|
||||
"command",
|
||||
name);
|
||||
return nullptr;
|
||||
}
|
||||
return cmd_as_multi;
|
||||
};
|
||||
|
||||
size_t num_args = path.GetArgumentCount();
|
||||
if (num_args == 0) {
|
||||
result.SetErrorString("empty command path");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (num_args == 1 && leaf_is_command) {
|
||||
// We just got a leaf command to be added to the root. That's not an error,
|
||||
// just return null for the container.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Start by getting the root command from the interpreter.
|
||||
const char *cur_name = path.GetArgumentAtIndex(0);
|
||||
CommandObjectSP cur_cmd_sp = GetCommandSPExact(cur_name);
|
||||
CommandObjectMultiword *cur_as_multi =
|
||||
get_multi_or_report_error(cur_cmd_sp, cur_name);
|
||||
if (cur_as_multi == nullptr)
|
||||
return nullptr;
|
||||
|
||||
size_t num_path_elements = num_args - (leaf_is_command ? 1 : 0);
|
||||
for (size_t cursor = 1; cursor < num_path_elements && cur_as_multi != nullptr;
|
||||
cursor++) {
|
||||
cur_name = path.GetArgumentAtIndex(cursor);
|
||||
cur_cmd_sp = cur_as_multi->GetSubcommandSPExact(cur_name);
|
||||
cur_as_multi = get_multi_or_report_error(cur_cmd_sp, cur_name);
|
||||
}
|
||||
return cur_as_multi;
|
||||
}
|
||||
|
||||
CommandObjectSP
|
||||
CommandInterpreter::GetCommandSP(llvm::StringRef cmd_str, bool include_aliases,
|
||||
bool exact, StringList *matches,
|
||||
@@ -923,10 +980,17 @@ CommandInterpreter::GetCommandSP(llvm::StringRef cmd_str, bool include_aliases,
|
||||
command_sp = pos->second;
|
||||
}
|
||||
|
||||
if (HasUserMultiwordCommands()) {
|
||||
auto pos = m_user_mw_dict.find(cmd);
|
||||
if (pos != m_user_mw_dict.end())
|
||||
command_sp = pos->second;
|
||||
}
|
||||
|
||||
if (!exact && !command_sp) {
|
||||
// We will only get into here if we didn't find any exact matches.
|
||||
|
||||
CommandObjectSP user_match_sp, alias_match_sp, real_match_sp;
|
||||
CommandObjectSP user_match_sp, user_mw_match_sp, alias_match_sp,
|
||||
real_match_sp;
|
||||
|
||||
StringList local_matches;
|
||||
if (matches == nullptr)
|
||||
@@ -935,6 +999,7 @@ CommandInterpreter::GetCommandSP(llvm::StringRef cmd_str, bool include_aliases,
|
||||
unsigned int num_cmd_matches = 0;
|
||||
unsigned int num_alias_matches = 0;
|
||||
unsigned int num_user_matches = 0;
|
||||
unsigned int num_user_mw_matches = 0;
|
||||
|
||||
// Look through the command dictionaries one by one, and if we get only one
|
||||
// match from any of them in toto, then return that, otherwise return an
|
||||
@@ -978,14 +1043,32 @@ CommandInterpreter::GetCommandSP(llvm::StringRef cmd_str, bool include_aliases,
|
||||
user_match_sp = pos->second;
|
||||
}
|
||||
|
||||
if (HasUserMultiwordCommands()) {
|
||||
num_user_mw_matches = AddNamesMatchingPartialString(
|
||||
m_user_mw_dict, cmd_str, *matches, descriptions);
|
||||
}
|
||||
|
||||
if (num_user_mw_matches == 1) {
|
||||
cmd.assign(matches->GetStringAtIndex(num_cmd_matches + num_alias_matches +
|
||||
num_user_matches));
|
||||
|
||||
auto pos = m_user_mw_dict.find(cmd);
|
||||
if (pos != m_user_mw_dict.end())
|
||||
user_mw_match_sp = pos->second;
|
||||
}
|
||||
|
||||
// If we got exactly one match, return that, otherwise return the match
|
||||
// list.
|
||||
|
||||
if (num_user_matches + num_cmd_matches + num_alias_matches == 1) {
|
||||
if (num_user_matches + num_user_mw_matches + num_cmd_matches +
|
||||
num_alias_matches ==
|
||||
1) {
|
||||
if (num_cmd_matches)
|
||||
return real_match_sp;
|
||||
else if (num_alias_matches)
|
||||
return alias_match_sp;
|
||||
else if (num_user_mw_matches)
|
||||
return user_mw_match_sp;
|
||||
else
|
||||
return user_match_sp;
|
||||
}
|
||||
@@ -1008,6 +1091,8 @@ bool CommandInterpreter::AddCommand(llvm::StringRef name,
|
||||
if (name.empty())
|
||||
return false;
|
||||
|
||||
cmd_sp->SetIsUserCommand(false);
|
||||
|
||||
std::string name_sstr(name);
|
||||
auto name_iter = m_command_dict.find(name_sstr);
|
||||
if (name_iter != m_command_dict.end()) {
|
||||
@@ -1020,33 +1105,49 @@ bool CommandInterpreter::AddCommand(llvm::StringRef name,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CommandInterpreter::AddUserCommand(llvm::StringRef name,
|
||||
const lldb::CommandObjectSP &cmd_sp,
|
||||
bool can_replace) {
|
||||
Status CommandInterpreter::AddUserCommand(llvm::StringRef name,
|
||||
const lldb::CommandObjectSP &cmd_sp,
|
||||
bool can_replace) {
|
||||
Status result;
|
||||
if (cmd_sp.get())
|
||||
lldbassert((this == &cmd_sp->GetCommandInterpreter()) &&
|
||||
"tried to add a CommandObject from a different interpreter");
|
||||
|
||||
if (!name.empty()) {
|
||||
// do not allow replacement of internal commands
|
||||
if (CommandExists(name)) {
|
||||
if (!can_replace)
|
||||
return false;
|
||||
if (!m_command_dict[std::string(name)]->IsRemovable())
|
||||
return false;
|
||||
}
|
||||
|
||||
if (UserCommandExists(name)) {
|
||||
if (!can_replace)
|
||||
return false;
|
||||
if (!m_user_dict[std::string(name)]->IsRemovable())
|
||||
return false;
|
||||
}
|
||||
|
||||
m_user_dict[std::string(name)] = cmd_sp;
|
||||
return true;
|
||||
if (name.empty()) {
|
||||
result.SetErrorString("can't use the empty string for a command name");
|
||||
return result;
|
||||
}
|
||||
return false;
|
||||
// do not allow replacement of internal commands
|
||||
if (CommandExists(name)) {
|
||||
result.SetErrorString("can't replace builtin command");
|
||||
return result;
|
||||
}
|
||||
|
||||
if (UserCommandExists(name)) {
|
||||
if (!can_replace) {
|
||||
result.SetErrorString("user command exists and force replace not set");
|
||||
return result;
|
||||
}
|
||||
if (cmd_sp->IsMultiwordObject()) {
|
||||
if (!m_user_mw_dict[std::string(name)]->IsRemovable()) {
|
||||
result.SetErrorString(
|
||||
"can't replace explicitly non-removable multi-word command");
|
||||
return result;
|
||||
}
|
||||
} else {
|
||||
if (!m_user_dict[std::string(name)]->IsRemovable()) {
|
||||
result.SetErrorString("can't replace explicitly non-removable command");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cmd_sp->SetIsUserCommand(true);
|
||||
|
||||
if (cmd_sp->IsMultiwordObject())
|
||||
m_user_mw_dict[std::string(name)] = cmd_sp;
|
||||
else
|
||||
m_user_dict[std::string(name)] = cmd_sp;
|
||||
return result;
|
||||
}
|
||||
|
||||
CommandObjectSP
|
||||
@@ -1127,6 +1228,44 @@ CommandInterpreter::GetCommandObject(llvm::StringRef cmd_str,
|
||||
return GetCommandSP(cmd_str, true, false, matches, descriptions).get();
|
||||
}
|
||||
|
||||
CommandObject *CommandInterpreter::GetUserCommandObject(
|
||||
llvm::StringRef cmd, StringList *matches, StringList *descriptions) const {
|
||||
std::string cmd_str(cmd);
|
||||
auto find_exact = [&](const CommandObject::CommandMap &map) {
|
||||
auto found_elem = map.find(std::string(cmd));
|
||||
if (found_elem == map.end())
|
||||
return (CommandObject *)nullptr;
|
||||
CommandObject *exact_cmd = found_elem->second.get();
|
||||
if (exact_cmd) {
|
||||
if (matches)
|
||||
matches->AppendString(exact_cmd->GetCommandName());
|
||||
if (descriptions)
|
||||
descriptions->AppendString(exact_cmd->GetHelp());
|
||||
return exact_cmd;
|
||||
}
|
||||
return (CommandObject *)nullptr;
|
||||
};
|
||||
|
||||
CommandObject *exact_cmd = find_exact(GetUserCommands());
|
||||
if (exact_cmd)
|
||||
return exact_cmd;
|
||||
|
||||
exact_cmd = find_exact(GetUserMultiwordCommands());
|
||||
if (exact_cmd)
|
||||
return exact_cmd;
|
||||
|
||||
// We didn't have an exact command, so now look for partial matches.
|
||||
size_t num_found;
|
||||
StringList tmp_list;
|
||||
StringList *matches_ptr = matches ? matches : &tmp_list;
|
||||
num_found =
|
||||
AddNamesMatchingPartialString(GetUserCommands(), cmd_str, *matches_ptr);
|
||||
num_found += AddNamesMatchingPartialString(GetUserMultiwordCommands(),
|
||||
cmd_str, *matches_ptr);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool CommandInterpreter::CommandExists(llvm::StringRef cmd) const {
|
||||
return m_command_dict.find(std::string(cmd)) != m_command_dict.end();
|
||||
}
|
||||
@@ -1169,6 +1308,10 @@ bool CommandInterpreter::UserCommandExists(llvm::StringRef cmd) const {
|
||||
return m_user_dict.find(std::string(cmd)) != m_user_dict.end();
|
||||
}
|
||||
|
||||
bool CommandInterpreter::UserMultiwordCommandExists(llvm::StringRef cmd) const {
|
||||
return m_user_mw_dict.find(std::string(cmd)) != m_user_mw_dict.end();
|
||||
}
|
||||
|
||||
CommandAlias *
|
||||
CommandInterpreter::AddAlias(llvm::StringRef alias_name,
|
||||
lldb::CommandObjectSP &command_obj_sp,
|
||||
@@ -1209,9 +1352,10 @@ bool CommandInterpreter::RemoveCommand(llvm::StringRef cmd) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool CommandInterpreter::RemoveUser(llvm::StringRef alias_name) {
|
||||
|
||||
bool CommandInterpreter::RemoveUser(llvm::StringRef user_name) {
|
||||
CommandObject::CommandMap::iterator pos =
|
||||
m_user_dict.find(std::string(alias_name));
|
||||
m_user_dict.find(std::string(user_name));
|
||||
if (pos != m_user_dict.end()) {
|
||||
m_user_dict.erase(pos);
|
||||
return true;
|
||||
@@ -1219,6 +1363,16 @@ bool CommandInterpreter::RemoveUser(llvm::StringRef alias_name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CommandInterpreter::RemoveUserMultiword(llvm::StringRef multi_name) {
|
||||
CommandObject::CommandMap::iterator pos =
|
||||
m_user_mw_dict.find(std::string(multi_name));
|
||||
if (pos != m_user_mw_dict.end()) {
|
||||
m_user_mw_dict.erase(pos);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CommandInterpreter::GetHelp(CommandReturnObject &result,
|
||||
uint32_t cmd_types) {
|
||||
llvm::StringRef help_prologue(GetDebugger().GetIOHandlerHelpPrologue());
|
||||
@@ -1274,6 +1428,18 @@ void CommandInterpreter::GetHelp(CommandReturnObject &result,
|
||||
result.AppendMessage("");
|
||||
}
|
||||
|
||||
if (!m_user_mw_dict.empty() &&
|
||||
((cmd_types & eCommandTypesUserMW) == eCommandTypesUserMW)) {
|
||||
result.AppendMessage("Current user-defined container commands:");
|
||||
result.AppendMessage("");
|
||||
max_len = FindLongestCommandWord(m_user_mw_dict);
|
||||
for (pos = m_user_dict.begin(); pos != m_user_mw_dict.end(); ++pos) {
|
||||
OutputFormattedHelpText(result.GetOutputStream(), pos->first, "--",
|
||||
pos->second->GetHelp(), max_len);
|
||||
}
|
||||
result.AppendMessage("");
|
||||
}
|
||||
|
||||
result.AppendMessageWithFormat(
|
||||
"For more information on any command, type '%shelp <command-name>'.\n",
|
||||
GetCommandPrefix());
|
||||
@@ -1931,6 +2097,10 @@ bool CommandInterpreter::HasAliases() const { return (!m_alias_dict.empty()); }
|
||||
|
||||
bool CommandInterpreter::HasUserCommands() const { return (!m_user_dict.empty()); }
|
||||
|
||||
bool CommandInterpreter::HasUserMultiwordCommands() const {
|
||||
return (!m_user_mw_dict.empty());
|
||||
}
|
||||
|
||||
bool CommandInterpreter::HasAliasOptions() const { return HasAliases(); }
|
||||
|
||||
void CommandInterpreter::BuildAliasCommandArgs(CommandObject *alias_cmd_obj,
|
||||
@@ -2581,6 +2751,9 @@ void CommandInterpreter::OutputFormattedHelpText(Stream &strm,
|
||||
|
||||
strm.IndentMore(prefix.size());
|
||||
bool prefixed_yet = false;
|
||||
// Even if we have no help text we still want to emit the command name.
|
||||
if (help_text.empty())
|
||||
help_text = "No help text";
|
||||
while (!help_text.empty()) {
|
||||
// Prefix the first line, indent subsequent lines to line up
|
||||
if (!prefixed_yet) {
|
||||
@@ -2700,7 +2873,8 @@ void CommandInterpreter::FindCommandsForApropos(llvm::StringRef search_word,
|
||||
StringList &commands_help,
|
||||
bool search_builtin_commands,
|
||||
bool search_user_commands,
|
||||
bool search_alias_commands) {
|
||||
bool search_alias_commands,
|
||||
bool search_user_mw_commands) {
|
||||
CommandObject::CommandMap::const_iterator pos;
|
||||
|
||||
if (search_builtin_commands)
|
||||
@@ -2711,6 +2885,10 @@ void CommandInterpreter::FindCommandsForApropos(llvm::StringRef search_word,
|
||||
FindCommandsForApropos(search_word, commands_found, commands_help,
|
||||
m_user_dict);
|
||||
|
||||
if (search_user_mw_commands)
|
||||
FindCommandsForApropos(search_word, commands_found, commands_help,
|
||||
m_user_mw_dict);
|
||||
|
||||
if (search_alias_commands)
|
||||
FindCommandsForApropos(search_word, commands_found, commands_help,
|
||||
m_alias_dict);
|
||||
|
||||
@@ -1120,7 +1120,7 @@ CommandObject::ArgumentTableEntry CommandObject::g_arguments_data[] = {
|
||||
{ eArgTypeWatchpointIDRange, "watchpt-id-list", CommandCompletions::eNoCompletion, { nullptr, false }, "For example, '1-3' or '1 to 3'." },
|
||||
{ eArgTypeWatchType, "watch-type", CommandCompletions::eNoCompletion, { nullptr, false }, "Specify the type for a watchpoint." },
|
||||
{ eArgRawInput, "raw-input", CommandCompletions::eNoCompletion, { nullptr, false }, "Free-form text passed to a command without prior interpretation, allowing spaces without requiring quotes. To pass arguments and free form text put two dashes ' -- ' between the last argument and any raw input." },
|
||||
{ eArgTypeCommand, "command", CommandCompletions::eNoCompletion, { nullptr, false }, "An LLDB Command line command." },
|
||||
{ eArgTypeCommand, "command", CommandCompletions::eNoCompletion, { nullptr, false }, "An LLDB Command line command element." },
|
||||
{ eArgTypeColumnNum, "column", CommandCompletions::eNoCompletion, { nullptr, false }, "Column number in a source file." },
|
||||
{ eArgTypeModuleUUID, "module-uuid", CommandCompletions::eModuleUUIDCompletion, { nullptr, false }, "A module UUID value." },
|
||||
{ eArgTypeSaveCoreStyle, "corefile-style", CommandCompletions::eNoCompletion, { nullptr, false }, "The type of corefile that lldb will try to create, dependant on this target's capabilities." }
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
"""
|
||||
Test user added container commands
|
||||
"""
|
||||
|
||||
|
||||
import sys
|
||||
import lldb
|
||||
from lldbsuite.test.decorators import *
|
||||
from lldbsuite.test.lldbtest import *
|
||||
|
||||
|
||||
class TestCmdContainer(TestBase):
|
||||
|
||||
mydir = TestBase.compute_mydir(__file__)
|
||||
NO_DEBUG_INFO_TESTCASE = True
|
||||
|
||||
def test_container_add(self):
|
||||
self.container_add()
|
||||
|
||||
def check_command_tree_exists(self):
|
||||
"""This makes sure we can still run the command tree we added."""
|
||||
self.runCmd("test-multi")
|
||||
self.runCmd("test-multi test-multi-sub")
|
||||
self.runCmd("test-multi test-multi-sub welcome")
|
||||
|
||||
def container_add(self):
|
||||
# Make sure we can't overwrite built-in commands:
|
||||
self.expect("command container add process", "Can't replace builtin container command",
|
||||
substrs=["can't replace builtin command"], error=True)
|
||||
self.expect("command container add process non_such_subcommand", "Can't add to built-in subcommand",
|
||||
substrs=["Path component: 'process' is not a user command"], error=True)
|
||||
self.expect("command container add process launch", "Can't replace builtin subcommand",
|
||||
substrs=["Path component: 'process' is not a user command"], error=True)
|
||||
|
||||
# Now lets make a container command:
|
||||
self.runCmd("command container add -h 'A test container command' test-multi")
|
||||
# Make sure the help works:
|
||||
self.expect("help test-multi", "Help works for top-level multi",
|
||||
substrs=["A test container command"])
|
||||
# Add a subcommand:
|
||||
self.runCmd("command container add -h 'A test container sub-command' test-multi test-multi-sub")
|
||||
# Make sure the help works:
|
||||
self.expect("help test-multi", "Help shows sub-multi",
|
||||
substrs=["A test container command", "test-multi-sub -- A test container sub-command"])
|
||||
self.expect("help test-multi test-multi-sub", "Help shows sub-multi",
|
||||
substrs=["A test container sub-command"])
|
||||
|
||||
# Now add a script based command to the container command:
|
||||
self.runCmd("command script import welcome.py")
|
||||
self.runCmd("command script add -c welcome.WelcomeCommand test-multi test-multi-sub welcome")
|
||||
# Make sure the help still works:
|
||||
self.expect("help test-multi test-multi-sub", "Listing subcommands works",
|
||||
substrs=["A test container sub-command", "welcome -- Just a docstring for Welcome"])
|
||||
self.expect("help test-multi test-multi-sub welcome", "Subcommand help works",
|
||||
substrs=["Just a docstring for Welcome"])
|
||||
# And make sure it actually works:
|
||||
self.expect("test-multi test-multi-sub welcome friend", "Test command works",
|
||||
substrs=["Hello friend, welcome to LLDB"])
|
||||
|
||||
# Make sure overwriting works, first the leaf command:
|
||||
# We should not be able to remove extant commands by default:
|
||||
self.expect("command script add -c welcome.WelcomeCommand2 test-multi test-multi-sub welcome",
|
||||
"overwrite command w/o -o",
|
||||
substrs=["cannot add command: sub-command already exists"], error=True)
|
||||
# But we can with the -o option:
|
||||
self.runCmd("command script add -c welcome.WelcomeCommand2 -o test-multi test-multi-sub welcome")
|
||||
# Make sure we really did overwrite:
|
||||
self.expect("test-multi test-multi-sub welcome friend", "Used the new command class",
|
||||
substrs=["Hello friend, welcome again to LLDB"])
|
||||
|
||||
self.expect("apropos welcome", "welcome should show up in apropos", substrs=["Just a docstring for the second Welcome"])
|
||||
|
||||
# Make sure we give good errors when the input is wrong:
|
||||
self.expect("command script delete test-mult test-multi-sub welcome", "Delete script command - wrong first path component",
|
||||
substrs=["'test-mult' not found"], error=True)
|
||||
|
||||
self.expect("command script delete test-multi test-multi-su welcome", "Delete script command - wrong second path component",
|
||||
substrs=["'test-multi-su' not found"], error=True)
|
||||
self.check_command_tree_exists()
|
||||
|
||||
self.expect("command script delete test-multi test-multi-sub welcom", "Delete script command - wrong leaf component",
|
||||
substrs=["'welcom' not found"], error=True)
|
||||
self.check_command_tree_exists()
|
||||
|
||||
self.expect("command script delete test-multi test-multi-sub", "Delete script command - no leaf component",
|
||||
substrs=["subcommand 'test-multi-sub' is not a user command"], error=True)
|
||||
self.check_command_tree_exists()
|
||||
|
||||
# You can't use command script delete to delete container commands:
|
||||
self.expect("command script delete test-multi", "Delete script - can't delete container",
|
||||
substrs=["command 'test-multi' is a multi-word command."], error=True)
|
||||
self.expect("command script delete test-multi test-multi-sub", "Delete script - can't delete container",
|
||||
substrs=["subcommand 'test-multi-sub' is not a user command"], error = True)
|
||||
|
||||
# You can't use command container delete to delete scripted commands:
|
||||
self.expect("command container delete test-multi test-multi-sub welcome", "command container can't delete user commands",
|
||||
substrs=["subcommand 'welcome' is not a container command"], error = True)
|
||||
|
||||
# Also make sure you can't alias on top of container commands:
|
||||
self.expect("command alias test-multi process launch", "Tried to alias on top of a container command",
|
||||
substrs=["'test-multi' is a user container command and cannot be overwritten."], error=True)
|
||||
self.check_command_tree_exists()
|
||||
|
||||
# Also assert that we can't delete builtin commands:
|
||||
self.expect("command script delete process launch", "Delete builtin command fails", substrs=["command 'process' is not a user command"], error=True)
|
||||
# Now let's do the version that works
|
||||
self.expect("command script delete test-multi test-multi-sub welcome", "Delete script command by path", substrs=["Deleted command: test-multi test-multi-sub welcome"])
|
||||
|
||||
# Now overwrite the sub-command, it should end up empty:
|
||||
self.runCmd("command container add -h 'A different help string' -o test-multi test-multi-sub")
|
||||
# welcome should be gone:
|
||||
self.expect("test-multi test-multi-sub welcome friend", "did remove subcommand",
|
||||
substrs=["'test-multi-sub' does not have any subcommands."], error=True)
|
||||
# We should have the new help:
|
||||
self.expect("help test-multi test-multi-sub", "help changed",
|
||||
substrs=["A different help string"])
|
||||
|
||||
# Now try deleting commands.
|
||||
self.runCmd("command container delete test-multi test-multi-sub")
|
||||
self.expect("test-multi test-multi-sub", "Command is not active", error=True,
|
||||
substrs = ["'test-multi' does not have any subcommands"])
|
||||
self.expect("help test-multi", matching=False, substrs=["test-multi-sub"])
|
||||
|
||||
|
||||
# Next the root command:
|
||||
self.runCmd("command container delete test-multi")
|
||||
self.expect("test-multi", "Root command gone", substrs=["'test-multi' is not a valid command."], error=True)
|
||||
28
lldb/test/API/commands/command/container/welcome.py
Normal file
28
lldb/test/API/commands/command/container/welcome.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from __future__ import print_function
|
||||
import lldb
|
||||
import sys
|
||||
|
||||
|
||||
class WelcomeCommand(object):
|
||||
|
||||
def __init__(self, debugger, session_dict):
|
||||
pass
|
||||
|
||||
def get_short_help(self):
|
||||
return "Just a docstring for Welcome\nA command that says hello to LLDB users"
|
||||
|
||||
def __call__(self, debugger, args, exe_ctx, result):
|
||||
print('Hello ' + args + ', welcome to LLDB', file=result)
|
||||
return None
|
||||
|
||||
class WelcomeCommand2(object):
|
||||
|
||||
def __init__(self, debugger, session_dict):
|
||||
pass
|
||||
|
||||
def get_short_help(self):
|
||||
return "Just a docstring for the second Welcome\nA command that says hello to LLDB users"
|
||||
|
||||
def __call__(self, debugger, args, exe_ctx, result):
|
||||
print('Hello ' + args + ', welcome again to LLDB', file=result)
|
||||
return None
|
||||
@@ -9,10 +9,10 @@ class InvalidArgsCommandTestCase(TestBase):
|
||||
@no_debug_info_test
|
||||
def test_script_add(self):
|
||||
self.expect("command script add 1 2", error=True,
|
||||
substrs=["'command script add' requires one argument"])
|
||||
substrs=["Path component: '1' not found"])
|
||||
|
||||
self.expect("command script add", error=True,
|
||||
substrs=["'command script add' requires one argument"])
|
||||
substrs=["'command script add' requires at least one argument"])
|
||||
|
||||
@no_debug_info_test
|
||||
def test_script_clear(self):
|
||||
|
||||
@@ -147,7 +147,7 @@ class CmdPythonTestCase(TestBase):
|
||||
self.expect('my_command Blah', substrs=['Hello Blah, welcome to LLDB'])
|
||||
|
||||
self.runCmd(
|
||||
'command script add my_command --class welcome.TargetnameCommand')
|
||||
'command script add my_command -o --class welcome.TargetnameCommand')
|
||||
self.expect('my_command', substrs=['a.out'])
|
||||
|
||||
self.runCmd("command script clear")
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#include <stdio.h>
|
||||
|
||||
int foo(char c) { return 1; }
|
||||
int foo(signed char c) { return 2; }
|
||||
int foo(unsigned char c) { return 3; }
|
||||
@@ -6,5 +8,6 @@ int main() {
|
||||
char c = 0;
|
||||
signed char sc = 0;
|
||||
unsigned char uc = 0;
|
||||
printf("%d %d %d\n", foo(c), foo(sc), foo(uc));
|
||||
return 0; // Break here
|
||||
}
|
||||
|
||||
@@ -510,7 +510,7 @@ class CommandLineCompletionTestCase(TestBase):
|
||||
|
||||
def test_command_script_delete(self):
|
||||
self.runCmd("command script add -h test_desc -f none -s current usercmd1")
|
||||
self.check_completion_with_desc('command script delete ', [['usercmd1', 'test_desc']])
|
||||
self.check_completion_with_desc('command script delete ', [['usercmd1', '']])
|
||||
|
||||
def test_command_delete(self):
|
||||
self.runCmd(r"command regex test_command s/^$/finish/ 's/([0-9]+)/frame select %1/'")
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
add_lldb_unittest(InterpreterTests
|
||||
TestCommandPaths.cpp
|
||||
TestCompletion.cpp
|
||||
TestOptionArgParser.cpp
|
||||
TestOptionValue.cpp
|
||||
TestOptionValueFileColonLine.cpp
|
||||
|
||||
LINK_LIBS
|
||||
lldbInterpreter
|
||||
lldbUtilityHelpers
|
||||
)
|
||||
lldbCore
|
||||
lldbHost
|
||||
lldbTarget
|
||||
lldbSymbol
|
||||
lldbUtility
|
||||
lldbUtilityHelpers
|
||||
lldbInterpreter
|
||||
lldbPluginPlatformMacOSX
|
||||
)
|
||||
|
||||
159
lldb/unittests/Interpreter/TestCommandPaths.cpp
Normal file
159
lldb/unittests/Interpreter/TestCommandPaths.cpp
Normal file
@@ -0,0 +1,159 @@
|
||||
//===-- ProcessEventDataTest.cpp ------------------------------------------===//
|
||||
//
|
||||
// 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 "Plugins/Platform/MacOSX/PlatformMacOSX.h"
|
||||
#include "lldb/Core/Debugger.h"
|
||||
#include "lldb/Host/FileSystem.h"
|
||||
#include "lldb/Host/HostInfo.h"
|
||||
#include "lldb/Interpreter/CommandInterpreter.h"
|
||||
#include "lldb/Interpreter/CommandObject.h"
|
||||
#include "lldb/Interpreter/CommandObjectMultiword.h"
|
||||
#include "lldb/Interpreter/CommandReturnObject.h"
|
||||
#include "lldb/Utility/Args.h"
|
||||
#include "lldb/Utility/Reproducer.h"
|
||||
#include "lldb/Utility/Status.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
using namespace lldb_private;
|
||||
using namespace lldb_private::repro;
|
||||
using namespace lldb;
|
||||
|
||||
namespace {
|
||||
class VerifyUserMultiwordCmdPathTest : public ::testing::Test {
|
||||
void SetUp() override {
|
||||
llvm::cantFail(Reproducer::Initialize(ReproducerMode::Off, llvm::None));
|
||||
FileSystem::Initialize();
|
||||
HostInfo::Initialize();
|
||||
PlatformMacOSX::Initialize();
|
||||
}
|
||||
void TearDown() override {
|
||||
PlatformMacOSX::Terminate();
|
||||
HostInfo::Terminate();
|
||||
FileSystem::Terminate();
|
||||
Reproducer::Terminate();
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
class CommandObjectLeaf : public CommandObjectParsed {
|
||||
public:
|
||||
CommandObjectLeaf(CommandInterpreter &interpreter)
|
||||
: CommandObjectParsed(interpreter, "dummy subcommand leaf",
|
||||
"Does nothing", "dummy subcommand leaf") {
|
||||
SetIsUserCommand(true);
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual bool DoExecute(Args &command, CommandReturnObject &result) {
|
||||
result.SetStatus(eReturnStatusSuccessFinishResult);
|
||||
result.AppendMessage("I did nothing");
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class CommandObjectMultiwordSubDummy : public CommandObjectMultiword {
|
||||
public:
|
||||
CommandObjectMultiwordSubDummy(CommandInterpreter &interpreter)
|
||||
: CommandObjectMultiword(interpreter, "dummy subcommand", "Does nothing",
|
||||
"dummy subcommand") {
|
||||
SetIsUserCommand(true);
|
||||
LoadSubCommand("leaf", CommandObjectSP(new CommandObjectLeaf(interpreter)));
|
||||
}
|
||||
|
||||
~CommandObjectMultiwordSubDummy() override = default;
|
||||
};
|
||||
|
||||
class CommandObjectMultiwordDummy : public CommandObjectMultiword {
|
||||
public:
|
||||
CommandObjectMultiwordDummy(CommandInterpreter &interpreter)
|
||||
: CommandObjectMultiword(interpreter, "dummy", "Does nothing", "dummy") {
|
||||
SetIsUserCommand(true);
|
||||
LoadSubCommand(
|
||||
"subcommand",
|
||||
CommandObjectSP(new CommandObjectMultiwordSubDummy(interpreter)));
|
||||
}
|
||||
|
||||
~CommandObjectMultiwordDummy() override = default;
|
||||
};
|
||||
|
||||
// Pass in the command path to args. If success is true, we make sure the MWC
|
||||
// returned matches the test string. If success is false, we make sure the
|
||||
// lookup error matches test_str.
|
||||
void RunTest(CommandInterpreter &interp, const char *args, bool is_leaf,
|
||||
bool success, const char *test_str) {
|
||||
CommandObjectMultiword *multi_word_cmd = nullptr;
|
||||
Args test_args(args);
|
||||
Status error;
|
||||
multi_word_cmd =
|
||||
interp.VerifyUserMultiwordCmdPath(test_args, is_leaf, error);
|
||||
if (success) {
|
||||
ASSERT_NE(multi_word_cmd, nullptr);
|
||||
ASSERT_TRUE(error.Success());
|
||||
ASSERT_STREQ(multi_word_cmd->GetCommandName().str().c_str(), test_str);
|
||||
} else {
|
||||
ASSERT_EQ(multi_word_cmd, nullptr);
|
||||
ASSERT_TRUE(error.Fail());
|
||||
ASSERT_STREQ(error.AsCString(), test_str);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(VerifyUserMultiwordCmdPathTest, TestErrors) {
|
||||
DebuggerSP debugger_sp = Debugger::CreateInstance();
|
||||
ASSERT_TRUE(debugger_sp);
|
||||
|
||||
CommandInterpreter &interp = debugger_sp->GetCommandInterpreter();
|
||||
|
||||
Status error;
|
||||
bool success;
|
||||
bool is_leaf;
|
||||
|
||||
// Test that we reject non-user path components:
|
||||
success = false;
|
||||
is_leaf = true;
|
||||
RunTest(interp, "process launch", is_leaf, success,
|
||||
"Path component: 'process' is not a user command");
|
||||
|
||||
// Test that we reject non-existent commands:
|
||||
is_leaf = true;
|
||||
success = false;
|
||||
RunTest(interp, "wewouldnevernameacommandthis subcommand", is_leaf, success,
|
||||
"Path component: 'wewouldnevernameacommandthis' not found");
|
||||
|
||||
// Now we have to add a multiword command, and then probe it.
|
||||
error = interp.AddUserCommand(
|
||||
"dummy", CommandObjectSP(new CommandObjectMultiwordDummy(interp)), true);
|
||||
ASSERT_TRUE(error.Success());
|
||||
|
||||
// Now pass the correct path, and make sure we get back the right MWC.
|
||||
is_leaf = false;
|
||||
success = true;
|
||||
RunTest(interp, "dummy subcommand", is_leaf, success, "dummy subcommand");
|
||||
|
||||
is_leaf = true;
|
||||
RunTest(interp, "dummy subcommand", is_leaf, success, "dummy");
|
||||
|
||||
// If you tell us the last node is a leaf, we don't check that. Make sure
|
||||
// that is true:
|
||||
is_leaf = true;
|
||||
success = true;
|
||||
RunTest(interp, "dummy subcommand leaf", is_leaf, success,
|
||||
"dummy subcommand");
|
||||
// But we should fail if we say the last component is a multiword:
|
||||
|
||||
is_leaf = false;
|
||||
success = false;
|
||||
RunTest(interp, "dummy subcommand leaf", is_leaf, success,
|
||||
"Path component: 'leaf' is not a container command");
|
||||
|
||||
// We should fail if we get the second path component wrong:
|
||||
is_leaf = false;
|
||||
success = false;
|
||||
RunTest(interp, "dummy not-subcommand", is_leaf, success,
|
||||
"Path component: 'not-subcommand' not found");
|
||||
}
|
||||
Reference in New Issue
Block a user