Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(3)

Side by Side Diff: tools/ddbg/lib/commando.dart

Issue 69343017: Add support for command-line editing to ddbg. (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: Created 7 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
hausner 2013/11/21 16:12:03 2013
turnidge 2013/11/21 18:25:11 Done.
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.
4
5 import 'dart:async';
6 import 'dart:convert';
7 import 'dart:io';
8 import 'dart:math';
9
10 import 'terminfo.dart';
11
12 typedef List<String> CommandCompleter(List<String> commandParts);
13
14 class Commando {
15 // Ctrl keys
16 static const int runeCtrlA = 0x01;
hausner 2013/11/21 19:24:56 I find this to be quite verbose to read. How about
turnidge 2013/11/22 19:59:58 Done.
17 static const int runeCtrlB = 0x02;
18 static const int runeCtrlD = 0x04;
19 static const int runeCtrlE = 0x05;
20 static const int runeCtrlF = 0x06;
21 static const int runeTAB = 0x09;
22 static const int runeNewline = 0x0a;
23 static const int runeCtrlK = 0x0b;
24 static const int runeCtrlL = 0x0c;
25 static const int runeCtrlN = 0x0e;
26 static const int runeCtrlP = 0x10;
27 static const int runeCtrlU = 0x15;
28 static const int runeCtrlY = 0x19;
29 static const int runeESC = 0x1b;
30 static const int runeSpace = 0x20;
31 static const int runeDEL = 0x7F;
32
33 Commando(this._stdin,
34 this._stdout,
35 this._handleCommand,
36 {this.prompt:'> ', this.completer:null}) {
hausner 2013/11/21 19:24:56 Should there be spaces around :
turnidge 2013/11/22 19:59:58 Seems reasonable. Done.
37 _stdin.echoMode = false;
38 _stdin.lineMode = false;
39 _screenWidth = _term.cols - 1;
40 _writePrompt();
41 _stdinSubscription =
42 _stdin.transform(UTF8.decoder).listen(_handleText, onDone:done);
43 }
44
45 void _handleText(String text) {
46 try {
47 if (!_promptShown) {
48 _bufferedInput.write(text);
49 return;
50 }
51
52 var runes = text.runes.toList();
hausner 2013/11/21 19:24:56 I'm learning as I read your code and the api docs.
turnidge 2013/11/22 19:59:58 I pass the List into handleRegularSequence/handleC
53 var pos = 0;
54 while (pos < runes.length) {
55 if (!_promptShown) {
56 // A command was processed which hid the prompt. Buffer
57 // the rest of the input.
58 _bufferedInput.write(
59 new String.fromCharCodes(runes.skip(pos)));
60 return;
61 }
62
63 var char = runes[pos];
hausner 2013/11/21 19:24:56 This would now be the rune variable from above.
64
65 // Count consecutive tabs because double-tab is meaningful.
66 if (char == runeTAB) {
67 _tabCount++;
68 } else {
69 _tabCount = 0;
70 }
71
72 if (_isControlCharacter(char)) {
hausner 2013/11/21 19:24:56 isControlRune() ? (maybe rather not, unless we ren
turnidge 2013/11/22 19:59:58 Why not? Changed.
73 pos += _handleControlSequence(runes, pos);
74 } else {
75 pos += _handleRegularSequence(runes, pos);
76 }
77 }
78 } catch(e, trace) {
79 stderr.writeln('\nUnexpected exception: $e');
80 stderr.writeln(trace);
81 stderr.close().then((_) {
82 done();
83 });
84 }
85 }
86
87 int _handleControlSequence(List<int> runes, int pos) {
88 var runesConsumed = 1; // Most common result.
89 var char = runes[pos];
90 switch (char) {
91 case runeCtrlA:
92 _home();
93 break;
94
95 case runeCtrlB:
96 _leftArrow();
97 break;
98
99 case runeCtrlD:
100 if (_currentLine.length == 0) {
101 // ^D on an empty line means quit.
102 _stdout.writeln();
103 done();
104 } else {
105 _delete();
106 }
107 break;
108
109 case runeCtrlE:
110 _end();
111 break;
112
113 case runeCtrlF:
114 _rightArrow();
115 break;
116
117 case runeTAB:
118 if (_complete(_tabCount > 1)) {
119 _tabCount = 0;
120 }
121 break;
122
123 case runeNewline:
124 _newline();
125 break;
126
127 case runeCtrlK:
128 _kill();
129 break;
130
131 case runeCtrlL:
132 _clearScreen();
133 break;
134
135 case runeCtrlN:
136 _historyNext();
137 break;
138
139 case runeCtrlP:
140 _historyPrevious();
141 break;
142
143 case runeCtrlU:
144 _clearLine();
145 break;
146
147 case runeCtrlY:
148 _yank();
149 break;
150
151 case runeESC:
152 // Check to see if this is an arrow key.
153 if (pos + 2 < runes.length && // must be a 3 char sequence.
154 runes[pos + 1] == 0x5b) { // second char must be '['.
155 switch (runes[pos + 2]) {
156 case 0x41: // ^[[A = up arrow
157 _historyPrevious();
158 runesConsumed = 3;
159 break;
160
161 case 0x42: // ^[[B = down arrow
162 _historyNext();
163 runesConsumed = 3;
164 break;
165
166 case 0x43: // ^[[C = right arrow
167 _rightArrow();
168 runesConsumed = 3;
169 break;
170
171 case 0x44: // ^[[D = left arrow
172 _leftArrow();
173 runesConsumed = 3;
174 break;
175
176 default:
177 // Ignore the escape character.
178 break;
179 }
180 }
181 break;
182
183 case runeDEL:
184 _backspace();
185 break;
186
187 default:
188 // Ignore the escape character.
189 break;
190 }
191 return runesConsumed;
192 }
193
194 int _handleRegularSequence(List<int> runes, int pos) {
195 var len = pos + 1;
196 while (len < runes.length && !_isControlCharacter(runes[len])) {
197 len++;
198 }
199 _addChars(runes.getRange(pos, len));
200 return len;
201 }
202
203 bool _isControlCharacter(int char) {
204 return (char >= 0x00 && char < 0x20) || (char == 0x7f);
205 }
206
207 void done() {
208 _stdin.echoMode = true;
209 _stdin.lineMode = true;
210 _stdinSubscription.cancel();
211 }
212
213 void _writePromptAndLine() {
214 _writePrompt();
215 var pos = _writeRange(_currentLine, 0, _currentLine.length);
216 _cursorPos = _move(pos, _cursorPos);
217 }
218
219 void _writePrompt() {
220 _stdout.write(prompt);
221 }
222
223 void _addChars(Iterable<int> chars) {
224 var newLine = [];
225 newLine..addAll(_currentLine.take(_cursorPos))
226 ..addAll(chars)
227 ..addAll(_currentLine.skip(_cursorPos));
228 _update(newLine, (_cursorPos + chars.length));
229 }
230
231 void _backspace() {
232 if (_cursorPos == 0) {
233 return;
234 }
235
236 var newLine = [];
237 newLine..addAll(_currentLine.take(_cursorPos - 1))
238 ..addAll(_currentLine.skip(_cursorPos));
239 _update(newLine, (_cursorPos - 1));
240 }
241
242 void _delete() {
243 if (_cursorPos == _currentLine.length) {
244 return;
245 }
246
247 var newLine = [];
248 newLine..addAll(_currentLine.take(_cursorPos))
249 ..addAll(_currentLine.skip(_cursorPos + 1));
250 _update(newLine, _cursorPos);
251 }
252
253 void _home() {
254 _updatePos(0);
255 }
256
257 void _end() {
258 _updatePos(_currentLine.length);
259 }
260
261 void _clearScreen() {
262 _stdout.write(_term.clear);
263 _writePromptAndLine();
264 }
265
266 void _kill() {
267 var newLine = [];
268 newLine.addAll(_currentLine.take(_cursorPos));
269 _killBuffer = _currentLine.skip(_cursorPos).toList();
270 _update(newLine, _cursorPos);
271 }
272
273 void _clearLine() {
274 _update([], 0);
275 }
276
277 void _yank() {
278 var newLine = [];
279 newLine..addAll(_currentLine.take(_cursorPos))
280 ..addAll(_killBuffer)
281 ..addAll(_currentLine.skip(_cursorPos));
282 _update(newLine, (_cursorPos + _killBuffer.length));
283 }
284
285 static String _trimLeadingSpaces(String line) {
286 bool _isSpace(int rune) {
287 return rune == runeSpace;
288 }
289 return new String.fromCharCodes(line.runes.skipWhile(_isSpace));
hausner 2013/11/21 19:24:56 Is this really correct? You are feeding runes into
turnidge 2013/11/22 19:59:58 I do not know if this is correct or not. :-/ Adde
290 }
291
292 static String _sharedPrefix(String one, String two) {
293 var len = min(one.length, two.length);
294 var runesOne = one.runes.toList();
295 var runesTwo = two.runes.toList();
296 var pos;
297 for (pos = 0; pos < len; pos++) {
298 if (runesOne[pos] != runesTwo[pos]) {
299 break;
300 }
301 }
302 var shared = new String.fromCharCodes(runesOne.take(pos));
303 return shared;
304 }
305
306 bool _complete(bool showCompletions) {
307 if (completer == null) {
308 return false;
309 }
310
311 var linePrefix = _currentLine.take(_cursorPos).toList();
312 List<String> commandParts =
313 _trimLeadingSpaces(new String.fromCharCodes(linePrefix)).split(' ');
314 List<String> completionList = completer(commandParts);
315 var completion = '';
316
317 if (completionList.length == 0) {
318 // The current line admits no possible completion.
319 return false;
320
321 } else if (completionList.length == 1) {
322 // There is a single, non-ambiguous completion for the current line.
323 completion = completionList[0];
324
325 // If we are at the end of the line, add a space to signal that
326 // the completion is unambiguous.
327 if (_currentLine.length == _cursorPos) {
328 completion = completion + ' ';
329 }
330 } else {
331 // There are ambiguous completions. Find the longest common
332 // shared prefix of all of the completions.
333 completion = completionList.fold(completionList[0], _sharedPrefix);
334 }
335
336 var lastWord = commandParts.last;
337 if (completion == lastWord) {
338 // The completion does not add anything.
339 if (showCompletions) {
340 // User hit double-TAB. Show them all possible completions.
341 _move(_cursorPos, _currentLine.length);
342 _stdout.writeln();
343 _stdout.writeln(completionList);
344 _writePromptAndLine();
345 }
346 return false;
347 } else {
348 // Apply the current completion.
349 var completionRunes = completion.runes.toList();
350
351 var newLine = [];
352 newLine..addAll(linePrefix)
353 ..addAll(completionRunes.skip(lastWord.length))
354 ..addAll(_currentLine.skip(_cursorPos));
355 _update(newLine, _cursorPos + completionRunes.length - lastWord.length);
356 return true;
357 }
358 }
359
360 void _newline() {
361 _addLineToHistory(_currentLine);
362 _linePos = _lines.length;
363
364 _end();
365 _stdout.writeln();
366
367 // Call the user's command handler.
368 _handleCommand(new String.fromCharCodes(_currentLine));
369
370 _currentLine = [];
371 _cursorPos = 0;
372 _linePos = _lines.length;
373 if (_promptShown) {
374 _writePrompt();
375 }
376 }
377
378 void _leftArrow() {
379 _updatePos(_cursorPos - 1);
380 }
381
382 void _rightArrow() {
383 _updatePos(_cursorPos + 1);
384 }
385
386 void _addLineToHistory(List<int> line) {
387 if (_tempLineAdded) {
388 _lines.removeLast();
389 _tempLineAdded = false;
390 }
391 if (line.length > 0) {
392 _lines.add(line);
393 }
394 }
395
396 void _addTempLineToHistory(List<int> line) {
397 _lines.add(line);
398 _tempLineAdded = true;
399 }
400
401 void _replaceHistory(List<int> line, int linePos) {
402 _lines[linePos] = line;
403 }
404
405 void _historyPrevious() {
406 if (_linePos == 0) {
407 return;
408 }
409
410 if (_linePos == _lines.length) {
411 _addTempLineToHistory(_currentLine);
412 } else {
413 // Any edits get committed to history.
414 _replaceHistory(_currentLine, _linePos);
hausner 2013/11/21 19:24:56 So does this mean that if at the prompt I hit up a
turnidge 2013/11/22 19:59:58 Isn't that weird?! That's what gdb and bash do :-
415 }
416
417 _linePos -= 1;
418 var line = _lines[_linePos];
419 _update(line, line.length);
420 }
421
422 void _historyNext() {
423 if (_linePos == (_lines.length - 1)) {
424 return;
425 }
426
427 // Any edits get committed to history.
428 _replaceHistory(_currentLine, _linePos);
429
430 _linePos += 1;
431 var line = _lines[_linePos];
432 _update(line, line.length);
433 }
434
435 void _updatePos(int newCursorPos) {
436 if (newCursorPos < 0) {
437 return;
438 }
439 if (newCursorPos > _currentLine.length) {
440 return;
441 }
442
443 _cursorPos = _move(_cursorPos, newCursorPos);
444 }
445
446 void _update(List<int> newLine, int newCursorPos) {
447 var pos = _cursorPos;
448 var diffPos;
449 var sharedLen = min(_currentLine.length, newLine.length);
450
451 // Find first difference.
452 for (diffPos = 0; diffPos < sharedLen; diffPos++) {
453 if (_currentLine[diffPos] != newLine[diffPos]) {
454 break;
455 }
456 }
457
458 // Move the cursor to where the difference begins.
459 pos = _move(pos, diffPos);
460
461 // Write the new text.
462 pos = _writeRange(newLine, pos, newLine.length);
463
464 // Clear any extra characters at the end.
465 pos = _clearRange(pos, _currentLine.length);
466
467 // Move the cursor back to the input point.
468 _cursorPos = _move(pos, newCursorPos);
469 _currentLine = newLine;
470 }
471
472 void hide() {
473 if (!_promptShown) {
474 return;
475 }
476 _promptShown = false;
477 // We need to erase everything, including the prompt.
478 var curLine = _getLine(_cursorPos);
479 var lastLine = _getLine(_currentLine.length);
480
481 // Go to last line.
482 if (curLine < lastLine) {
483 for (var i = 0; i < (lastLine - curLine); i++) {
484 // This moves us to column 0.
485 _stdout.write(_term.cursorDown);
486 }
487 curLine = lastLine;
488 } else {
489 // Move to column 0.
490 _stdout.write('\r');
491 }
492
493 // Work our way up, clearing lines.
494 while (true) {
495 _stdout.write(_term.clrEOL);
496 if (curLine > 0) {
497 _stdout.write(_term.cursorUp);
498 } else {
499 break;
500 }
501 }
502 }
503
504 void show() {
505 if (_promptShown) {
506 return;
507 }
508 _promptShown = true;
509 _writePromptAndLine();
510
511 // If input was buffered while the prompt was hidden, process it
512 // now.
513 if (!_bufferedInput.isEmpty) {
514 var input = _bufferedInput.toString();
515 _bufferedInput.clear();
516 _handleText(input);
517 }
518 }
519
520 int _writeRange(List<int> text, int pos, int writeToPos) {
521 if (pos >= writeToPos) {
522 return pos;
523 }
524 while (pos < writeToPos) {
525 var margin = _nextMargin(pos);
526 var limit = min(writeToPos, margin);
527 _stdout.write(new String.fromCharCodes(text.getRange(pos, limit)));
528 pos = limit;
529 if (pos == margin) {
530 _stdout.write('\n');
531 }
532 }
533 return pos;
534 }
535
536 int _clearRange(int pos, int clearToPos) {
537 if (pos >= clearToPos) {
538 return pos;
539 }
540 while (true) {
541 var limit = _nextMargin(pos);
542 _stdout.write(_term.clrEOL);
543 if (limit >= clearToPos) {
544 return pos;
545 }
546 _stdout.write('\n');
547 pos = limit;
548 }
549 }
550
551 int _move(int pos, int newPos) {
552 if (pos == newPos) {
553 return pos;
554 }
555
556 var curCol = _getCol(pos);
557 var curLine = _getLine(pos);
558 var newCol = _getCol(newPos);
559 var newLine = _getLine(newPos);
560
561 if (curLine > newLine) {
562 for (var i = 0; i < (curLine - newLine); i++) {
563 _stdout.write(_term.cursorUp);
564 }
565 }
566 if (curLine < newLine) {
567 for (var i = 0; i < (newLine - curLine); i++) {
568 _stdout.write(_term.cursorDown);
569 }
570
571 // Moving down resets column to zero, oddly.
572 curCol = 0;
573 }
574 if (curCol > newCol) {
575 for (var i = 0; i < (curCol - newCol); i++) {
576 _stdout.write(_term.cursorBack);
577 }
578 }
579 if (curCol < newCol) {
580 for (var i = 0; i < (newCol - curCol); i++) {
581 _stdout.write(_term.cursorForward);
582 }
583 }
584
585 return newPos;
586 }
587
588 int _nextMargin(int pos) {
589 var truePos = pos + prompt.length;
590 var curLine = _getLine(pos);
591 return ((truePos ~/ _screenWidth) + 1) * _screenWidth - prompt.length;
592 }
593
594 int _getLine(int pos) {
595 var truePos = pos + prompt.length;
596 return truePos ~/ _screenWidth;
597 }
598
599 int _getCol(int pos) {
600 var truePos = pos + prompt.length;
601 return truePos % _screenWidth;
602 }
603
604 Stdin _stdin;
605 StreamSubscription _stdinSubscription;
606 IOSink _stdout;
607 final _handleCommand;
608 final String prompt;
609 bool _promptShown = true;
610 final CommandCompleter completer;
611 TermInfo _term = new TermInfo();
612
613 int _screenWidth;
hausner 2013/11/21 19:24:56 This gets initialized once only in the constructor
turnidge 2013/11/22 19:59:58 Added a TODO. My intention was to update this whe
614 List<int> _currentLine = []; // A list of runes.
615 StringBuffer _bufferedInput = new StringBuffer();
616 List<List<int>> _lines = [];
hausner 2013/11/21 19:24:56 I would find a short description helpful that expl
turnidge 2013/11/22 19:59:58 Done.
617 bool _tempLineAdded = false;
618 int _linePos = 0;
619 int _cursorPos = 0;
620 int _tabCount = 0;
621 List<int> _killBuffer = [];
622 }
623
624
625 // Demo code.
626
627
628 List<String> _myCompleter(List<String> commandTokens) {
629 List<String> completions = new List<String>();
630
631 // First word completions.
632 if (commandTokens.length <= 1) {
633 String prefix = '';
634 if (commandTokens.length == 1) {
635 prefix = commandTokens.first;
636 }
637 if ('quit'.startsWith(prefix)) {
638 completions.add('quit');
639 }
640 if ('help'.startsWith(prefix)) {
641 completions.add('help');
642 }
643 if ('happyface'.startsWith(prefix)) {
644 completions.add('happyface');
645 }
646 }
647
648 // Complete 'foobar' or 'gondola' anywhere in string.
649 String lastWord = commandTokens.last;
650 if ('foobar'.startsWith(lastWord)) {
651 completions.add('foobar');
652 }
653 if ('gondola'.startsWith(lastWord)) {
654 completions.add('gondola');
655 }
656
657 return completions;
658 }
659
660
661 int _helpCount = 0;
662 Commando cmdo;
663
664
665 void _handleCommand(String rawCommand) {
666 String command = rawCommand.trim();
667 if (command == 'quit') {
668 cmdo.done();
669 } else if (command == 'help') {
670 switch (_helpCount) {
671 case 0:
672 print('I will not help you.');
673 break;
674 case 1:
675 print('I mean it.');
676 break;
677 case 2:
678 print('Seriously.');
679 break;
680 case 100:
681 print('Well now.');
682 break;
683 default:
684 print("Okay. Type 'quit' to quit");
685 break;
686 }
687 _helpCount++;
688 } else if (command == 'happyface') {
689 print(':-)');
690 } else {
691 print('Received command($command)');
692 }
693 }
694
695
696 void main() {
697 stdout.writeln('[Commando demo]');
698 cmd = new Commando(stdin, stdout, _handleCommand,
699 completer:_myCompleter);
700 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698