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: std::string(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: std::string(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 | CommandMap::iterator pos; |
88 | bool success = true; |
89 | |
90 | pos = m_subcommand_dict.find(x: std::string(name)); |
91 | if (pos == m_subcommand_dict.end()) { |
92 | m_subcommand_dict[std::string(name)] = cmd_obj_sp; |
93 | } else |
94 | success = false; |
95 | |
96 | return success; |
97 | } |
98 | |
99 | llvm::Error CommandObjectMultiword::LoadUserSubcommand( |
100 | llvm::StringRef name, const CommandObjectSP &cmd_obj_sp, bool can_replace) { |
101 | Status result; |
102 | if (cmd_obj_sp) |
103 | lldbassert((&GetCommandInterpreter() == &cmd_obj_sp->GetCommandInterpreter()) && |
104 | "tried to add a CommandObject from a different interpreter" ); |
105 | if (!IsUserCommand()) { |
106 | return llvm::createStringError(EC: llvm::inconvertibleErrorCode(), |
107 | Msg: "can't add a user subcommand to a builtin container command." ); |
108 | } |
109 | // Make sure this a user command if it isn't already: |
110 | cmd_obj_sp->SetIsUserCommand(true); |
111 | |
112 | std::string str_name(name); |
113 | |
114 | auto pos = m_subcommand_dict.find(x: str_name); |
115 | if (pos == m_subcommand_dict.end()) { |
116 | m_subcommand_dict[str_name] = cmd_obj_sp; |
117 | return llvm::Error::success(); |
118 | } |
119 | |
120 | const char *error_str = nullptr; |
121 | if (!can_replace) |
122 | error_str = "sub-command already exists" ; |
123 | if (!(*pos).second->IsUserCommand()) |
124 | error_str = "can't replace a builtin subcommand" ; |
125 | |
126 | if (error_str) { |
127 | return llvm::createStringError(EC: llvm::inconvertibleErrorCode(), Msg: error_str); |
128 | } |
129 | m_subcommand_dict[str_name] = cmd_obj_sp; |
130 | return llvm::Error::success(); |
131 | } |
132 | |
133 | llvm::Error CommandObjectMultiword::RemoveUserSubcommand(llvm::StringRef cmd_name, |
134 | bool must_be_multiword) { |
135 | CommandMap::iterator pos; |
136 | std::string str_name(cmd_name); |
137 | |
138 | pos = m_subcommand_dict.find(x: str_name); |
139 | if (pos == m_subcommand_dict.end()) { |
140 | return llvm::createStringError(EC: llvm::inconvertibleErrorCode(),Fmt: "subcommand '%s' not found." , |
141 | Vals: str_name.c_str()); |
142 | } |
143 | if (!(*pos).second->IsUserCommand()) { |
144 | return llvm::createStringError(EC: llvm::inconvertibleErrorCode(),Fmt: "subcommand '%s' not a user command." , |
145 | Vals: str_name.c_str()); |
146 | } |
147 | |
148 | if (must_be_multiword && !(*pos).second->IsMultiwordObject()) { |
149 | return llvm::createStringError(EC: llvm::inconvertibleErrorCode(),Fmt: "subcommand '%s' is not a container command" , |
150 | Vals: str_name.c_str()); |
151 | } |
152 | if (!must_be_multiword && (*pos).second->IsMultiwordObject()) { |
153 | return llvm::createStringError(EC: llvm::inconvertibleErrorCode(),Fmt: "subcommand '%s' is not a user command" , |
154 | Vals: str_name.c_str()); |
155 | } |
156 | |
157 | m_subcommand_dict.erase(position: pos); |
158 | |
159 | return llvm::Error::success(); |
160 | } |
161 | |
162 | void CommandObjectMultiword::Execute(const char *args_string, |
163 | CommandReturnObject &result) { |
164 | Args args(args_string); |
165 | const size_t argc = args.GetArgumentCount(); |
166 | if (argc == 0) { |
167 | this->CommandObject::GenerateHelpText(result); |
168 | return; |
169 | } |
170 | |
171 | auto sub_command = args[0].ref(); |
172 | if (sub_command.empty()) { |
173 | result.AppendError(in_string: "Need to specify a non-empty subcommand." ); |
174 | return; |
175 | } |
176 | |
177 | if (m_subcommand_dict.empty()) { |
178 | result.AppendErrorWithFormat(format: "'%s' does not have any subcommands.\n" , |
179 | GetCommandName().str().c_str()); |
180 | return; |
181 | } |
182 | |
183 | StringList matches; |
184 | CommandObject *sub_cmd_obj = GetSubcommandObject(sub_cmd: sub_command, matches: &matches); |
185 | if (sub_cmd_obj != nullptr) { |
186 | // Now call CommandObject::Execute to process options in `rest_of_line`. |
187 | // From there the command-specific version of Execute will be called, with |
188 | // the processed arguments. |
189 | |
190 | args.Shift(); |
191 | sub_cmd_obj->Execute(args_string, result); |
192 | return; |
193 | } |
194 | |
195 | std::string error_msg; |
196 | const size_t num_subcmd_matches = matches.GetSize(); |
197 | if (num_subcmd_matches > 0) |
198 | error_msg.assign(s: "ambiguous command " ); |
199 | else |
200 | error_msg.assign(s: "invalid command " ); |
201 | |
202 | error_msg.append(s: "'" ); |
203 | error_msg.append(str: std::string(GetCommandName())); |
204 | error_msg.append(s: " " ); |
205 | error_msg.append(str: std::string(sub_command)); |
206 | error_msg.append(s: "'." ); |
207 | |
208 | if (num_subcmd_matches > 0) { |
209 | error_msg.append(s: " Possible completions:" ); |
210 | for (const std::string &match : matches) { |
211 | error_msg.append(s: "\n\t" ); |
212 | error_msg.append(str: match); |
213 | } |
214 | } |
215 | error_msg.append(s: "\n" ); |
216 | result.AppendRawError(in_string: error_msg.c_str()); |
217 | } |
218 | |
219 | void CommandObjectMultiword::GenerateHelpText(Stream &output_stream) { |
220 | // First time through here, generate the help text for the object and push it |
221 | // to the return result object as well |
222 | |
223 | CommandObject::GenerateHelpText(result&: output_stream); |
224 | output_stream.PutCString(cstr: "\nThe following subcommands are supported:\n\n" ); |
225 | |
226 | CommandMap::iterator pos; |
227 | uint32_t max_len = FindLongestCommandWord(dict&: m_subcommand_dict); |
228 | |
229 | if (max_len) |
230 | max_len += 4; // Indent the output by 4 spaces. |
231 | |
232 | for (pos = m_subcommand_dict.begin(); pos != m_subcommand_dict.end(); ++pos) { |
233 | std::string indented_command(" " ); |
234 | indented_command.append(str: pos->first); |
235 | if (pos->second->WantsRawCommandString()) { |
236 | std::string help_text(std::string(pos->second->GetHelp())); |
237 | help_text.append(s: " Expects 'raw' input (see 'help raw-input'.)" ); |
238 | m_interpreter.OutputFormattedHelpText(stream&: output_stream, command_word: indented_command, |
239 | separator: "--" , help_text, max_word_len: max_len); |
240 | } else |
241 | m_interpreter.OutputFormattedHelpText(stream&: output_stream, command_word: indented_command, |
242 | separator: "--" , help_text: pos->second->GetHelp(), |
243 | max_word_len: max_len); |
244 | } |
245 | |
246 | output_stream.PutCString(cstr: "\nFor more help on any particular subcommand, type " |
247 | "'help <command> <subcommand>'.\n" ); |
248 | } |
249 | |
250 | void CommandObjectMultiword::HandleCompletion(CompletionRequest &request) { |
251 | auto arg0 = request.GetParsedLine()[0].ref(); |
252 | if (request.GetCursorIndex() == 0) { |
253 | StringList new_matches, descriptions; |
254 | AddNamesMatchingPartialString(in_map: m_subcommand_dict, cmd_str: arg0, matches&: new_matches, |
255 | descriptions: &descriptions); |
256 | request.AddCompletions(completions: new_matches, descriptions); |
257 | |
258 | if (new_matches.GetSize() == 1 && |
259 | new_matches.GetStringAtIndex(idx: 0) != nullptr && |
260 | (arg0 == new_matches.GetStringAtIndex(idx: 0))) { |
261 | StringList temp_matches; |
262 | CommandObject *cmd_obj = GetSubcommandObject(sub_cmd: arg0, matches: &temp_matches); |
263 | if (cmd_obj != nullptr) { |
264 | if (request.GetParsedLine().GetArgumentCount() != 1) { |
265 | request.GetParsedLine().Shift(); |
266 | request.AppendEmptyArgument(); |
267 | cmd_obj->HandleCompletion(request); |
268 | } |
269 | } |
270 | } |
271 | return; |
272 | } |
273 | |
274 | StringList new_matches; |
275 | CommandObject *sub_command_object = GetSubcommandObject(sub_cmd: arg0, matches: &new_matches); |
276 | |
277 | // The subcommand is ambiguous. The completion isn't meaningful. |
278 | if (!sub_command_object) |
279 | return; |
280 | |
281 | // Remove the one match that we got from calling GetSubcommandObject. |
282 | new_matches.DeleteStringAtIndex(id: 0); |
283 | request.AddCompletions(completions: new_matches); |
284 | request.ShiftArguments(); |
285 | sub_command_object->HandleCompletion(request); |
286 | } |
287 | |
288 | std::optional<std::string> |
289 | CommandObjectMultiword::GetRepeatCommand(Args ¤t_command_args, |
290 | uint32_t index) { |
291 | index++; |
292 | if (current_command_args.GetArgumentCount() <= index) |
293 | return std::nullopt; |
294 | CommandObject *sub_command_object = |
295 | GetSubcommandObject(sub_cmd: current_command_args[index].ref()); |
296 | if (sub_command_object == nullptr) |
297 | return std::nullopt; |
298 | return sub_command_object->GetRepeatCommand(current_command_args, index); |
299 | } |
300 | |
301 | CommandObjectProxy::CommandObjectProxy(CommandInterpreter &interpreter, |
302 | const char *name, const char *help, |
303 | const char *syntax, uint32_t flags) |
304 | : CommandObject(interpreter, name, help, syntax, flags) {} |
305 | |
306 | CommandObjectProxy::~CommandObjectProxy() = default; |
307 | |
308 | Options *CommandObjectProxy::GetOptions() { |
309 | CommandObject *proxy_command = GetProxyCommandObject(); |
310 | if (proxy_command) |
311 | return proxy_command->GetOptions(); |
312 | return CommandObject::GetOptions(); |
313 | } |
314 | |
315 | llvm::StringRef CommandObjectProxy::GetHelp() { |
316 | CommandObject *proxy_command = GetProxyCommandObject(); |
317 | if (proxy_command) |
318 | return proxy_command->GetHelp(); |
319 | return CommandObject::GetHelp(); |
320 | } |
321 | |
322 | llvm::StringRef CommandObjectProxy::GetSyntax() { |
323 | CommandObject *proxy_command = GetProxyCommandObject(); |
324 | if (proxy_command) |
325 | return proxy_command->GetSyntax(); |
326 | return CommandObject::GetSyntax(); |
327 | } |
328 | |
329 | llvm::StringRef CommandObjectProxy::GetHelpLong() { |
330 | CommandObject *proxy_command = GetProxyCommandObject(); |
331 | if (proxy_command) |
332 | return proxy_command->GetHelpLong(); |
333 | return CommandObject::GetHelpLong(); |
334 | } |
335 | |
336 | bool CommandObjectProxy::IsRemovable() const { |
337 | const CommandObject *proxy_command = |
338 | const_cast<CommandObjectProxy *>(this)->GetProxyCommandObject(); |
339 | if (proxy_command) |
340 | return proxy_command->IsRemovable(); |
341 | return false; |
342 | } |
343 | |
344 | bool CommandObjectProxy::IsMultiwordObject() { |
345 | CommandObject *proxy_command = GetProxyCommandObject(); |
346 | if (proxy_command) |
347 | return proxy_command->IsMultiwordObject(); |
348 | return false; |
349 | } |
350 | |
351 | CommandObjectMultiword *CommandObjectProxy::GetAsMultiwordCommand() { |
352 | CommandObject *proxy_command = GetProxyCommandObject(); |
353 | if (proxy_command) |
354 | return proxy_command->GetAsMultiwordCommand(); |
355 | return nullptr; |
356 | } |
357 | |
358 | void CommandObjectProxy::GenerateHelpText(Stream &result) { |
359 | CommandObject *proxy_command = GetProxyCommandObject(); |
360 | if (proxy_command) |
361 | proxy_command->GenerateHelpText(result); |
362 | else |
363 | CommandObject::GenerateHelpText(result); |
364 | } |
365 | |
366 | lldb::CommandObjectSP |
367 | CommandObjectProxy::GetSubcommandSP(llvm::StringRef sub_cmd, |
368 | StringList *matches) { |
369 | CommandObject *proxy_command = GetProxyCommandObject(); |
370 | if (proxy_command) |
371 | return proxy_command->GetSubcommandSP(sub_cmd, matches); |
372 | return lldb::CommandObjectSP(); |
373 | } |
374 | |
375 | CommandObject *CommandObjectProxy::GetSubcommandObject(llvm::StringRef sub_cmd, |
376 | StringList *matches) { |
377 | CommandObject *proxy_command = GetProxyCommandObject(); |
378 | if (proxy_command) |
379 | return proxy_command->GetSubcommandObject(sub_cmd, matches); |
380 | return nullptr; |
381 | } |
382 | |
383 | bool CommandObjectProxy::LoadSubCommand( |
384 | llvm::StringRef cmd_name, const lldb::CommandObjectSP &command_sp) { |
385 | CommandObject *proxy_command = GetProxyCommandObject(); |
386 | if (proxy_command) |
387 | return proxy_command->LoadSubCommand(cmd_name, command_obj: command_sp); |
388 | return false; |
389 | } |
390 | |
391 | bool CommandObjectProxy::WantsRawCommandString() { |
392 | CommandObject *proxy_command = GetProxyCommandObject(); |
393 | if (proxy_command) |
394 | return proxy_command->WantsRawCommandString(); |
395 | return false; |
396 | } |
397 | |
398 | bool CommandObjectProxy::WantsCompletion() { |
399 | CommandObject *proxy_command = GetProxyCommandObject(); |
400 | if (proxy_command) |
401 | return proxy_command->WantsCompletion(); |
402 | return false; |
403 | } |
404 | |
405 | void CommandObjectProxy::HandleCompletion(CompletionRequest &request) { |
406 | CommandObject *proxy_command = GetProxyCommandObject(); |
407 | if (proxy_command) |
408 | proxy_command->HandleCompletion(request); |
409 | } |
410 | |
411 | void CommandObjectProxy::HandleArgumentCompletion( |
412 | CompletionRequest &request, OptionElementVector &opt_element_vector) { |
413 | CommandObject *proxy_command = GetProxyCommandObject(); |
414 | if (proxy_command) |
415 | proxy_command->HandleArgumentCompletion(request, opt_element_vector); |
416 | } |
417 | |
418 | std::optional<std::string> |
419 | CommandObjectProxy::GetRepeatCommand(Args ¤t_command_args, |
420 | uint32_t index) { |
421 | CommandObject *proxy_command = GetProxyCommandObject(); |
422 | if (proxy_command) |
423 | return proxy_command->GetRepeatCommand(current_command_args, index); |
424 | return std::nullopt; |
425 | } |
426 | |
427 | llvm::StringRef CommandObjectProxy::GetUnsupportedError() { |
428 | return "command is not implemented" ; |
429 | } |
430 | |
431 | void CommandObjectProxy::Execute(const char *args_string, |
432 | CommandReturnObject &result) { |
433 | if (CommandObject *proxy_command = GetProxyCommandObject()) |
434 | proxy_command->Execute(args_string, result); |
435 | else |
436 | result.AppendError(in_string: GetUnsupportedError()); |
437 | } |
438 | |