| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 81e039f commit 0a31149
3 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1313,6 +1313,16 @@ const { createInterface } = require('readline'); | |||
| 1313 | 1313 | <td>Delete from the current position to the end of line</td> | |
| 1314 | 1314 | <td></td> | |
| 1315 | 1315 | </tr> | |
| 1316 | + <tr> | ||
| 1317 | + <td><kbd>Ctrl</kbd>+<kbd>Y</kbd></td> | ||
| 1318 | + <td>Yank (Recall) the previously deleted text</td> | ||
| 1319 | + <td>Only works with text deleted by <kbd>Ctrl</kbd>+<kbd>U</kbd> or <kbd>Ctrl</kbd>+<kbd>K</kbd></td> | ||
| 1320 | + </tr> | ||
| 1321 | + <tr> | ||
| 1322 | + <td><kbd>Meta</kbd>+<kbd>Y</kbd></td> | ||
| 1323 | + <td>Cycle among previously deleted lines</td> | ||
| 1324 | + <td>Only available when the last keystroke is <kbd>Ctrl</kbd>+<kbd>Y</kbd></td> | ||
| 1325 | + </tr> | ||
| 1316 | 1326 | <tr> | |
| 1317 | 1327 | <td><kbd>Ctrl</kbd>+<kbd>A</kbd></td> | |
| 1318 | 1328 | <td>Go to start of line</td> | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -81,6 +81,9 @@ const kQuestionCancel = Symbol('kQuestionCancel'); | |||
| 81 | 81 | // GNU readline library - keyseq-timeout is 500ms (default) | |
| 82 | 82 | const ESCAPE_CODE_TIMEOUT = 500; | |
| 83 | 83 | ||
| 84 | + // Max length of the kill ring | ||
| 85 | + const kMaxLengthOfKillRing = 32; | ||
| 86 | + | ||
| 84 | 87 | const kAddHistory = Symbol('_addHistory'); | |
| 85 | 88 | const kBeforeEdit = Symbol('_beforeEdit'); | |
| 86 | 89 | const kDecoder = Symbol('_decoder'); | |
@@ -96,12 +99,15 @@ const kHistoryPrev = Symbol('_historyPrev'); | |||
| 96 | 99 | const kInsertString = Symbol('_insertString'); | |
| 97 | 100 | const kLine = Symbol('_line'); | |
| 98 | 101 | const kLine_buffer = Symbol('_line_buffer'); | |
| 102 | + const kKillRing = Symbol('_killRing'); | ||
| 103 | + const kKillRingCursor = Symbol('_killRingCursor'); | ||
| 99 | 104 | const kMoveCursor = Symbol('_moveCursor'); | |
| 100 | 105 | const kNormalWrite = Symbol('_normalWrite'); | |
| 101 | 106 | const kOldPrompt = Symbol('_oldPrompt'); | |
| 102 | 107 | const kOnLine = Symbol('_onLine'); | |
| 103 | 108 | const kPreviousKey = Symbol('_previousKey'); | |
| 104 | 109 | const kPrompt = Symbol('_prompt'); | |
| 110 | + const kPushToKillRing = Symbol('_pushToKillRing'); | ||
| 105 | 111 | const kPushToUndoStack = Symbol('_pushToUndoStack'); | |
| 106 | 112 | const kQuestionCallback = Symbol('_questionCallback'); | |
| 107 | 113 | const kRedo = Symbol('_redo'); | |
@@ -118,6 +124,9 @@ const kUndoStack = Symbol('_undoStack'); | |||
| 118 | 124 | const kWordLeft = Symbol('_wordLeft'); | |
| 119 | 125 | const kWordRight = Symbol('_wordRight'); | |
| 120 | 126 | const kWriteToOutput = Symbol('_writeToOutput'); | |
| 127 | + const kYank = Symbol('_yank'); | ||
| 128 | + const kYanking = Symbol('_yanking'); | ||
| 129 | + const kYankPop = Symbol('_yankPop'); | ||
| 121 | 130 | ||
| 122 | 131 | function InterfaceConstructor(input, output, completer, terminal) { | |
| 123 | 132 | this[kSawReturnAt] = 0; | |
@@ -211,6 +220,15 @@ function InterfaceConstructor(input, output, completer, terminal) { | |||
| 211 | 220 | this[kRedoStack] = []; | |
| 212 | 221 | this.history = history; | |
| 213 | 222 | this.historySize = historySize; | |
| 223 | + | ||
| 224 | + // The kill ring is a global list of blocks of text that were previously | ||
| 225 | + // killed (deleted). If its size exceeds kMaxLengthOfKillRing, the oldest | ||
| 226 | + // element will be removed to make room for the latest deletion. With kill | ||
| 227 | + // ring, users are able to recall (yank) or cycle (yank pop) among previously | ||
| 228 | + // killed texts, quite similar to the behavior of Emacs. | ||
| 229 | + this[kKillRing] = []; | ||
| 230 | + this[kKillRingCursor] = 0; | ||
| 231 | + | ||
| 214 | 232 | this.removeHistoryDuplicates = !!removeHistoryDuplicates; | |
| 215 | 233 | this.crlfDelay = crlfDelay ? | |
| 216 | 234 | MathMax(kMincrlfDelay, crlfDelay) : | |
@@ -606,10 +624,12 @@ class Interface extends InterfaceConstructor { | |||
| 606 | 624 | this.cursor += c.length; | |
| 607 | 625 | this[kRefreshLine](); | |
| 608 | 626 | } else { | |
| 627 | + const oldPos = this.getCursorPos(); | ||
| 609 | 628 | this.line += c; | |
| 610 | 629 | this.cursor += c.length; | |
| 630 | + const newPos = this.getCursorPos(); | ||
| 611 | 631 | ||
| 612 | - if (this.getCursorPos().cols === 0) { | ||
| 632 | + if (oldPos.rows < newPos.rows) { | ||
| 613 | 633 | this[kRefreshLine](); | |
| 614 | 634 | } else { | |
| 615 | 635 | this[kWriteToOutput](c); | |
@@ -792,17 +812,57 @@ class Interface extends InterfaceConstructor { | |||
| 792 | 812 | ||
| 793 | 813 | [kDeleteLineLeft]() { | |
| 794 | 814 | this[kBeforeEdit](this.line, this.cursor); | |
| 815 | + const del = StringPrototypeSlice(this.line, 0, this.cursor); | ||
| 795 | 816 | this.line = StringPrototypeSlice(this.line, this.cursor); | |
| 796 | 817 | this.cursor = 0; | |
| 818 | + this[kPushToKillRing](del); | ||
| 797 | 819 | this[kRefreshLine](); | |
| 798 | 820 | } | |
| 799 | 821 | ||
| 800 | 822 | [kDeleteLineRight]() { | |
| 801 | 823 | this[kBeforeEdit](this.line, this.cursor); | |
| 824 | + const del = StringPrototypeSlice(this.line, this.cursor); | ||
| 802 | 825 | this.line = StringPrototypeSlice(this.line, 0, this.cursor); | |
| 826 | + this[kPushToKillRing](del); | ||
| 803 | 827 | this[kRefreshLine](); | |
| 804 | 828 | } | |
| 805 | 829 | ||
| 830 | + [kPushToKillRing](del) { | ||
| 831 | + if (!del || del === this[kKillRing][0]) return; | ||
| 832 | + ArrayPrototypeUnshift(this[kKillRing], del); | ||
| 833 | + this[kKillRingCursor] = 0; | ||
| 834 | + while (this[kKillRing].length > kMaxLengthOfKillRing) | ||
| 835 | + ArrayPrototypePop(this[kKillRing]); | ||
| 836 | + } | ||
| 837 | + | ||
| 838 | + [kYank]() { | ||
| 839 | + if (this[kKillRing].length > 0) { | ||
| 840 | + this[kYanking] = true; | ||
| 841 | + this[kInsertString](this[kKillRing][this[kKillRingCursor]]); | ||
| 842 | + } | ||
| 843 | + } | ||
| 844 | + | ||
| 845 | + [kYankPop]() { | ||
| 846 | + if (!this[kYanking]) { | ||
| 847 | + return; | ||
| 848 | + } | ||
| 849 | + if (this[kKillRing].length > 1) { | ||
| 850 | + const lastYank = this[kKillRing][this[kKillRingCursor]]; | ||
| 851 | + this[kKillRingCursor]++; | ||
| 852 | + if (this[kKillRingCursor] >= this[kKillRing].length) { | ||
| 853 | + this[kKillRingCursor] = 0; | ||
| 854 | + } | ||
| 855 | + const currentYank = this[kKillRing][this[kKillRingCursor]]; | ||
| 856 | + const head = | ||
| 857 | + StringPrototypeSlice(this.line, 0, this.cursor - lastYank.length); | ||
| 858 | + const tail = | ||
| 859 | + StringPrototypeSlice(this.line, this.cursor); | ||
| 860 | + this.line = head + currentYank + tail; | ||
| 861 | + this.cursor = head.length + currentYank.length; | ||
| 862 | + this[kRefreshLine](); | ||
| 863 | + } | ||
| 864 | + } | ||
| 865 | + | ||
| 806 | 866 | clearLine() { | |
| 807 | 867 | this[kMoveCursor](+Infinity); | |
| 808 | 868 | this[kWriteToOutput]('\r\n'); | |
@@ -984,6 +1044,11 @@ class Interface extends InterfaceConstructor { | |||
| 984 | 1044 | key = key || {}; | |
| 985 | 1045 | this[kPreviousKey] = key; | |
| 986 | 1046 | ||
| 1047 | + if (!key.meta || key.name !== 'y') { | ||
| 1048 | + // Reset yanking state unless we are doing yank pop. | ||
| 1049 | + this[kYanking] = false; | ||
| 1050 | + } | ||
| 1051 | + | ||
| 987 | 1052 | // Activate or deactivate substring search. | |
| 988 | 1053 | if ( | |
| 989 | 1054 | (key.name === 'up' || key.name === 'down') && | |
@@ -1094,6 +1159,10 @@ class Interface extends InterfaceConstructor { | |||
| 1094 | 1159 | this[kHistoryPrev](); | |
| 1095 | 1160 | break; | |
| 1096 | 1161 | ||
| 1162 | + case 'y': // Yank killed string | ||
| 1163 | + this[kYank](); | ||
| 1164 | + break; | ||
| 1165 | + | ||
| 1097 | 1166 | case 'z': | |
| 1098 | 1167 | if (process.platform === 'win32') break; | |
| 1099 | 1168 | if (this.listenerCount('SIGTSTP') > 0) { | |
@@ -1158,6 +1227,10 @@ class Interface extends InterfaceConstructor { | |||
| 1158 | 1227 | case 'backspace': // Delete backwards to a word boundary | |
| 1159 | 1228 | this[kDeleteWordLeft](); | |
| 1160 | 1229 | break; | |
| 1230 | + | ||
| 1231 | + case 'y': // Doing yank pop | ||
| 1232 | + this[kYankPop](); | ||
| 1233 | + break; | ||
| 1161 | 1234 | } | |
| 1162 | 1235 | } else { | |
| 1163 | 1236 | /* No modifier keys used */ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -674,6 +674,77 @@ function assertCursorRowsAndCols(rli, rows, cols) { | |||
| 674 | 674 | rli.close(); | |
| 675 | 675 | } | |
| 676 | 676 | ||
| 677 | + // yank | ||
| 678 | + { | ||
| 679 | + const [rli, fi] = getInterface({ terminal: true, prompt: '' }); | ||
| 680 | + fi.emit('data', 'the quick brown fox'); | ||
| 681 | + assertCursorRowsAndCols(rli, 0, 19); | ||
| 682 | + | ||
| 683 | + // Go to the start of the line | ||
| 684 | + fi.emit('keypress', '.', { ctrl: true, name: 'a' }); | ||
| 685 | + // Move forward one char | ||
| 686 | + fi.emit('keypress', '.', { ctrl: true, name: 'f' }); | ||
| 687 | + // Delete the right part | ||
| 688 | + fi.emit('keypress', '.', { ctrl: true, shift: true, name: 'delete' }); | ||
| 689 | + assertCursorRowsAndCols(rli, 0, 1); | ||
| 690 | + | ||
| 691 | + // Yank | ||
| 692 | + fi.emit('keypress', '.', { ctrl: true, name: 'y' }); | ||
| 693 | + assertCursorRowsAndCols(rli, 0, 19); | ||
| 694 | + | ||
| 695 | + rli.on('line', common.mustCall((line) => { | ||
| 696 | + assert.strictEqual(line, 'the quick brown fox'); | ||
| 697 | + })); | ||
| 698 | + | ||
| 699 | + fi.emit('data', '\n'); | ||
| 700 | + rli.close(); | ||
| 701 | + } | ||
| 702 | + | ||
| 703 | + // yank pop | ||
| 704 | + { | ||
| 705 | + const [rli, fi] = getInterface({ terminal: true, prompt: '' }); | ||
| 706 | + fi.emit('data', 'the quick brown fox'); | ||
| 707 | + assertCursorRowsAndCols(rli, 0, 19); | ||
| 708 | + | ||
| 709 | + // Go to the start of the line | ||
| 710 | + fi.emit('keypress', '.', { ctrl: true, name: 'a' }); | ||
| 711 | + // Move forward one char | ||
| 712 | + fi.emit('keypress', '.', { ctrl: true, name: 'f' }); | ||
| 713 | + // Delete the right part | ||
| 714 | + fi.emit('keypress', '.', { ctrl: true, shift: true, name: 'delete' }); | ||
| 715 | + assertCursorRowsAndCols(rli, 0, 1); | ||
| 716 | + // Yank | ||
| 717 | + fi.emit('keypress', '.', { ctrl: true, name: 'y' }); | ||
| 718 | + assertCursorRowsAndCols(rli, 0, 19); | ||
| 719 | + | ||
| 720 | + // Go to the start of the line | ||
| 721 | + fi.emit('keypress', '.', { ctrl: true, name: 'a' }); | ||
| 722 | + // Move forward four chars | ||
| 723 | + fi.emit('keypress', '.', { ctrl: true, name: 'f' }); | ||
| 724 | + fi.emit('keypress', '.', { ctrl: true, name: 'f' }); | ||
| 725 | + fi.emit('keypress', '.', { ctrl: true, name: 'f' }); | ||
| 726 | + fi.emit('keypress', '.', { ctrl: true, name: 'f' }); | ||
| 727 | + // Delete the right part | ||
| 728 | + fi.emit('keypress', '.', { ctrl: true, shift: true, name: 'delete' }); | ||
| 729 | + assertCursorRowsAndCols(rli, 0, 4); | ||
| 730 | + // Go to the start of the line | ||
| 731 | + fi.emit('keypress', '.', { ctrl: true, name: 'a' }); | ||
| 732 | + assertCursorRowsAndCols(rli, 0, 0); | ||
| 733 | + | ||
| 734 | + // Yank: 'quick brown fox|the ' | ||
| 735 | + fi.emit('keypress', '.', { ctrl: true, name: 'y' }); | ||
| 736 | + // Yank pop: 'he quick brown fox|the' | ||
| 737 | + fi.emit('keypress', '.', { meta: true, name: 'y' }); | ||
| 738 | + assertCursorRowsAndCols(rli, 0, 18); | ||
| 739 | + | ||
| 740 | + rli.on('line', common.mustCall((line) => { | ||
| 741 | + assert.strictEqual(line, 'he quick brown foxthe '); | ||
| 742 | + })); | ||
| 743 | + | ||
| 744 | + fi.emit('data', '\n'); | ||
| 745 | + rli.close(); | ||
| 746 | + } | ||
| 747 | + | ||
| 677 | 748 | // Close readline interface | |
| 678 | 749 | { | |
| 679 | 750 | const [rli, fi] = getInterface({ terminal: true, prompt: '' }); | |
| Back | FazBrowse Home | New Git URL |
0 commit comments