Chromium Code Reviews| OLD | NEW |
|---|---|
| (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 } | |
| OLD | NEW |