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

Unified 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « tools/ddbg.dart ('k') | tools/ddbg/lib/terminfo.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: tools/ddbg/lib/commando.dart
===================================================================
--- tools/ddbg/lib/commando.dart (revision 0)
+++ tools/ddbg/lib/commando.dart (revision 0)
@@ -0,0 +1,710 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+import 'dart:math';
+
+import 'terminfo.dart';
+
+typedef List<String> CommandCompleter(List<String> commandParts);
+
+class Commando {
+ // Ctrl keys
+ static const runeCtrlA = 0x01;
+ static const runeCtrlB = 0x02;
+ static const runeCtrlD = 0x04;
+ static const runeCtrlE = 0x05;
+ static const runeCtrlF = 0x06;
+ static const runeTAB = 0x09;
+ static const runeNewline = 0x0a;
+ static const runeCtrlK = 0x0b;
+ static const runeCtrlL = 0x0c;
+ static const runeCtrlN = 0x0e;
+ static const runeCtrlP = 0x10;
+ static const runeCtrlU = 0x15;
+ static const runeCtrlY = 0x19;
+ static const runeESC = 0x1b;
+ static const runeSpace = 0x20;
+ static const runeDEL = 0x7F;
+
+ Commando(this._stdin,
+ this._stdout,
+ this._handleCommand,
+ {this.prompt : '> ', this.completer : null}) {
+ _stdin.echoMode = false;
+ _stdin.lineMode = false;
+ _screenWidth = _term.cols - 1;
+ _writePrompt();
+ _stdinSubscription =
+ _stdin.transform(UTF8.decoder).listen(_handleText, onDone:done);
+ }
+
+ void _handleText(String text) {
+ try {
+ if (!_promptShown) {
+ _bufferedInput.write(text);
+ return;
+ }
+
+ var runes = text.runes.toList();
+ var pos = 0;
+ while (pos < runes.length) {
+ if (!_promptShown) {
+ // A command was processed which hid the prompt. Buffer
+ // the rest of the input.
+ //
+ // TODO(turnidge): Here and elsewhere in the file I pass
+ // runes to String.fromCharCodes. Does this work?
+ _bufferedInput.write(
+ new String.fromCharCodes(runes.skip(pos)));
+ return;
+ }
+
+ var rune = runes[pos];
+
+ // Count consecutive tabs because double-tab is meaningful.
+ if (rune == runeTAB) {
+ _tabCount++;
+ } else {
+ _tabCount = 0;
+ }
+
+ if (_isControlRune(rune)) {
+ pos += _handleControlSequence(runes, pos);
+ } else {
+ pos += _handleRegularSequence(runes, pos);
+ }
+ }
+ } catch(e, trace) {
+ stderr.writeln('\nUnexpected exception: $e');
+ stderr.writeln(trace);
+ stderr.close().then((_) {
+ done();
+ });
+ }
+ }
+
+ int _handleControlSequence(List<int> runes, int pos) {
+ var runesConsumed = 1; // Most common result.
+ var char = runes[pos];
+ switch (char) {
+ case runeCtrlA:
+ _home();
+ break;
+
+ case runeCtrlB:
+ _leftArrow();
+ break;
+
+ case runeCtrlD:
+ if (_currentLine.length == 0) {
+ // ^D on an empty line means quit.
+ _stdout.writeln();
+ done();
+ } else {
+ _delete();
+ }
+ break;
+
+ case runeCtrlE:
+ _end();
+ break;
+
+ case runeCtrlF:
+ _rightArrow();
+ break;
+
+ case runeTAB:
+ if (_complete(_tabCount > 1)) {
+ _tabCount = 0;
+ }
+ break;
+
+ case runeNewline:
+ _newline();
+ break;
+
+ case runeCtrlK:
+ _kill();
+ break;
+
+ case runeCtrlL:
+ _clearScreen();
+ break;
+
+ case runeCtrlN:
+ _historyNext();
+ break;
+
+ case runeCtrlP:
+ _historyPrevious();
+ break;
+
+ case runeCtrlU:
+ _clearLine();
+ break;
+
+ case runeCtrlY:
+ _yank();
+ break;
+
+ case runeESC:
+ // Check to see if this is an arrow key.
+ if (pos + 2 < runes.length && // must be a 3 char sequence.
+ runes[pos + 1] == 0x5b) { // second char must be '['.
+ switch (runes[pos + 2]) {
+ case 0x41: // ^[[A = up arrow
+ _historyPrevious();
+ runesConsumed = 3;
+ break;
+
+ case 0x42: // ^[[B = down arrow
+ _historyNext();
+ runesConsumed = 3;
+ break;
+
+ case 0x43: // ^[[C = right arrow
+ _rightArrow();
+ runesConsumed = 3;
+ break;
+
+ case 0x44: // ^[[D = left arrow
+ _leftArrow();
+ runesConsumed = 3;
+ break;
+
+ default:
+ // Ignore the escape character.
+ break;
+ }
+ }
+ break;
+
+ case runeDEL:
+ _backspace();
+ break;
+
+ default:
+ // Ignore the escape character.
+ break;
+ }
+ return runesConsumed;
+ }
+
+ int _handleRegularSequence(List<int> runes, int pos) {
+ var len = pos + 1;
+ while (len < runes.length && !_isControlRune(runes[len])) {
+ len++;
+ }
+ _addChars(runes.getRange(pos, len));
+ return len;
+ }
+
+ bool _isControlRune(int char) {
+ return (char >= 0x00 && char < 0x20) || (char == 0x7f);
+ }
+
+ void done() {
+ _stdin.echoMode = true;
+ _stdin.lineMode = true;
+ _stdinSubscription.cancel();
+ }
+
+ void _writePromptAndLine() {
+ _writePrompt();
+ var pos = _writeRange(_currentLine, 0, _currentLine.length);
+ _cursorPos = _move(pos, _cursorPos);
+ }
+
+ void _writePrompt() {
+ _stdout.write(prompt);
+ }
+
+ void _addChars(Iterable<int> chars) {
+ var newLine = [];
+ newLine..addAll(_currentLine.take(_cursorPos))
+ ..addAll(chars)
+ ..addAll(_currentLine.skip(_cursorPos));
+ _update(newLine, (_cursorPos + chars.length));
+ }
+
+ void _backspace() {
+ if (_cursorPos == 0) {
+ return;
+ }
+
+ var newLine = [];
+ newLine..addAll(_currentLine.take(_cursorPos - 1))
+ ..addAll(_currentLine.skip(_cursorPos));
+ _update(newLine, (_cursorPos - 1));
+ }
+
+ void _delete() {
+ if (_cursorPos == _currentLine.length) {
+ return;
+ }
+
+ var newLine = [];
+ newLine..addAll(_currentLine.take(_cursorPos))
+ ..addAll(_currentLine.skip(_cursorPos + 1));
+ _update(newLine, _cursorPos);
+ }
+
+ void _home() {
+ _updatePos(0);
+ }
+
+ void _end() {
+ _updatePos(_currentLine.length);
+ }
+
+ void _clearScreen() {
+ _stdout.write(_term.clear);
+ _writePromptAndLine();
+ }
+
+ void _kill() {
+ var newLine = [];
+ newLine.addAll(_currentLine.take(_cursorPos));
+ _killBuffer = _currentLine.skip(_cursorPos).toList();
+ _update(newLine, _cursorPos);
+ }
+
+ void _clearLine() {
+ _update([], 0);
+ }
+
+ void _yank() {
+ var newLine = [];
+ newLine..addAll(_currentLine.take(_cursorPos))
+ ..addAll(_killBuffer)
+ ..addAll(_currentLine.skip(_cursorPos));
+ _update(newLine, (_cursorPos + _killBuffer.length));
+ }
+
+ static String _trimLeadingSpaces(String line) {
+ bool _isSpace(int rune) {
+ return rune == runeSpace;
+ }
+ return new String.fromCharCodes(line.runes.skipWhile(_isSpace));
+ }
+
+ static String _sharedPrefix(String one, String two) {
+ var len = min(one.length, two.length);
+ var runesOne = one.runes.toList();
+ var runesTwo = two.runes.toList();
+ var pos;
+ for (pos = 0; pos < len; pos++) {
+ if (runesOne[pos] != runesTwo[pos]) {
+ break;
+ }
+ }
+ var shared = new String.fromCharCodes(runesOne.take(pos));
+ return shared;
+ }
+
+ bool _complete(bool showCompletions) {
+ if (completer == null) {
+ return false;
+ }
+
+ var linePrefix = _currentLine.take(_cursorPos).toList();
+ List<String> commandParts =
+ _trimLeadingSpaces(new String.fromCharCodes(linePrefix)).split(' ');
+ List<String> completionList = completer(commandParts);
+ var completion = '';
+
+ if (completionList.length == 0) {
+ // The current line admits no possible completion.
+ return false;
+
+ } else if (completionList.length == 1) {
+ // There is a single, non-ambiguous completion for the current line.
+ completion = completionList[0];
+
+ // If we are at the end of the line, add a space to signal that
+ // the completion is unambiguous.
+ if (_currentLine.length == _cursorPos) {
+ completion = completion + ' ';
+ }
+ } else {
+ // There are ambiguous completions. Find the longest common
+ // shared prefix of all of the completions.
+ completion = completionList.fold(completionList[0], _sharedPrefix);
+ }
+
+ var lastWord = commandParts.last;
+ if (completion == lastWord) {
+ // The completion does not add anything.
+ if (showCompletions) {
+ // User hit double-TAB. Show them all possible completions.
+ _move(_cursorPos, _currentLine.length);
+ _stdout.writeln();
+ _stdout.writeln(completionList);
+ _writePromptAndLine();
+ }
+ return false;
+ } else {
+ // Apply the current completion.
+ var completionRunes = completion.runes.toList();
+
+ var newLine = [];
+ newLine..addAll(linePrefix)
+ ..addAll(completionRunes.skip(lastWord.length))
+ ..addAll(_currentLine.skip(_cursorPos));
+ _update(newLine, _cursorPos + completionRunes.length - lastWord.length);
+ return true;
+ }
+ }
+
+ void _newline() {
+ _addLineToHistory(_currentLine);
+ _linePos = _lines.length;
+
+ _end();
+ _stdout.writeln();
+
+ // Call the user's command handler.
+ _handleCommand(new String.fromCharCodes(_currentLine));
+
+ _currentLine = [];
+ _cursorPos = 0;
+ _linePos = _lines.length;
+ if (_promptShown) {
+ _writePrompt();
+ }
+ }
+
+ void _leftArrow() {
+ _updatePos(_cursorPos - 1);
+ }
+
+ void _rightArrow() {
+ _updatePos(_cursorPos + 1);
+ }
+
+ void _addLineToHistory(List<int> line) {
+ if (_tempLineAdded) {
+ _lines.removeLast();
+ _tempLineAdded = false;
+ }
+ if (line.length > 0) {
+ _lines.add(line);
+ }
+ }
+
+ void _addTempLineToHistory(List<int> line) {
+ _lines.add(line);
+ _tempLineAdded = true;
+ }
+
+ void _replaceHistory(List<int> line, int linePos) {
+ _lines[linePos] = line;
+ }
+
+ void _historyPrevious() {
+ if (_linePos == 0) {
+ return;
+ }
+
+ if (_linePos == _lines.length) {
+ // The current in-progress line gets temporarily stored in history.
+ _addTempLineToHistory(_currentLine);
+ } else {
+ // Any edits get committed to history.
+ _replaceHistory(_currentLine, _linePos);
+ }
+
+ _linePos -= 1;
+ var line = _lines[_linePos];
+ _update(line, line.length);
+ }
+
+ void _historyNext() {
+ if (_linePos == (_lines.length - 1)) {
+ return;
+ }
+
+ // Any edits get committed to history.
+ _replaceHistory(_currentLine, _linePos);
+
+ _linePos += 1;
+ var line = _lines[_linePos];
+ _update(line, line.length);
+ }
+
+ void _updatePos(int newCursorPos) {
+ if (newCursorPos < 0) {
+ return;
+ }
+ if (newCursorPos > _currentLine.length) {
+ return;
+ }
+
+ _cursorPos = _move(_cursorPos, newCursorPos);
+ }
+
+ void _update(List<int> newLine, int newCursorPos) {
+ var pos = _cursorPos;
+ var diffPos;
+ var sharedLen = min(_currentLine.length, newLine.length);
+
+ // Find first difference.
+ for (diffPos = 0; diffPos < sharedLen; diffPos++) {
+ if (_currentLine[diffPos] != newLine[diffPos]) {
+ break;
+ }
+ }
+
+ // Move the cursor to where the difference begins.
+ pos = _move(pos, diffPos);
+
+ // Write the new text.
+ pos = _writeRange(newLine, pos, newLine.length);
+
+ // Clear any extra characters at the end.
+ pos = _clearRange(pos, _currentLine.length);
+
+ // Move the cursor back to the input point.
+ _cursorPos = _move(pos, newCursorPos);
+ _currentLine = newLine;
+ }
+
+ void hide() {
+ if (!_promptShown) {
+ return;
+ }
+ _promptShown = false;
+ // We need to erase everything, including the prompt.
+ var curLine = _getLine(_cursorPos);
+ var lastLine = _getLine(_currentLine.length);
+
+ // Go to last line.
+ if (curLine < lastLine) {
+ for (var i = 0; i < (lastLine - curLine); i++) {
+ // This moves us to column 0.
+ _stdout.write(_term.cursorDown);
+ }
+ curLine = lastLine;
+ } else {
+ // Move to column 0.
+ _stdout.write('\r');
+ }
+
+ // Work our way up, clearing lines.
+ while (true) {
+ _stdout.write(_term.clrEOL);
+ if (curLine > 0) {
+ _stdout.write(_term.cursorUp);
+ } else {
+ break;
+ }
+ }
+ }
+
+ void show() {
+ if (_promptShown) {
+ return;
+ }
+ _promptShown = true;
+ _writePromptAndLine();
+
+ // If input was buffered while the prompt was hidden, process it
+ // now.
+ if (!_bufferedInput.isEmpty) {
+ var input = _bufferedInput.toString();
+ _bufferedInput.clear();
+ _handleText(input);
+ }
+ }
+
+ int _writeRange(List<int> text, int pos, int writeToPos) {
+ if (pos >= writeToPos) {
+ return pos;
+ }
+ while (pos < writeToPos) {
+ var margin = _nextMargin(pos);
+ var limit = min(writeToPos, margin);
+ _stdout.write(new String.fromCharCodes(text.getRange(pos, limit)));
+ pos = limit;
+ if (pos == margin) {
+ _stdout.write('\n');
+ }
+ }
+ return pos;
+ }
+
+ int _clearRange(int pos, int clearToPos) {
+ if (pos >= clearToPos) {
+ return pos;
+ }
+ while (true) {
+ var limit = _nextMargin(pos);
+ _stdout.write(_term.clrEOL);
+ if (limit >= clearToPos) {
+ return pos;
+ }
+ _stdout.write('\n');
+ pos = limit;
+ }
+ }
+
+ int _move(int pos, int newPos) {
+ if (pos == newPos) {
+ return pos;
+ }
+
+ var curCol = _getCol(pos);
+ var curLine = _getLine(pos);
+ var newCol = _getCol(newPos);
+ var newLine = _getLine(newPos);
+
+ if (curLine > newLine) {
+ for (var i = 0; i < (curLine - newLine); i++) {
+ _stdout.write(_term.cursorUp);
+ }
+ }
+ if (curLine < newLine) {
+ for (var i = 0; i < (newLine - curLine); i++) {
+ _stdout.write(_term.cursorDown);
+ }
+
+ // Moving down resets column to zero, oddly.
+ curCol = 0;
+ }
+ if (curCol > newCol) {
+ for (var i = 0; i < (curCol - newCol); i++) {
+ _stdout.write(_term.cursorBack);
+ }
+ }
+ if (curCol < newCol) {
+ for (var i = 0; i < (newCol - curCol); i++) {
+ _stdout.write(_term.cursorForward);
+ }
+ }
+
+ return newPos;
+ }
+
+ int _nextMargin(int pos) {
+ var truePos = pos + prompt.length;
+ var curLine = _getLine(pos);
+ return ((truePos ~/ _screenWidth) + 1) * _screenWidth - prompt.length;
+ }
+
+ int _getLine(int pos) {
+ var truePos = pos + prompt.length;
+ return truePos ~/ _screenWidth;
+ }
+
+ int _getCol(int pos) {
+ var truePos = pos + prompt.length;
+ return truePos % _screenWidth;
+ }
+
+ Stdin _stdin;
+ StreamSubscription _stdinSubscription;
+ IOSink _stdout;
+ final _handleCommand;
+ final String prompt;
+ bool _promptShown = true;
+ final CommandCompleter completer;
+ TermInfo _term = new TermInfo();
+
+ // TODO(turnidge): Update screenwidth when we clear the screen. See
+ // if we can get screen resize events too.
+ int _screenWidth;
+ List<int> _currentLine = []; // A list of runes.
+ StringBuffer _bufferedInput = new StringBuffer();
+ List<List<int>> _lines = [];
+
+ // When using the command history, the current line is temporarily
+ // added to the history to allow the user to return to it. This
+ // values tracks whether the history has a temporary line at the end.
+ bool _tempLineAdded = false;
+ int _linePos = 0;
+ int _cursorPos = 0;
+ int _tabCount = 0;
+ List<int> _killBuffer = [];
+}
+
+
+// Demo code.
+
+
+List<String> _myCompleter(List<String> commandTokens) {
+ List<String> completions = new List<String>();
+
+ // First word completions.
+ if (commandTokens.length <= 1) {
+ String prefix = '';
+ if (commandTokens.length == 1) {
+ prefix = commandTokens.first;
+ }
+ if ('quit'.startsWith(prefix)) {
+ completions.add('quit');
+ }
+ if ('help'.startsWith(prefix)) {
+ completions.add('help');
+ }
+ if ('happyface'.startsWith(prefix)) {
+ completions.add('happyface');
+ }
+ }
+
+ // Complete 'foobar' or 'gondola' anywhere in string.
+ String lastWord = commandTokens.last;
+ if ('foobar'.startsWith(lastWord)) {
+ completions.add('foobar');
+ }
+ if ('gondola'.startsWith(lastWord)) {
+ completions.add('gondola');
+ }
+
+ return completions;
+}
+
+
+int _helpCount = 0;
+Commando cmdo;
+
+
+void _handleCommand(String rawCommand) {
+ String command = rawCommand.trim();
+ if (command == 'quit') {
+ cmdo.done();
+ } else if (command == 'help') {
+ switch (_helpCount) {
+ case 0:
+ print('I will not help you.');
+ break;
+ case 1:
+ print('I mean it.');
+ break;
+ case 2:
+ print('Seriously.');
+ break;
+ case 100:
+ print('Well now.');
+ break;
+ default:
+ print("Okay. Type 'quit' to quit");
+ break;
+ }
+ _helpCount++;
+ } else if (command == 'happyface') {
+ print(':-)');
+ } else {
+ print('Received command($command)');
+ }
+}
+
+
+void main() {
+ stdout.writeln('[Commando demo]');
+ cmd = new Commando(stdin, stdout, _handleCommand,
+ completer:_myCompleter);
+}
« 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