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

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

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

Powered by Google App Engine
This is Rietveld 408576698