xref: /haiku/src/apps/debugger/user_interface/cli/CommandLineUserInterface.cpp (revision aed35104852941f0f6f3d1dcc5338b5f337d0a3c)
1 /*
2  * Copyright 2011, Rene Gollent, rene@gollent.com.
3  * Copyright 2012, Ingo Weinhold, ingo_weinhold@gmx.de.
4  * Distributed under the terms of the MIT License.
5  */
6 
7 
8 #include "CommandLineUserInterface.h"
9 
10 #include <stdio.h>
11 
12 #include <algorithm>
13 
14 #include <ArgumentVector.h>
15 #include <AutoDeleter.h>
16 #include <Referenceable.h>
17 
18 #include "CliContext.h"
19 #include "CliContinueCommand.h"
20 #include "CliQuitCommand.h"
21 #include "CliStackTraceCommand.h"
22 #include "CliStopCommand.h"
23 #include "CliThreadCommand.h"
24 #include "CliThreadsCommand.h"
25 
26 
27 static const char* kDebuggerPrompt = "debugger> ";
28 
29 
30 // #pragma mark - CommandEntry
31 
32 
33 struct CommandLineUserInterface::CommandEntry {
34 	CommandEntry(const BString& name, CliCommand* command)
35 		:
36 		fName(name),
37 		fCommand(command)
38 	{
39 	}
40 
41 	const BString& Name() const
42 	{
43 		return fName;
44 	}
45 
46 	CliCommand* Command() const
47 	{
48 		return fCommand.Get();
49 	}
50 
51 private:
52 	BString					fName;
53 	BReference<CliCommand>	fCommand;
54 };
55 
56 
57 // #pragma mark - HelpCommand
58 
59 
60 struct CommandLineUserInterface::HelpCommand : CliCommand {
61 	HelpCommand(CommandLineUserInterface* userInterface)
62 		:
63 		CliCommand("print help for a command or a list of all commands",
64 			"%s [ <command> ]\n"
65 			"Prints help for command <command>, if given, or a list of all "
66 				"commands\n"
67 			"otherwise."),
68 		fUserInterface(userInterface)
69 	{
70 	}
71 
72 	virtual void Execute(int argc, const char* const* argv, CliContext& context)
73 	{
74 		if (argc > 2) {
75 			PrintUsage(argv[0]);
76 			return;
77 		}
78 
79 		fUserInterface->_PrintHelp(argc == 2 ? argv[1] : NULL);
80 	}
81 
82 private:
83 	CommandLineUserInterface* fUserInterface;
84 };
85 
86 
87 // #pragma mark - CommandLineUserInterface
88 
89 
90 CommandLineUserInterface::CommandLineUserInterface()
91 	:
92 	fCommands(20, true),
93 	fShowSemaphore(-1),
94 	fShown(false),
95 	fTerminating(false)
96 {
97 }
98 
99 
100 CommandLineUserInterface::~CommandLineUserInterface()
101 {
102 	if (fShowSemaphore >= 0)
103 		delete_sem(fShowSemaphore);
104 }
105 
106 
107 const char*
108 CommandLineUserInterface::ID() const
109 {
110 	return "BasicCommandLineUserInterface";
111 }
112 
113 
114 status_t
115 CommandLineUserInterface::Init(Team* team, UserInterfaceListener* listener)
116 {
117 	status_t error = fContext.Init(team, listener);
118 	if (error != B_OK)
119 		return error;
120 
121 	error = _RegisterCommands();
122 	if (error != B_OK)
123 		return error;
124 
125 	fShowSemaphore = create_sem(0, "show CLI");
126 	if (fShowSemaphore < 0)
127 		return fShowSemaphore;
128 
129 	return B_OK;
130 }
131 
132 
133 void
134 CommandLineUserInterface::Show()
135 {
136 	fShown = true;
137 	release_sem(fShowSemaphore);
138 }
139 
140 
141 void
142 CommandLineUserInterface::Terminate()
143 {
144 	fTerminating = true;
145 
146 	if (fShown) {
147 		fContext.Terminating();
148 
149 		// Wait for input loop to finish.
150 		while (acquire_sem(fShowSemaphore) == B_INTERRUPTED) {
151 		}
152 	} else {
153 		// The main thread will still be blocked in Run(). Unblock it.
154 		delete_sem(fShowSemaphore);
155 		fShowSemaphore = -1;
156 	}
157 
158 	fContext.Cleanup();
159 }
160 
161 
162 status_t
163 CommandLineUserInterface::LoadSettings(const TeamUiSettings* settings)
164 {
165 	return B_OK;
166 }
167 
168 
169 status_t
170 CommandLineUserInterface::SaveSettings(TeamUiSettings*& settings) const
171 {
172 	return B_OK;
173 }
174 
175 
176 void
177 CommandLineUserInterface::NotifyUser(const char* title, const char* message,
178 	user_notification_type type)
179 {
180 }
181 
182 
183 int32
184 CommandLineUserInterface::SynchronouslyAskUser(const char* title,
185 	const char* message, const char* choice1, const char* choice2,
186 	const char* choice3)
187 {
188 	return -1;
189 }
190 
191 
192 void
193 CommandLineUserInterface::Run()
194 {
195 	// Wait for the Show() semaphore to be released.
196 	status_t error;
197 	do {
198 		error = acquire_sem(fShowSemaphore);
199 	} while (error == B_INTERRUPTED);
200 
201 	if (error != B_OK)
202 		return;
203 
204 	_InputLoop();
205 
206 	// Release the Show() semaphore to signal Terminate().
207 	release_sem(fShowSemaphore);
208 }
209 
210 
211 /*static*/ status_t
212 CommandLineUserInterface::_InputLoopEntry(void* data)
213 {
214 	return ((CommandLineUserInterface*)data)->_InputLoop();
215 }
216 
217 
218 status_t
219 CommandLineUserInterface::_InputLoop()
220 {
221 	thread_id currentThread = -1;
222 
223 	while (!fTerminating) {
224 		// Wait for a thread or Ctrl-C.
225 		fContext.WaitForThreadOrUser();
226 		if (fContext.IsTerminating())
227 			break;
228 
229 		// Print the active thread, if it changed.
230 		if (fContext.CurrentThreadID() != currentThread) {
231 			fContext.PrintCurrentThread();
232 			currentThread = fContext.CurrentThreadID();
233 		}
234 
235 		// read a command line
236 		const char* line = fContext.PromptUser(kDebuggerPrompt);
237 		if (line == NULL)
238 			break;
239 
240 		// parse the command line
241 		ArgumentVector args;
242 		const char* parseErrorLocation;
243 		switch (args.Parse(line, &parseErrorLocation)) {
244 			case ArgumentVector::NO_ERROR:
245 				break;
246 			case ArgumentVector::NO_MEMORY:
247 				printf("Insufficient memory parsing the command line.\n");
248 				continue;
249 			case ArgumentVector::UNTERMINATED_QUOTED_STRING:
250 				printf("Parse error: Unterminated quoted string starting at "
251 					"character %zu.\n", parseErrorLocation - line + 1);
252 				continue;
253 			case ArgumentVector::TRAILING_BACKSPACE:
254 				printf("Parse error: trailing backspace.\n");
255 				continue;
256 		}
257 
258 		if (args.ArgumentCount() == 0)
259 			continue;
260 
261 		// add line to history
262 		fContext.AddLineToInputHistory(line);
263 
264 		// execute command
265 		_ExecuteCommand(args.ArgumentCount(), args.Arguments());
266 	}
267 
268 	return B_OK;
269 }
270 
271 
272 status_t
273 CommandLineUserInterface::_RegisterCommands()
274 {
275 	BReference<CliCommand> stackTraceCommandReference(
276 		new(std::nothrow) CliStackTraceCommand, true);
277 	BReference<CliCommand> stackTraceCommandReference2(
278 		stackTraceCommandReference.Get());
279 
280 	if (_RegisterCommand("bt", stackTraceCommandReference.Detach()) &&
281 		_RegisterCommand("continue", new(std::nothrow) CliContinueCommand) &&
282 		_RegisterCommand("help", new(std::nothrow) HelpCommand(this)) &&
283 		_RegisterCommand("quit", new(std::nothrow) CliQuitCommand) &&
284 		_RegisterCommand("sc", stackTraceCommandReference2.Detach()) &&
285 		_RegisterCommand("stop", new(std::nothrow) CliStopCommand) &&
286 		_RegisterCommand("thread", new(std::nothrow) CliThreadCommand) &&
287 		_RegisterCommand("threads", new(std::nothrow) CliThreadsCommand)) {
288 		return B_OK;
289 	}
290 
291 	return B_NO_MEMORY;
292 }
293 
294 
295 bool
296 CommandLineUserInterface::_RegisterCommand(const BString& name,
297 	CliCommand* command)
298 {
299 	BReference<CliCommand> commandReference(command, true);
300 	if (name.IsEmpty() || command == NULL)
301 		return false;
302 
303 	CommandEntry* entry = new(std::nothrow) CommandEntry(name, command);
304 	if (entry == NULL || !fCommands.AddItem(entry)) {
305 		delete entry;
306 		return false;
307 	}
308 
309 	return true;
310 }
311 
312 
313 void
314 CommandLineUserInterface::_ExecuteCommand(int argc, const char* const* argv)
315 {
316 	CommandEntry* commandEntry = _FindCommand(argv[0]);
317 	if (commandEntry != NULL)
318 		commandEntry->Command()->Execute(argc, argv, fContext);
319 }
320 
321 
322 CommandLineUserInterface::CommandEntry*
323 CommandLineUserInterface::_FindCommand(const char* commandName)
324 {
325 	size_t commandNameLength = strlen(commandName);
326 
327 	// try to find an exact match first
328 	CommandEntry* commandEntry = NULL;
329 	for (int32 i = 0; CommandEntry* entry = fCommands.ItemAt(i); i++) {
330 		if (entry->Name() == commandName) {
331 			commandEntry = entry;
332 			break;
333 		}
334 	}
335 
336 	// If nothing found yet, try partial matches, but only, if they are
337 	// unambiguous.
338 	if (commandEntry == NULL) {
339 		for (int32 i = 0; CommandEntry* entry = fCommands.ItemAt(i); i++) {
340 			if (entry->Name().Compare(commandName, commandNameLength) == 0) {
341 				if (commandEntry != NULL) {
342 					printf("Error: Ambiguous command \"%s\".\n", commandName);
343 					return NULL;
344 				}
345 
346 				commandEntry = entry;
347 			}
348 		}
349 	}
350 
351 	if (commandEntry == NULL) {
352 		printf("Error: Unknown command \"%s\".\n", commandName);
353 		return NULL;
354 	}
355 
356 	return commandEntry;
357 }
358 
359 
360 void
361 CommandLineUserInterface::_PrintHelp(const char* commandName)
362 {
363 	// If a command name is given, print the usage for that one.
364 	if (commandName != NULL) {
365 		CommandEntry* commandEntry = _FindCommand(commandName);
366 		if (commandEntry != NULL)
367 			commandEntry->Command()->PrintUsage(commandEntry->Name().String());
368 		return;
369 	}
370 
371 	// No command name given -- print a list of all commands.
372 
373 	// determine longest command name
374 	int32 longestCommandName = 0;
375 	for (int32 i = 0; CommandEntry* entry = fCommands.ItemAt(i); i++) {
376 		longestCommandName
377 			= std::max(longestCommandName, entry->Name().Length());
378 	}
379 
380 	// print the command list
381 	for (int32 i = 0; CommandEntry* entry = fCommands.ItemAt(i); i++) {
382 		printf("%*s  -  %s\n", (int)longestCommandName, entry->Name().String(),
383 			entry->Command()->Summary());
384 	}
385 }
386