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