[Lldb-commits] [lldb] c5011ae - Add a "command container" hierarchy to allow users to add container nodes.
Jim Ingham via lldb-commits
lldb-commits at lists.llvm.org
Mon Oct 18 15:29:31 PDT 2021
Author: Jim Ingham
Date: 2021-10-18T15:29:24-07:00
New Revision: c5011aed9c297d6ddd8ee4f77453b215aa27554a
URL: https://github.com/llvm/llvm-project/commit/c5011aed9c297d6ddd8ee4f77453b215aa27554a
DIFF: https://github.com/llvm/llvm-project/commit/c5011aed9c297d6ddd8ee4f77453b215aa27554a.diff
LOG: 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
Added:
lldb/test/API/commands/command/container/TestContainerCommands.py
lldb/test/API/commands/command/container/welcome.py
lldb/unittests/Interpreter/TestCommandPaths.cpp
Modified:
lldb/include/lldb/Interpreter/CommandCompletions.h
lldb/include/lldb/Interpreter/CommandInterpreter.h
lldb/include/lldb/Interpreter/CommandObject.h
lldb/include/lldb/Interpreter/CommandObjectMultiword.h
lldb/source/API/SBCommandInterpreter.cpp
lldb/source/Commands/CommandCompletions.cpp
lldb/source/Commands/CommandObjectApropos.cpp
lldb/source/Commands/CommandObjectCommands.cpp
lldb/source/Commands/CommandObjectHelp.cpp
lldb/source/Commands/CommandObjectMultiword.cpp
lldb/source/Commands/Options.td
lldb/source/Interpreter/CommandInterpreter.cpp
lldb/source/Interpreter/CommandObject.cpp
lldb/test/API/commands/command/invalid-args/TestInvalidArgsCommand.py
lldb/test/API/commands/command/script/TestCommandScript.py
lldb/test/API/commands/expression/char/main.cpp
lldb/test/API/functionalities/completion/TestCompletion.py
lldb/unittests/Interpreter/CMakeLists.txt
Removed:
################################################################################
diff --git a/lldb/include/lldb/Interpreter/CommandCompletions.h b/lldb/include/lldb/Interpreter/CommandCompletions.h
index c80bde0e719bf..c13bc4997ff3e 100644
--- a/lldb/include/lldb/Interpreter/CommandCompletions.h
+++ b/lldb/include/lldb/Interpreter/CommandCompletions.h
@@ -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 @@ class CommandCompletions {
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
diff --git a/lldb/include/lldb/Interpreter/CommandInterpreter.h b/lldb/include/lldb/Interpreter/CommandInterpreter.h
index 3b3daced3e336..e6f0d5f9c4d43 100644
--- a/lldb/include/lldb/Interpreter/CommandInterpreter.h
+++ b/lldb/include/lldb/Interpreter/CommandInterpreter.h
@@ -231,11 +231,12 @@ class CommandInterpreter : public Broadcaster,
};
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 @@ class CommandInterpreter : public Broadcaster,
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 @@ class CommandInterpreter : public Broadcaster,
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 @@ class CommandInterpreter : public Broadcaster,
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 @@ class CommandInterpreter : public Broadcaster,
bool HasUserCommands() const;
+ bool HasUserMultiwordCommands() const;
+
bool HasAliasOptions() const;
void BuildAliasCommandArgs(CommandObject *alias_cmd_obj,
@@ -421,6 +466,7 @@ class CommandInterpreter : public Broadcaster,
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 @@ class CommandInterpreter : public Broadcaster,
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 @@ class CommandInterpreter : public Broadcaster,
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 @@ class CommandInterpreter : public Broadcaster,
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.
diff --git a/lldb/include/lldb/Interpreter/CommandObject.h b/lldb/include/lldb/Interpreter/CommandObject.h
index 8bc5d3e22355d..89cc161993a9f 100644
--- a/lldb/include/lldb/Interpreter/CommandObject.h
+++ b/lldb/include/lldb/Interpreter/CommandObject.h
@@ -145,6 +145,10 @@ class CommandObject {
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 @@ class CommandObject {
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 @@ class CommandObject {
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 @@ class CommandObject {
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.
diff --git a/lldb/include/lldb/Interpreter/CommandObjectMultiword.h b/lldb/include/lldb/Interpreter/CommandObjectMultiword.h
index f330a745f9bd2..a0e8d163c4b6d 100644
--- a/lldb/include/lldb/Interpreter/CommandObjectMultiword.h
+++ b/lldb/include/lldb/Interpreter/CommandObjectMultiword.h
@@ -35,11 +35,19 @@ class CommandObjectMultiword : public CommandObject {
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;
diff --git a/lldb/source/API/SBCommandInterpreter.cpp b/lldb/source/API/SBCommandInterpreter.cpp
index b4a69c3e972a4..3830f6ed80baf 100644
--- a/lldb/source/API/SBCommandInterpreter.cpp
+++ b/lldb/source/API/SBCommandInterpreter.cpp
@@ -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());
}
diff --git a/lldb/source/Commands/CommandCompletions.cpp b/lldb/source/Commands/CommandCompletions.cpp
index 191c25f5e6771..42b0bac717bd4 100644
--- a/lldb/source/Commands/CommandCompletions.cpp
+++ b/lldb/source/Commands/CommandCompletions.cpp
@@ -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);
+}
diff --git a/lldb/source/Commands/CommandObjectApropos.cpp b/lldb/source/Commands/CommandObjectApropos.cpp
index 656487169a34d..c6680f8b140d1 100644
--- a/lldb/source/Commands/CommandObjectApropos.cpp
+++ b/lldb/source/Commands/CommandObjectApropos.cpp
@@ -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'. "
diff --git a/lldb/source/Commands/CommandObjectCommands.cpp b/lldb/source/Commands/CommandObjectCommands.cpp
index 639279875e715..1ec54cf7ededa 100644
--- a/lldb/source/Commands/CommandObjectCommands.cpp
+++ b/lldb/source/Commands/CommandObjectCommands.cpp
@@ -443,6 +443,14 @@ rather than using a positional placeholder:"
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 @@ rather than using a positional placeholder:"
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 @@ class CommandObjectCommandsScriptAdd : public CommandObjectParsed,
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 @@ class CommandObjectCommandsScriptAdd : public CommandObjectParsed,
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 @@ class CommandObjectCommandsScriptAdd : public CommandObjectParsed,
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 @@ class CommandObjectCommandsScriptAdd : public CommandObjectParsed,
m_class_name.clear();
m_funct_name.clear();
m_short_help.clear();
+ m_overwrite = false;
m_synchronicity = eScriptedCommandSynchronicitySynchronous;
}
@@ -1450,6 +1484,7 @@ class CommandObjectCommandsScriptAdd : public CommandObjectParsed,
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 @@ class CommandObjectCommandsScriptAdd : public CommandObjectParsed,
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 @@ class CommandObjectCommandsScriptAdd : public CommandObjectParsed,
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_cmd_name = std::string(command[0].ref());
+ 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;
+ }
+
+ 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 @@ class CommandObjectCommandsScriptAdd : public CommandObjectParsed,
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 @@ class CommandObjectCommandsScriptClear : public CommandObjectParsed {
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 @@ class CommandObjectCommandsScriptDelete : public CommandObjectParsed {
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();
+ 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;
+ }
- if (cmd_name.empty() || !m_interpreter.HasUserCommands() ||
- !m_interpreter.UserCommandExists(cmd_name)) {
- result.AppendErrorWithFormat("command %s not found", command[0].c_str());
+ 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;
}
- m_interpreter.RemoveUser(cmd_name);
+ 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 @@ class CommandObjectMultiwordCommandsScript : public CommandObjectMultiword {
~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(
diff --git a/lldb/source/Commands/CommandObjectHelp.cpp b/lldb/source/Commands/CommandObjectHelp.cpp
index 4643ee30f0f9b..8c24efaa08ee3 100644
--- a/lldb/source/Commands/CommandObjectHelp.cpp
+++ b/lldb/source/Commands/CommandObjectHelp.cpp
@@ -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;
diff --git a/lldb/source/Commands/CommandObjectMultiword.cpp b/lldb/source/Commands/CommandObjectMultiword.cpp
index a523fd0b1560c..e800bcc12bd36 100644
--- a/lldb/source/Commands/CommandObjectMultiword.cpp
+++ b/lldb/source/Commands/CommandObjectMultiword.cpp
@@ -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
diff erent 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
diff erent 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);
diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td
index 3d69bb8ad8d05..b9f8950d2cace 100644
--- a/lldb/source/Commands/Options.td
+++ b/lldb/source/Commands/Options.td
@@ -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 "
diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp
index 223cccf28d74b..1c43ea15af01b 100644
--- a/lldb/source/Interpreter/CommandInterpreter.cpp
+++ b/lldb/source/Interpreter/CommandInterpreter.cpp
@@ -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
diff erent 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 (name.empty()) {
+ result.SetErrorString("can't use the empty string for a command name");
+ return result;
+ }
+ // 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;
+ }
}
+ }
- if (UserCommandExists(name)) {
- if (!can_replace)
- return false;
- if (!m_user_dict[std::string(name)]->IsRemovable())
- return false;
- }
+ 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 true;
- }
- return false;
+ 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);
diff --git a/lldb/source/Interpreter/CommandObject.cpp b/lldb/source/Interpreter/CommandObject.cpp
index a7dcd56827017..64b23d04abea3 100644
--- a/lldb/source/Interpreter/CommandObject.cpp
+++ b/lldb/source/Interpreter/CommandObject.cpp
@@ -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." }
diff --git a/lldb/test/API/commands/command/container/TestContainerCommands.py b/lldb/test/API/commands/command/container/TestContainerCommands.py
new file mode 100644
index 0000000000000..408303dd43a54
--- /dev/null
+++ b/lldb/test/API/commands/command/container/TestContainerCommands.py
@@ -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
diff erent 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
diff erent 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)
diff --git a/lldb/test/API/commands/command/container/welcome.py b/lldb/test/API/commands/command/container/welcome.py
new file mode 100644
index 0000000000000..6283e69564006
--- /dev/null
+++ b/lldb/test/API/commands/command/container/welcome.py
@@ -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
diff --git a/lldb/test/API/commands/command/invalid-args/TestInvalidArgsCommand.py b/lldb/test/API/commands/command/invalid-args/TestInvalidArgsCommand.py
index 47d77b0e56962..3a9a4a39d89d8 100644
--- a/lldb/test/API/commands/command/invalid-args/TestInvalidArgsCommand.py
+++ b/lldb/test/API/commands/command/invalid-args/TestInvalidArgsCommand.py
@@ -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):
diff --git a/lldb/test/API/commands/command/script/TestCommandScript.py b/lldb/test/API/commands/command/script/TestCommandScript.py
index b103298f24e10..975a0a538ed67 100644
--- a/lldb/test/API/commands/command/script/TestCommandScript.py
+++ b/lldb/test/API/commands/command/script/TestCommandScript.py
@@ -147,7 +147,7 @@ def cleanup():
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")
diff --git a/lldb/test/API/commands/expression/char/main.cpp b/lldb/test/API/commands/expression/char/main.cpp
index c8b0beb1b3553..9ff4436d88a08 100644
--- a/lldb/test/API/commands/expression/char/main.cpp
+++ b/lldb/test/API/commands/expression/char/main.cpp
@@ -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
}
diff --git a/lldb/test/API/functionalities/completion/TestCompletion.py b/lldb/test/API/functionalities/completion/TestCompletion.py
index 1688612c57854..ed901890f7dfe 100644
--- a/lldb/test/API/functionalities/completion/TestCompletion.py
+++ b/lldb/test/API/functionalities/completion/TestCompletion.py
@@ -510,7 +510,7 @@ def test_command_argument_completion(self):
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/'")
diff --git a/lldb/unittests/Interpreter/CMakeLists.txt b/lldb/unittests/Interpreter/CMakeLists.txt
index 6ea5996e2b036..2080ce9085400 100644
--- a/lldb/unittests/Interpreter/CMakeLists.txt
+++ b/lldb/unittests/Interpreter/CMakeLists.txt
@@ -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
+)
diff --git a/lldb/unittests/Interpreter/TestCommandPaths.cpp b/lldb/unittests/Interpreter/TestCommandPaths.cpp
new file mode 100644
index 0000000000000..2a654d7fe7042
--- /dev/null
+++ b/lldb/unittests/Interpreter/TestCommandPaths.cpp
@@ -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");
+}
More information about the lldb-commits
mailing list