OLD | NEW |
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library debugger; | 5 library debugger; |
6 | 6 |
7 import "dart:async"; | 7 import "dart:async"; |
8 import "dart:io"; | 8 import "dart:io"; |
9 | 9 |
10 import "package:ddbg/commando.dart"; | 10 import "package:ddbg/commando.dart"; |
11 import "package:observatory/service_io.dart"; | 11 import "package:observatory/service_io.dart"; |
12 | 12 |
13 class Debugger { | 13 class Debugger { |
14 Commando cmdo; | 14 Commando cmdo; |
15 var _cmdoSubscription; | 15 var _cmdoSubscription; |
16 | 16 |
| 17 CommandList _commands; |
| 18 |
17 VM _vm; | 19 VM _vm; |
18 VM get vm => _vm; | 20 VM get vm => _vm; |
19 set vm(VM vm) { | 21 set vm(VM vm) { |
20 if (_vm == vm) { | 22 if (_vm == vm) { |
21 // Do nothing. | 23 // Do nothing. |
22 return; | 24 return; |
23 } | 25 } |
24 if (_vm != null) { | 26 if (_vm != null) { |
25 _vm.disconnect(); | 27 _vm.disconnect(); |
26 } | 28 } |
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
84 error is ServiceError) { | 86 error is ServiceError) { |
85 // These are handled elsewhere. Ignore. | 87 // These are handled elsewhere. Ignore. |
86 return; | 88 return; |
87 } | 89 } |
88 cmdo.print('\n--------\nExiting due to unexpected error:\n' | 90 cmdo.print('\n--------\nExiting due to unexpected error:\n' |
89 ' $error\n$trace\n'); | 91 ' $error\n$trace\n'); |
90 quit(); | 92 quit(); |
91 } | 93 } |
92 | 94 |
93 Debugger() { | 95 Debugger() { |
94 cmdo = new Commando(completer: completeCommand); | 96 cmdo = new Commando(completer: _completeCommand); |
95 _cmdoSubscription = cmdo.commands.listen(_processCommand, | 97 _cmdoSubscription = cmdo.commands.listen(_processCommand, |
96 onError: _cmdoError, | 98 onError: _cmdoError, |
97 onDone: _cmdoDone); | 99 onDone: _cmdoDone); |
| 100 _commands = new CommandList(); |
| 101 _commands.register(new AttachCommand()); |
| 102 _commands.register(new DetachCommand()); |
| 103 _commands.register(new HelpCommand(_commands)); |
| 104 _commands.register(new IsolateCommand()); |
| 105 _commands.register(new QuitCommand()); |
98 } | 106 } |
99 | 107 |
100 Future _closeCmdo() { | 108 Future _closeCmdo() { |
101 var sub = _cmdoSubscription; | 109 var sub = _cmdoSubscription; |
102 _cmdoSubscription = null; | 110 _cmdoSubscription = null; |
103 cmdo = null; | 111 cmdo = null; |
104 | 112 |
105 var future = sub.cancel(); | 113 var future = sub.cancel(); |
106 if (future != null) { | 114 if (future != null) { |
107 return future; | 115 return future; |
108 } else { | 116 } else { |
109 return new Future.value(); | 117 return new Future.value(); |
110 } | 118 } |
111 } | 119 } |
112 | 120 |
113 Future quit() { | 121 Future quit() { |
114 return Future.wait([_closeCmdo()]).then((_) { | 122 return Future.wait([_closeCmdo()]).then((_) { |
115 exit(0); | 123 exit(0); |
116 }); | 124 }); |
117 } | 125 } |
118 | 126 |
119 void _cmdoError(self, parent, zone, error, StackTrace trace) { | 127 void _cmdoError(error, StackTrace trace) { |
120 cmdo.print('\n--------\nExiting due to unexpected error:\n' | 128 cmdo.print('\n--------\nExiting due to unexpected error:\n' |
121 ' $error\n$trace\n'); | 129 ' $error\n$trace\n'); |
122 quit(); | 130 quit(); |
123 } | 131 } |
124 | 132 |
125 void _cmdoDone() { | 133 void _cmdoDone() { |
126 quit(); | 134 quit(); |
127 } | 135 } |
128 | 136 |
| 137 List<String> _completeCommand(List<String> commandParts) { |
| 138 return _commands.complete(commandParts); |
| 139 } |
| 140 |
129 void _processCommand(String cmdLine) { | 141 void _processCommand(String cmdLine) { |
130 void huh() { | 142 void huh() { |
131 cmdo.print("'$cmdLine' not understood, try 'help' for help."); | 143 cmdo.print("'$cmdLine' not understood, try 'help' for help."); |
132 } | 144 } |
133 | 145 |
134 cmdo.hide(); | 146 cmdo.hide(); |
135 cmdLine = cmdLine.trim(); | 147 cmdLine = cmdLine.trim(); |
136 var args = cmdLine.split(' '); | 148 var args = cmdLine.split(' '); |
137 if (args.length == 0) { | 149 if (args.length == 0) { |
138 return; | 150 return; |
139 } | 151 } |
140 var command = args[0]; | 152 var command = args[0]; |
141 var matches = matchCommand(command, true); | 153 var matches = _commands.match(command, true); |
142 if (matches.length == 0) { | 154 if (matches.length == 0) { |
143 huh(); | 155 huh(); |
144 cmdo.show(); | 156 cmdo.show(); |
145 } else if (matches.length == 1) { | 157 } else if (matches.length == 1) { |
146 matches[0].run(this, args).then((_) { | 158 matches[0].run(this, args).then((_) { |
147 cmdo.show(); | 159 cmdo.show(); |
148 }); | 160 }); |
149 } else { | 161 } else { |
150 var matchNames = matches.map((handler) => handler.name); | 162 var matchNames = matches.map((handler) => handler.name); |
151 cmdo.print("Ambigous command '$command' : ${matchNames.toList()}"); | 163 cmdo.print("Ambigous command '$command' : ${matchNames.toList()}"); |
152 cmdo.show(); | 164 cmdo.show(); |
153 } | 165 } |
154 } | 166 } |
155 | 167 |
156 } | 168 } |
157 | 169 |
158 // Every debugger command extends this base class. | 170 // Every debugger command extends this base class. |
159 abstract class Command { | 171 abstract class Command { |
160 String get name; | 172 String get name; |
161 String get helpShort; | 173 String get helpShort; |
162 void printHelp(Debugger debugger, List<String> args); | 174 void printHelp(Debugger debugger, List<String> args); |
163 Future run(Debugger debugger, List<String> args); | 175 Future run(Debugger debugger, List<String> args); |
| 176 List<String> complete(List<String> commandParts) { |
| 177 return ["$name ${commandParts.join(' ')}"]; |
| 178 |
| 179 } |
164 } | 180 } |
165 | 181 |
166 class AttachCommand extends Command { | 182 class AttachCommand extends Command { |
167 final name = 'attach'; | 183 final name = 'attach'; |
168 final helpShort = 'Attach to a running Dart VM'; | 184 final helpShort = 'Attach to a running Dart VM'; |
169 void printHelp(Debugger debugger, List<String> args) { | 185 void printHelp(Debugger debugger, List<String> args) { |
170 debugger.cmdo.print(''' | 186 debugger.cmdo.print(''' |
171 ----- attach ----- | 187 ----- attach ----- |
172 | 188 |
173 Attach to the Dart VM running at the indicated host:port. If no | 189 Attach to the Dart VM running at the indicated host:port. If no |
(...skipping 22 matching lines...) Expand all Loading... |
196 for (var isolate in vm.isolates) { | 212 for (var isolate in vm.isolates) { |
197 if (isolate.name == 'root') { | 213 if (isolate.name == 'root') { |
198 debugger.isolate = isolate; | 214 debugger.isolate = isolate; |
199 } | 215 } |
200 } | 216 } |
201 } | 217 } |
202 }); | 218 }); |
203 } | 219 } |
204 } | 220 } |
205 | 221 |
| 222 class CommandList { |
| 223 List _commands = new List<Command>(); |
| 224 |
| 225 void register(Command cmd) { |
| 226 _commands.add(cmd); |
| 227 } |
| 228 |
| 229 List<Command> match(String commandName, bool exactMatchWins) { |
| 230 var matches = []; |
| 231 for (var command in _commands) { |
| 232 if (command.name.startsWith(commandName)) { |
| 233 if (exactMatchWins && command.name == commandName) { |
| 234 // Exact match |
| 235 return [command]; |
| 236 } else { |
| 237 matches.add(command); |
| 238 } |
| 239 } |
| 240 } |
| 241 return matches; |
| 242 } |
| 243 |
| 244 List<String> complete(List<String> commandParts) { |
| 245 var completions = new List<String>(); |
| 246 String prefix = commandParts[0]; |
| 247 for (var command in _commands) { |
| 248 if (command.name.startsWith(prefix)) { |
| 249 completions.addAll(command.complete(commandParts.sublist(1))); |
| 250 } |
| 251 } |
| 252 return completions; |
| 253 } |
| 254 |
| 255 void printHelp(Debugger debugger, List<String> args) { |
| 256 var cmdo = debugger.cmdo; |
| 257 if (args.length <= 1) { |
| 258 cmdo.print("\nDebugger commands:\n"); |
| 259 for (var command in _commands) { |
| 260 cmdo.print(' ${command.name.padRight(11)} ${command.helpShort}'); |
| 261 } |
| 262 cmdo.print("For more information about a particular command, type:\n\n" |
| 263 " help <command>\n"); |
| 264 |
| 265 cmdo.print("Commands may be abbreviated: e.g. type 'h' for 'help.\n"); |
| 266 } else { |
| 267 var commandName = args[1]; |
| 268 var matches =match(commandName, true); |
| 269 if (matches.length == 0) { |
| 270 cmdo.print("Command '$commandName' not recognized. " |
| 271 "Try 'help' for a list of commands."); |
| 272 } else { |
| 273 for (var command in matches) { |
| 274 command.printHelp(debugger, args); |
| 275 } |
| 276 } |
| 277 } |
| 278 } |
| 279 } |
| 280 |
206 class DetachCommand extends Command { | 281 class DetachCommand extends Command { |
207 final name = 'detach'; | 282 final name = 'detach'; |
208 final helpShort = 'Detach from a running Dart VM'; | 283 final helpShort = 'Detach from a running Dart VM'; |
209 void printHelp(Debugger debugger, List<String> args) { | 284 void printHelp(Debugger debugger, List<String> args) { |
210 cmdo.print(''' | 285 debugger.cmdo.print(''' |
211 ----- detach ----- | 286 ----- detach ----- |
212 | 287 |
213 Detach from the Dart VM. | 288 Detach from the Dart VM. |
214 | 289 |
215 Usage: | 290 Usage: |
216 detach | 291 detach |
217 '''); | 292 '''); |
218 } | 293 } |
219 | 294 |
220 Future run(Debugger debugger, List<String> args) { | 295 Future run(Debugger debugger, List<String> args) { |
221 var cmdo = debugger.cmdo; | 296 var cmdo = debugger.cmdo; |
222 if (args.length > 1) { | 297 if (args.length > 1) { |
223 cmdo.print('$name expects no arguments'); | 298 cmdo.print('$name expects no arguments'); |
224 return new Future.value(); | 299 return new Future.value(); |
225 } | 300 } |
226 if (debugger.vm == null) { | 301 if (debugger.vm == null) { |
227 cmdo.print('No VM is attached'); | 302 cmdo.print('No VM is attached'); |
228 } else { | 303 } else { |
229 debugger.vm = null; | 304 debugger.vm = null; |
230 } | 305 } |
231 return new Future.value(); | 306 return new Future.value(); |
232 } | 307 } |
233 } | 308 } |
234 | 309 |
235 class HelpCommand extends Command { | 310 class HelpCommand extends Command { |
| 311 HelpCommand(this._commands); |
| 312 final CommandList _commands; |
| 313 |
236 final name = 'help'; | 314 final name = 'help'; |
237 final helpShort = 'Show a list of debugger commands'; | 315 final helpShort = 'Show a list of debugger commands'; |
238 void printHelp(Debugger debugger, List<String> args) { | 316 void printHelp(Debugger debugger, List<String> args) { |
239 debugger.cmdo.print(''' | 317 debugger.cmdo.print(''' |
240 ----- help ----- | 318 ----- help ----- |
241 | 319 |
242 Show a list of debugger commands or get more information about a | 320 Show a list of debugger commands or get more information about a |
243 particular command. | 321 particular command. |
244 | 322 |
245 Usage: | 323 Usage: |
246 help | 324 help |
247 help <command> | 325 help <command> |
248 '''); | 326 '''); |
249 } | 327 } |
250 | 328 |
251 Future run(Debugger debugger, List<String> args) { | 329 Future run(Debugger debugger, List<String> args) { |
252 var cmdo = debugger.cmdo; | 330 _commands.printHelp(debugger, args); |
253 if (args.length <= 1) { | 331 return new Future.value(); |
254 cmdo.print("\nDebugger commands:\n"); | 332 } |
255 for (var command in commandList) { | |
256 cmdo.print(' ${command.name.padRight(11)} ${command.helpShort}'); | |
257 } | |
258 cmdo.print("For more information about a particular command, type:\n\n" | |
259 " help <command>\n"); | |
260 | 333 |
261 cmdo.print("Commands may be abbreviated: e.g. type 'h' for 'help.\n"); | 334 List<String> complete(List<String> commandParts) { |
262 } else { | 335 if (commandParts.isEmpty) { |
263 var commandName = args[1]; | 336 return ['$name ']; |
264 var matches = matchCommand(commandName, true); | |
265 if (matches.length == 0) { | |
266 cmdo.print("Command '$commandName' not recognized. " | |
267 "Try 'help' for a list of commands."); | |
268 } else { | |
269 for (var command in matches) { | |
270 command.printHelp(debugger, args); | |
271 } | |
272 } | |
273 } | 337 } |
274 | 338 return _commands.complete(commandParts).map((value) { |
275 return new Future.value(); | 339 return '$name $value'; |
| 340 }); |
276 } | 341 } |
277 } | 342 } |
278 | 343 |
279 class IsolateCommand extends Command { | 344 class IsolateCommand extends Command { |
280 final name = 'isolate'; | 345 final name = 'isolate'; |
281 final helpShort = 'Isolate control'; | 346 final helpShort = 'Isolate control'; |
282 void printHelp(Debugger debugger, List<String> args) { | 347 void printHelp(Debugger debugger, List<String> args) { |
283 debugger.cmdo.print(''' | 348 debugger.cmdo.print(''' |
284 ----- isolate ----- | 349 ----- isolate ----- |
285 | 350 |
(...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
368 } else if (isolate.loading) { | 433 } else if (isolate.loading) { |
369 // TODO(turnidge): This is weird in a command line debugger. | 434 // TODO(turnidge): This is weird in a command line debugger. |
370 sb.write('(not available)'); | 435 sb.write('(not available)'); |
371 } | 436 } |
372 sb.write('\n'); | 437 sb.write('\n'); |
373 } | 438 } |
374 cmdo.print(sb); | 439 cmdo.print(sb); |
375 }); | 440 }); |
376 return new Future.value(); | 441 return new Future.value(); |
377 } | 442 } |
| 443 |
| 444 List<String> complete(List<String> commandParts) { |
| 445 if (commandParts.isEmpty) { |
| 446 return ['$name ${commandParts.join(" ")}']; |
| 447 } else { |
| 448 var completions = _commands.complete(commandParts); |
| 449 return completions.map((completion) { |
| 450 return '$name $completion'; |
| 451 }); |
| 452 } |
| 453 } |
378 } | 454 } |
379 | 455 |
380 class QuitCommand extends Command { | 456 class QuitCommand extends Command { |
381 final name = 'quit'; | 457 final name = 'quit'; |
382 final helpShort = 'Quit the debugger.'; | 458 final helpShort = 'Quit the debugger.'; |
383 void printHelp(Debugger debugger, List<String> args) { | 459 void printHelp(Debugger debugger, List<String> args) { |
384 debugger.cmdo.print(''' | 460 debugger.cmdo.print(''' |
385 ----- quit ----- | 461 ----- quit ----- |
386 | 462 |
387 Quit the debugger. | 463 Quit the debugger. |
388 | 464 |
389 Usage: | 465 Usage: |
390 quit | 466 quit |
391 '''); | 467 '''); |
392 } | 468 } |
393 | 469 |
394 Future run(Debugger debugger, List<String> args) { | 470 Future run(Debugger debugger, List<String> args) { |
395 var cmdo = debugger.cmdo; | 471 var cmdo = debugger.cmdo; |
396 if (args.length > 1) { | 472 if (args.length > 1) { |
397 cmdo.print("Unexpected arguments to $name command."); | 473 cmdo.print("Unexpected arguments to $name command."); |
398 return new Future.value(); | 474 return new Future.value(); |
399 } | 475 } |
400 return debugger.quit(); | 476 return debugger.quit(); |
401 } | 477 } |
402 } | 478 } |
403 | |
404 List<Command> commandList = | |
405 [ new AttachCommand(), | |
406 new DetachCommand(), | |
407 new HelpCommand(), | |
408 new IsolateCommand(), | |
409 new QuitCommand() ]; | |
410 | |
411 List<Command> matchCommand(String commandName, bool exactMatchWins) { | |
412 var matches = []; | |
413 for (var command in commandList) { | |
414 if (command.name.startsWith(commandName)) { | |
415 if (exactMatchWins && command.name == commandName) { | |
416 // Exact match | |
417 return [command]; | |
418 } else { | |
419 matches.add(command); | |
420 } | |
421 } | |
422 } | |
423 return matches; | |
424 } | |
425 | |
426 List<String> completeCommand(List<String> commandParts) { | |
427 var completions = new List<String>(); | |
428 if (commandParts.length == 1) { | |
429 String prefix = commandParts[0]; | |
430 for (var command in commandList) { | |
431 if (command.name.startsWith(prefix)) { | |
432 completions.add(command.name); | |
433 } | |
434 } | |
435 } | |
436 return completions; | |
437 } | |
OLD | NEW |