| 1 | //===-- CommandObjectMultiword.cpp ----------------------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #include "lldb/Interpreter/CommandObjectMultiword.h" |
| 10 | #include "lldb/Interpreter/CommandInterpreter.h" |
| 11 | #include "lldb/Interpreter/CommandReturnObject.h" |
| 12 | #include "lldb/Interpreter/Options.h" |
| 13 | #include <optional> |
| 14 | |
| 15 | using namespace lldb; |
| 16 | using namespace lldb_private; |
| 17 | |
| 18 | // CommandObjectMultiword |
| 19 | |
| 20 | CommandObjectMultiword::CommandObjectMultiword(CommandInterpreter &interpreter, |
| 21 | const char *name, |
| 22 | const char *help, |
| 23 | const char *syntax, |
| 24 | uint32_t flags) |
| 25 | : CommandObject(interpreter, name, help, syntax, flags), |
| 26 | m_can_be_removed(false) {} |
| 27 | |
| 28 | CommandObjectMultiword::~CommandObjectMultiword() = default; |
| 29 | |
| 30 | CommandObjectSP |
| 31 | CommandObjectMultiword::GetSubcommandSPExact(llvm::StringRef sub_cmd) { |
| 32 | if (m_subcommand_dict.empty()) |
| 33 | return {}; |
| 34 | |
| 35 | auto pos = m_subcommand_dict.find(x: sub_cmd); |
| 36 | if (pos == m_subcommand_dict.end()) |
| 37 | return {}; |
| 38 | |
| 39 | return pos->second; |
| 40 | } |
| 41 | |
| 42 | CommandObjectSP CommandObjectMultiword::GetSubcommandSP(llvm::StringRef sub_cmd, |
| 43 | StringList *matches) { |
| 44 | if (m_subcommand_dict.empty()) |
| 45 | return {}; |
| 46 | |
| 47 | CommandObjectSP return_cmd_sp = GetSubcommandSPExact(sub_cmd); |
| 48 | if (return_cmd_sp) { |
| 49 | if (matches) |
| 50 | matches->AppendString(str: sub_cmd); |
| 51 | return return_cmd_sp; |
| 52 | } |
| 53 | |
| 54 | CommandObject::CommandMap::iterator pos; |
| 55 | |
| 56 | StringList local_matches; |
| 57 | if (matches == nullptr) |
| 58 | matches = &local_matches; |
| 59 | int num_matches = |
| 60 | AddNamesMatchingPartialString(in_map: m_subcommand_dict, cmd_str: sub_cmd, matches&: *matches); |
| 61 | |
| 62 | if (num_matches == 1) { |
| 63 | // Cleaner, but slightly less efficient would be to call back into this |
| 64 | // function, since I now know I have an exact match... |
| 65 | |
| 66 | sub_cmd = matches->GetStringAtIndex(idx: 0); |
| 67 | pos = m_subcommand_dict.find(x: sub_cmd); |
| 68 | if (pos != m_subcommand_dict.end()) |
| 69 | return_cmd_sp = pos->second; |
| 70 | } |
| 71 | |
| 72 | return return_cmd_sp; |
| 73 | } |
| 74 | |
| 75 | CommandObject * |
| 76 | CommandObjectMultiword::GetSubcommandObject(llvm::StringRef sub_cmd, |
| 77 | StringList *matches) { |
| 78 | return GetSubcommandSP(sub_cmd, matches).get(); |
| 79 | } |
| 80 | |
| 81 | bool CommandObjectMultiword::LoadSubCommand(llvm::StringRef name, |
| 82 | const CommandObjectSP &cmd_obj_sp) { |
| 83 | if (cmd_obj_sp) |
| 84 | lldbassert((&GetCommandInterpreter() == &cmd_obj_sp->GetCommandInterpreter()) && |
| 85 | "tried to add a CommandObject from a different interpreter" ); |
| 86 | |
| 87 | return m_subcommand_dict.try_emplace(k: std::string(name), args: cmd_obj_sp).second; |
| 88 | } |
| 89 | |
| 90 | llvm::Error CommandObjectMultiword::LoadUserSubcommand( |
| 91 | llvm::StringRef name, const CommandObjectSP &cmd_obj_sp, bool can_replace) { |
| 92 | Status result; |
| 93 | if (cmd_obj_sp) |
| 94 | lldbassert((&GetCommandInterpreter() == &cmd_obj_sp->GetCommandInterpreter()) && |
| 95 | "tried to add a CommandObject from a different interpreter" ); |
| 96 | if (!IsUserCommand()) { |
| 97 | return llvm::createStringError(EC: llvm::inconvertibleErrorCode(), |
| 98 | S: "can't add a user subcommand to a builtin container command." ); |
| 99 | } |
| 100 | // Make sure this a user command if it isn't already: |
| 101 | cmd_obj_sp->SetIsUserCommand(true); |
| 102 | |
| 103 | std::string str_name(name); |
| 104 | |
| 105 | auto [pos, inserted] = m_subcommand_dict.try_emplace(k: str_name, args: cmd_obj_sp); |
| 106 | if (inserted) |
| 107 | return llvm::Error::success(); |
| 108 | |
| 109 | const char *error_str = nullptr; |
| 110 | if (!can_replace) |
| 111 | error_str = "sub-command already exists" ; |
| 112 | if (!(*pos).second->IsUserCommand()) |
| 113 | error_str = "can't replace a builtin subcommand" ; |
| 114 | |
| 115 | if (error_str) { |
| 116 | return llvm::createStringError(EC: llvm::inconvertibleErrorCode(), S: error_str); |
| 117 | } |
| 118 | pos->second = cmd_obj_sp; |
| 119 | return llvm::Error::success(); |
| 120 | } |
| 121 | |
| 122 | llvm::Error CommandObjectMultiword::RemoveUserSubcommand(llvm::StringRef cmd_name, |
| 123 | bool must_be_multiword) { |
| 124 | CommandMap::iterator pos; |
| 125 | std::string str_name(cmd_name); |
| 126 | |
| 127 | pos = m_subcommand_dict.find(x: str_name); |
| 128 | if (pos == m_subcommand_dict.end()) { |
| 129 | return llvm::createStringError(EC: llvm::inconvertibleErrorCode(),Fmt: "subcommand '%s' not found." , |
| 130 | Vals: str_name.c_str()); |
| 131 | } |
| 132 | if (!(*pos).second->IsUserCommand()) { |
| 133 | return llvm::createStringError(EC: llvm::inconvertibleErrorCode(),Fmt: "subcommand '%s' not a user command." , |
| 134 | Vals: str_name.c_str()); |
| 135 | } |
| 136 | |
| 137 | if (must_be_multiword && !(*pos).second->IsMultiwordObject()) { |
| 138 | return llvm::createStringError(EC: llvm::inconvertibleErrorCode(),Fmt: "subcommand '%s' is not a container command" , |
| 139 | Vals: str_name.c_str()); |
| 140 | } |
| 141 | if (!must_be_multiword && (*pos).second->IsMultiwordObject()) { |
| 142 | return llvm::createStringError(EC: llvm::inconvertibleErrorCode(),Fmt: "subcommand '%s' is not a user command" , |
| 143 | Vals: str_name.c_str()); |
| 144 | } |
| 145 | |
| 146 | m_subcommand_dict.erase(position: pos); |
| 147 | |
| 148 | return llvm::Error::success(); |
| 149 | } |
| 150 | |
| 151 | void CommandObjectMultiword::Execute(const char *args_string, |
| 152 | CommandReturnObject &result) { |
| 153 | Args args(args_string); |
| 154 | const size_t argc = args.GetArgumentCount(); |
| 155 | if (argc == 0) { |
| 156 | this->CommandObject::GenerateHelpText(result); |
| 157 | return; |
| 158 | } |
| 159 | |
| 160 | auto sub_command = args[0].ref(); |
| 161 | if (sub_command.empty()) { |
| 162 | result.AppendError(in_string: "Need to specify a non-empty subcommand." ); |
| 163 | return; |
| 164 | } |
| 165 | |
| 166 | if (m_subcommand_dict.empty()) { |
| 167 | result.AppendErrorWithFormat(format: "'%s' does not have any subcommands.\n" , |
| 168 | GetCommandName().str().c_str()); |
| 169 | return; |
| 170 | } |
| 171 | |
| 172 | StringList matches; |
| 173 | CommandObject *sub_cmd_obj = GetSubcommandObject(sub_cmd: sub_command, matches: &matches); |
| 174 | if (sub_cmd_obj != nullptr) { |
| 175 | // Now call CommandObject::Execute to process options in `rest_of_line`. |
| 176 | // From there the command-specific version of Execute will be called, with |
| 177 | // the processed arguments. |
| 178 | |
| 179 | args.Shift(); |
| 180 | sub_cmd_obj->Execute(args_string, result); |
| 181 | return; |
| 182 | } |
| 183 | |
| 184 | std::string error_msg; |
| 185 | const size_t num_subcmd_matches = matches.GetSize(); |
| 186 | if (num_subcmd_matches > 0) { |
| 187 | error_msg.assign(s: "ambiguous command " ); |
| 188 | error_msg.append(s: "'" ); |
| 189 | error_msg.append(str: std::string(GetCommandName())); |
| 190 | error_msg.append(s: " " ); |
| 191 | error_msg.append(str: std::string(sub_command)); |
| 192 | error_msg.append(s: "'." ); |
| 193 | |
| 194 | error_msg.append(s: " Possible completions:" ); |
| 195 | for (const std::string &match : matches) { |
| 196 | error_msg.append(s: "\n\t" ); |
| 197 | error_msg.append(str: match); |
| 198 | } |
| 199 | } else { |
| 200 | // Try to offer some alternatives to help correct the command. |
| 201 | error_msg.assign( |
| 202 | str: llvm::Twine("\"" + sub_command + "\" is not a valid subcommand of \"" + |
| 203 | GetCommandName() + "\"." + GetSubcommandsHintText() + |
| 204 | " Use \"help " + GetCommandName() + "\" to find out more." ) |
| 205 | .str()); |
| 206 | } |
| 207 | error_msg.append(s: "\n" ); |
| 208 | result.AppendRawError(in_string: error_msg.c_str()); |
| 209 | } |
| 210 | |
| 211 | std::string CommandObjectMultiword::GetSubcommandsHintText() { |
| 212 | if (m_subcommand_dict.empty()) |
| 213 | return "" ; |
| 214 | const size_t maxCount = 5; |
| 215 | size_t i = 0; |
| 216 | std::string buffer = " Valid subcommand" ; |
| 217 | buffer.append(s: m_subcommand_dict.size() > 1 ? "s are:" : " is" ); |
| 218 | CommandMap::iterator pos; |
| 219 | for (pos = m_subcommand_dict.begin(); |
| 220 | pos != m_subcommand_dict.end() && i < maxCount; ++pos, ++i) { |
| 221 | buffer.append(s: " " ); |
| 222 | buffer.append(str: pos->first); |
| 223 | buffer.append(s: "," ); |
| 224 | } |
| 225 | if (i < m_subcommand_dict.size()) |
| 226 | buffer.append(s: " and others" ); |
| 227 | else |
| 228 | buffer.pop_back(); |
| 229 | |
| 230 | buffer.append(s: "." ); |
| 231 | return buffer; |
| 232 | } |
| 233 | |
| 234 | void CommandObjectMultiword::GenerateHelpText(Stream &output_stream) { |
| 235 | // First time through here, generate the help text for the object and push it |
| 236 | // to the return result object as well |
| 237 | |
| 238 | CommandObject::GenerateHelpText(result&: output_stream); |
| 239 | output_stream.PutCString(cstr: "\nThe following subcommands are supported:\n\n" ); |
| 240 | |
| 241 | CommandMap::iterator pos; |
| 242 | uint32_t max_len = FindLongestCommandWord(dict&: m_subcommand_dict); |
| 243 | |
| 244 | if (max_len) |
| 245 | max_len += 4; // Indent the output by 4 spaces. |
| 246 | |
| 247 | for (pos = m_subcommand_dict.begin(); pos != m_subcommand_dict.end(); ++pos) { |
| 248 | std::string indented_command(" " ); |
| 249 | indented_command.append(str: pos->first); |
| 250 | if (pos->second->WantsRawCommandString()) { |
| 251 | std::string help_text(std::string(pos->second->GetHelp())); |
| 252 | help_text.append(s: " Expects 'raw' input (see 'help raw-input'.)" ); |
| 253 | m_interpreter.OutputFormattedHelpText(stream&: output_stream, command_word: indented_command, |
| 254 | separator: "--" , help_text, max_word_len: max_len); |
| 255 | } else |
| 256 | m_interpreter.OutputFormattedHelpText(stream&: output_stream, command_word: indented_command, |
| 257 | separator: "--" , help_text: pos->second->GetHelp(), |
| 258 | max_word_len: max_len); |
| 259 | } |
| 260 | |
| 261 | output_stream.PutCString(cstr: "\nFor more help on any particular subcommand, type " |
| 262 | "'help <command> <subcommand>'.\n" ); |
| 263 | } |
| 264 | |
| 265 | void CommandObjectMultiword::HandleCompletion(CompletionRequest &request) { |
| 266 | auto arg0 = request.GetParsedLine()[0].ref(); |
| 267 | if (request.GetCursorIndex() == 0) { |
| 268 | StringList new_matches, descriptions; |
| 269 | AddNamesMatchingPartialString(in_map: m_subcommand_dict, cmd_str: arg0, matches&: new_matches, |
| 270 | descriptions: &descriptions); |
| 271 | request.AddCompletions(completions: new_matches, descriptions); |
| 272 | |
| 273 | if (new_matches.GetSize() == 1 && |
| 274 | new_matches.GetStringAtIndex(idx: 0) != nullptr && |
| 275 | (arg0 == new_matches.GetStringAtIndex(idx: 0))) { |
| 276 | StringList temp_matches; |
| 277 | CommandObject *cmd_obj = GetSubcommandObject(sub_cmd: arg0, matches: &temp_matches); |
| 278 | if (cmd_obj != nullptr) { |
| 279 | if (request.GetParsedLine().GetArgumentCount() != 1) { |
| 280 | request.GetParsedLine().Shift(); |
| 281 | request.AppendEmptyArgument(); |
| 282 | cmd_obj->HandleCompletion(request); |
| 283 | } |
| 284 | } |
| 285 | } |
| 286 | return; |
| 287 | } |
| 288 | |
| 289 | StringList new_matches; |
| 290 | CommandObject *sub_command_object = GetSubcommandObject(sub_cmd: arg0, matches: &new_matches); |
| 291 | |
| 292 | // The subcommand is ambiguous. The completion isn't meaningful. |
| 293 | if (!sub_command_object) |
| 294 | return; |
| 295 | |
| 296 | // Remove the one match that we got from calling GetSubcommandObject. |
| 297 | new_matches.DeleteStringAtIndex(id: 0); |
| 298 | request.AddCompletions(completions: new_matches); |
| 299 | request.ShiftArguments(); |
| 300 | sub_command_object->HandleCompletion(request); |
| 301 | } |
| 302 | |
| 303 | std::optional<std::string> |
| 304 | CommandObjectMultiword::GetRepeatCommand(Args ¤t_command_args, |
| 305 | uint32_t index) { |
| 306 | index++; |
| 307 | if (current_command_args.GetArgumentCount() <= index) |
| 308 | return std::nullopt; |
| 309 | CommandObject *sub_command_object = |
| 310 | GetSubcommandObject(sub_cmd: current_command_args[index].ref()); |
| 311 | if (sub_command_object == nullptr) |
| 312 | return std::nullopt; |
| 313 | return sub_command_object->GetRepeatCommand(current_command_args, index); |
| 314 | } |
| 315 | |
| 316 | CommandObjectProxy::CommandObjectProxy(CommandInterpreter &interpreter, |
| 317 | const char *name, const char *help, |
| 318 | const char *syntax, uint32_t flags) |
| 319 | : CommandObject(interpreter, name, help, syntax, flags) {} |
| 320 | |
| 321 | CommandObjectProxy::~CommandObjectProxy() = default; |
| 322 | |
| 323 | Options *CommandObjectProxy::GetOptions() { |
| 324 | CommandObject *proxy_command = GetProxyCommandObject(); |
| 325 | if (proxy_command) |
| 326 | return proxy_command->GetOptions(); |
| 327 | return CommandObject::GetOptions(); |
| 328 | } |
| 329 | |
| 330 | llvm::StringRef CommandObjectProxy::GetHelp() { |
| 331 | CommandObject *proxy_command = GetProxyCommandObject(); |
| 332 | if (proxy_command) |
| 333 | return proxy_command->GetHelp(); |
| 334 | return CommandObject::GetHelp(); |
| 335 | } |
| 336 | |
| 337 | llvm::StringRef CommandObjectProxy::GetSyntax() { |
| 338 | CommandObject *proxy_command = GetProxyCommandObject(); |
| 339 | if (proxy_command) |
| 340 | return proxy_command->GetSyntax(); |
| 341 | return CommandObject::GetSyntax(); |
| 342 | } |
| 343 | |
| 344 | llvm::StringRef CommandObjectProxy::GetHelpLong() { |
| 345 | CommandObject *proxy_command = GetProxyCommandObject(); |
| 346 | if (proxy_command) |
| 347 | return proxy_command->GetHelpLong(); |
| 348 | return CommandObject::GetHelpLong(); |
| 349 | } |
| 350 | |
| 351 | bool CommandObjectProxy::IsRemovable() const { |
| 352 | const CommandObject *proxy_command = |
| 353 | const_cast<CommandObjectProxy *>(this)->GetProxyCommandObject(); |
| 354 | if (proxy_command) |
| 355 | return proxy_command->IsRemovable(); |
| 356 | return false; |
| 357 | } |
| 358 | |
| 359 | bool CommandObjectProxy::IsMultiwordObject() { |
| 360 | CommandObject *proxy_command = GetProxyCommandObject(); |
| 361 | if (proxy_command) |
| 362 | return proxy_command->IsMultiwordObject(); |
| 363 | return false; |
| 364 | } |
| 365 | |
| 366 | CommandObjectMultiword *CommandObjectProxy::GetAsMultiwordCommand() { |
| 367 | CommandObject *proxy_command = GetProxyCommandObject(); |
| 368 | if (proxy_command) |
| 369 | return proxy_command->GetAsMultiwordCommand(); |
| 370 | return nullptr; |
| 371 | } |
| 372 | |
| 373 | void CommandObjectProxy::GenerateHelpText(Stream &result) { |
| 374 | CommandObject *proxy_command = GetProxyCommandObject(); |
| 375 | if (proxy_command) |
| 376 | proxy_command->GenerateHelpText(result); |
| 377 | else |
| 378 | CommandObject::GenerateHelpText(result); |
| 379 | } |
| 380 | |
| 381 | lldb::CommandObjectSP |
| 382 | CommandObjectProxy::GetSubcommandSP(llvm::StringRef sub_cmd, |
| 383 | StringList *matches) { |
| 384 | CommandObject *proxy_command = GetProxyCommandObject(); |
| 385 | if (proxy_command) |
| 386 | return proxy_command->GetSubcommandSP(sub_cmd, matches); |
| 387 | return lldb::CommandObjectSP(); |
| 388 | } |
| 389 | |
| 390 | CommandObject *CommandObjectProxy::GetSubcommandObject(llvm::StringRef sub_cmd, |
| 391 | StringList *matches) { |
| 392 | CommandObject *proxy_command = GetProxyCommandObject(); |
| 393 | if (proxy_command) |
| 394 | return proxy_command->GetSubcommandObject(sub_cmd, matches); |
| 395 | return nullptr; |
| 396 | } |
| 397 | |
| 398 | bool CommandObjectProxy::LoadSubCommand( |
| 399 | llvm::StringRef cmd_name, const lldb::CommandObjectSP &command_sp) { |
| 400 | CommandObject *proxy_command = GetProxyCommandObject(); |
| 401 | if (proxy_command) |
| 402 | return proxy_command->LoadSubCommand(cmd_name, command_obj: command_sp); |
| 403 | return false; |
| 404 | } |
| 405 | |
| 406 | bool CommandObjectProxy::WantsRawCommandString() { |
| 407 | CommandObject *proxy_command = GetProxyCommandObject(); |
| 408 | if (proxy_command) |
| 409 | return proxy_command->WantsRawCommandString(); |
| 410 | return false; |
| 411 | } |
| 412 | |
| 413 | bool CommandObjectProxy::WantsCompletion() { |
| 414 | CommandObject *proxy_command = GetProxyCommandObject(); |
| 415 | if (proxy_command) |
| 416 | return proxy_command->WantsCompletion(); |
| 417 | return false; |
| 418 | } |
| 419 | |
| 420 | void CommandObjectProxy::HandleCompletion(CompletionRequest &request) { |
| 421 | CommandObject *proxy_command = GetProxyCommandObject(); |
| 422 | if (proxy_command) |
| 423 | proxy_command->HandleCompletion(request); |
| 424 | } |
| 425 | |
| 426 | void CommandObjectProxy::HandleArgumentCompletion( |
| 427 | CompletionRequest &request, OptionElementVector &opt_element_vector) { |
| 428 | CommandObject *proxy_command = GetProxyCommandObject(); |
| 429 | if (proxy_command) |
| 430 | proxy_command->HandleArgumentCompletion(request, opt_element_vector); |
| 431 | } |
| 432 | |
| 433 | std::optional<std::string> |
| 434 | CommandObjectProxy::GetRepeatCommand(Args ¤t_command_args, |
| 435 | uint32_t index) { |
| 436 | CommandObject *proxy_command = GetProxyCommandObject(); |
| 437 | if (proxy_command) |
| 438 | return proxy_command->GetRepeatCommand(current_command_args, index); |
| 439 | return std::nullopt; |
| 440 | } |
| 441 | |
| 442 | llvm::StringRef CommandObjectProxy::GetUnsupportedError() { |
| 443 | return "command is not implemented" ; |
| 444 | } |
| 445 | |
| 446 | void CommandObjectProxy::Execute(const char *args_string, |
| 447 | CommandReturnObject &result) { |
| 448 | if (CommandObject *proxy_command = GetProxyCommandObject()) |
| 449 | proxy_command->Execute(args_string, result); |
| 450 | else |
| 451 | result.AppendError(in_string: GetUnsupportedError()); |
| 452 | } |
| 453 | |