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

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) 2013, the Dart project authors. Please see the AUTHORS file
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;
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}) {
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();
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];
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)) {
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));
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 // The current in-progress line gets temporarily stored in history.
412 _addTempLineToHistory(_currentLine);
413 } else {
414 // Any edits get committed to history.
415 _replaceHistory(_currentLine, _linePos);
416 }
417
418 _linePos -= 1;
419 var line = _lines[_linePos];
420 _update(line, line.length);
421 }
422
423 void _historyNext() {
424 if (_linePos == (_lines.length - 1)) {
425 return;
426 }
427
428 // Any edits get committed to history.
429 _replaceHistory(_currentLine, _linePos);
430
431 _linePos += 1;
432 var line = _lines[_linePos];
433 _update(line, line.length);
434 }
435
436 void _updatePos(int newCursorPos) {
437 if (newCursorPos < 0) {
438 return;
439 }
440 if (newCursorPos > _currentLine.length) {
441 return;
442 }
443
444 _cursorPos = _move(_cursorPos, newCursorPos);
445 }
446
447 void _update(List<int> newLine, int newCursorPos) {
448 var pos = _cursorPos;
449 var diffPos;
450 var sharedLen = min(_currentLine.length, newLine.length);
451
452 // Find first difference.
453 for (diffPos = 0; diffPos < sharedLen; diffPos++) {
454 if (_currentLine[diffPos] != newLine[diffPos]) {
455 break;
456 }
457 }
458
459 // Move the cursor to where the difference begins.
460 pos = _move(pos, diffPos);
461
462 // Write the new text.
463 pos = _writeRange(newLine, pos, newLine.length);
464
465 // Clear any extra characters at the end.
466 pos = _clearRange(pos, _currentLine.length);
467
468 // Move the cursor back to the input point.
469 _cursorPos = _move(pos, newCursorPos);
470 _currentLine = newLine;
471 }
472
473 void hide() {
474 if (!_promptShown) {
475 return;
476 }
477 _promptShown = false;
478 // We need to erase everything, including the prompt.
479 var curLine = _getLine(_cursorPos);
480 var lastLine = _getLine(_currentLine.length);
481
482 // Go to last line.
483 if (curLine < lastLine) {
484 for (var i = 0; i < (lastLine - curLine); i++) {
485 // This moves us to column 0.
486 _stdout.write(_term.cursorDown);
487 }
488 curLine = lastLine;
489 } else {
490 // Move to column 0.
491 _stdout.write('\r');
492 }
493
494 // Work our way up, clearing lines.
495 while (true) {
496 _stdout.write(_term.clrEOL);
497 if (curLine > 0) {
498 _stdout.write(_term.cursorUp);
499 } else {
500 break;
501 }
502 }
503 }
504
505 void show() {
506 if (_promptShown) {
507 return;
508 }
509 _promptShown = true;
510 _writePromptAndLine();
511
512 // If input was buffered while the prompt was hidden, process it
513 // now.
514 if (!_bufferedInput.isEmpty) {
515 var input = _bufferedInput.toString();
516 _bufferedInput.clear();
517 _handleText(input);
518 }
519 }
520
521 int _writeRange(List<int> text, int pos, int writeToPos) {
522 if (pos >= writeToPos) {
523 return pos;
524 }
525 while (pos < writeToPos) {
526 var margin = _nextMargin(pos);
527 var limit = min(writeToPos, margin);
528 _stdout.write(new String.fromCharCodes(text.getRange(pos, limit)));
529 pos = limit;
530 if (pos == margin) {
531 _stdout.write('\n');
532 }
533 }
534 return pos;
535 }
536
537 int _clearRange(int pos, int clearToPos) {
538 if (pos >= clearToPos) {
539 return pos;
540 }
541 while (true) {
542 var limit = _nextMargin(pos);
543 _stdout.write(_term.clrEOL);
544 if (limit >= clearToPos) {
545 return pos;
546 }
547 _stdout.write('\n');
548 pos = limit;
549 }
550 }
551
552 int _move(int pos, int newPos) {
553 if (pos == newPos) {
554 return pos;
555 }
556
557 var curCol = _getCol(pos);
558 var curLine = _getLine(pos);
559 var newCol = _getCol(newPos);
560 var newLine = _getLine(newPos);
561
562 if (curLine > newLine) {
563 for (var i = 0; i < (curLine - newLine); i++) {
564 _stdout.write(_term.cursorUp);
565 }
566 }
567 if (curLine < newLine) {
568 for (var i = 0; i < (newLine - curLine); i++) {
569 _stdout.write(_term.cursorDown);
570 }
571
572 // Moving down resets column to zero, oddly.
573 curCol = 0;
574 }
575 if (curCol > newCol) {
576 for (var i = 0; i < (curCol - newCol); i++) {
577 _stdout.write(_term.cursorBack);
578 }
579 }
580 if (curCol < newCol) {
581 for (var i = 0; i < (newCol - curCol); i++) {
582 _stdout.write(_term.cursorForward);
583 }
584 }
585
586 return newPos;
587 }
588
589 int _nextMargin(int pos) {
590 var truePos = pos + prompt.length;
591 var curLine = _getLine(pos);
592 return ((truePos ~/ _screenWidth) + 1) * _screenWidth - prompt.length;
593 }
594
595 int _getLine(int pos) {
596 var truePos = pos + prompt.length;
597 return truePos ~/ _screenWidth;
598 }
599
600 int _getCol(int pos) {
601 var truePos = pos + prompt.length;
602 return truePos % _screenWidth;
603 }
604
605 Stdin _stdin;
606 StreamSubscription _stdinSubscription;
607 IOSink _stdout;
608 final _handleCommand;
609 final String prompt;
610 bool _promptShown = true;
611 final CommandCompleter completer;
612 TermInfo _term = new TermInfo();
613
614 int _screenWidth;
615 List<int> _currentLine = []; // A list of runes.
616 StringBuffer _bufferedInput = new StringBuffer();
617 List<List<int>> _lines = [];
618 bool _tempLineAdded = false;
619 int _linePos = 0;
620 int _cursorPos = 0;
621 int _tabCount = 0;
622 List<int> _killBuffer = [];
623 }
624
625
626 // Demo code.
627
628
629 List<String> _myCompleter(List<String> commandTokens) {
630 List<String> completions = new List<String>();
631
632 // First word completions.
633 if (commandTokens.length <= 1) {
634 String prefix = '';
635 if (commandTokens.length == 1) {
636 prefix = commandTokens.first;
637 }
638 if ('quit'.startsWith(prefix)) {
639 completions.add('quit');
640 }
641 if ('help'.startsWith(prefix)) {
642 completions.add('help');
643 }
644 if ('happyface'.startsWith(prefix)) {
645 completions.add('happyface');
646 }
647 }
648
649 // Complete 'foobar' or 'gondola' anywhere in string.
650 String lastWord = commandTokens.last;
651 if ('foobar'.startsWith(lastWord)) {
652 completions.add('foobar');
653 }
654 if ('gondola'.startsWith(lastWord)) {
655 completions.add('gondola');
656 }
657
658 return completions;
659 }
660
661
662 int _helpCount = 0;
663 Commando cmdo;
664
665
666 void _handleCommand(String rawCommand) {
667 String command = rawCommand.trim();
668 if (command == 'quit') {
669 cmdo.done();
670 } else if (command == 'help') {
671 switch (_helpCount) {
672 case 0:
673 print('I will not help you.');
674 break;
675 case 1:
676 print('I mean it.');
677 break;
678 case 2:
679 print('Seriously.');
680 break;
681 case 100:
682 print('Well now.');
683 break;
684 default:
685 print("Okay. Type 'quit' to quit");
686 break;
687 }
688 _helpCount++;
689 } else if (command == 'happyface') {
690 print(':-)');
691 } else {
692 print('Received command($command)');
693 }
694 }
695
696
697 void main() {
698 stdout.writeln('[Commando demo]');
699 cmd = new Commando(stdin, stdout, _handleCommand,
700 completer:_myCompleter);
701 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698