@@ -155,6 +155,8 @@ const kSavePreviousState = Symbol('_savePreviousState');
155
155
const kRestorePreviousState = Symbol ( '_restorePreviousState' ) ;
156
156
const kPreviousLine = Symbol ( '_previousLine' ) ;
157
157
const kPreviousCursor = Symbol ( '_previousCursor' ) ;
158
+ const kPreviousCursorCols = Symbol ( '_previousCursorCols' ) ;
159
+ const kMultilineMove = Symbol ( '_multilineMove' ) ;
158
160
const kPreviousPrevRows = Symbol ( '_previousPrevRows' ) ;
159
161
const kAddNewLineOnTTY = Symbol ( '_addNewLineOnTTY' ) ;
160
162
@@ -245,6 +247,7 @@ function InterfaceConstructor(input, output, completer, terminal) {
245
247
this [ kRedoStack ] = [ ] ;
246
248
this . history = history ;
247
249
this . historySize = historySize ;
250
+ this [ kPreviousCursorCols ] = - 1 ;
248
251
249
252
// The kill ring is a global list of blocks of text that were previously
250
253
// killed (deleted). If its size exceeds kMaxLengthOfKillRing, the oldest
@@ -1114,27 +1117,50 @@ class Interface extends InterfaceConstructor {
1114
1117
this [ kRefreshLine ] ( ) ;
1115
1118
}
1116
1119
1117
- [ kMoveDownOrHistoryNext ] ( ) {
1118
- const { cols, rows } = this . getCursorPos ( ) ;
1119
- const splitLine = StringPrototypeSplit ( this . line , '\n' ) ;
1120
- if ( ! this . historyIndex && rows === splitLine . length ) {
1121
- return ;
1120
+ [ kMultilineMove ] ( direction , splitLines , { rows, cols } ) {
1121
+ const curr = splitLines [ rows ] ;
1122
+ const down = direction === 1 ;
1123
+ const adj = splitLines [ rows + direction ] ;
1124
+ const promptLen = kMultilinePrompt . description . length ;
1125
+ let amountToMove ;
1126
+ // Clamp distance to end of current + prompt + next/prev line + newline
1127
+ const clamp = down ?
1128
+ curr . length - cols + promptLen + adj . length + 1 :
1129
+ - cols + 1 ;
1130
+ const shouldClamp = cols > adj . length + 1 ;
1131
+
1132
+ if ( shouldClamp ) {
1133
+ if ( this [ kPreviousCursorCols ] === - 1 ) {
1134
+ this [ kPreviousCursorCols ] = cols ;
1135
+ }
1136
+ amountToMove = clamp ;
1137
+ } else {
1138
+ if ( down ) {
1139
+ amountToMove = curr . length + 1 ;
1140
+ } else {
1141
+ amountToMove = - adj . length - 1 ;
1142
+ }
1143
+ if ( this [ kPreviousCursorCols ] !== - 1 ) {
1144
+ if ( this [ kPreviousCursorCols ] <= adj . length ) {
1145
+ amountToMove += this [ kPreviousCursorCols ] - cols ;
1146
+ this [ kPreviousCursorCols ] = - 1 ;
1147
+ } else {
1148
+ amountToMove = clamp ;
1149
+ }
1150
+ }
1122
1151
}
1123
- // Go to the next history only if the cursor is in the first line of the multiline input.
1124
- // Otherwise treat the "arrow down" as a movement to the next row.
1125
- if ( this [ kIsMultiline ] && rows < splitLine . length - 1 ) {
1126
- const currentLine = splitLine [ rows ] ;
1127
- const nextLine = splitLine [ rows + 1 ] ;
1128
- // If I am moving down and the current line is longer than the next line
1129
- const amountToMove = ( cols > nextLine . length + 1 ) ?
1130
- currentLine . length - cols + nextLine . length +
1131
- kMultilinePrompt . description . length + 1 : // Move to the end of the current line
1132
- // + chars to account for the kMultilinePrompt prefix, + 1 to go to the first char
1133
- currentLine . length + 1 ; // Otherwise just move to the next line, in the same position
1134
- this [ kMoveCursor ] ( amountToMove ) ;
1152
+
1153
+ this [ kMoveCursor ] ( amountToMove ) ;
1154
+ }
1155
+
1156
+ [ kMoveDownOrHistoryNext ] ( ) {
1157
+ const cursorPos = this . getCursorPos ( ) ;
1158
+ const splitLines = StringPrototypeSplit ( this . line , '\n' ) ;
1159
+ if ( this [ kIsMultiline ] && cursorPos . rows < splitLines . length - 1 ) {
1160
+ this [ kMultilineMove ] ( 1 , splitLines , cursorPos ) ;
1135
1161
return ;
1136
1162
}
1137
-
1163
+ this [ kPreviousCursorCols ] = - 1 ;
1138
1164
this [ kHistoryNext ] ( ) ;
1139
1165
}
1140
1166
@@ -1169,23 +1195,13 @@ class Interface extends InterfaceConstructor {
1169
1195
}
1170
1196
1171
1197
[ kMoveUpOrHistoryPrev ] ( ) {
1172
- const { cols, rows } = this . getCursorPos ( ) ;
1173
- if ( this . historyIndex === this . history . length && rows ) {
1174
- return ;
1175
- }
1176
- // Go to the previous history only if the cursor is in the first line of the multiline input.
1177
- // Otherwise treat the "arrow up" as a movement to the previous row.
1178
- if ( this [ kIsMultiline ] && rows > 0 ) {
1179
- const splitLine = StringPrototypeSplit ( this . line , '\n' ) ;
1180
- const previousLine = splitLine [ rows - 1 ] ;
1181
- // If I am moving up and the current line is longer than the previous line
1182
- const amountToMove = ( cols > previousLine . length + 1 ) ?
1183
- - cols + 1 : // Move to the beginning of the current line + 1 char to go to the end of the previous line
1184
- - previousLine . length - 1 ; // Otherwise just move to the previous line, in the same position
1185
- this [ kMoveCursor ] ( amountToMove ) ;
1198
+ const cursorPos = this . getCursorPos ( ) ;
1199
+ if ( this [ kIsMultiline ] && cursorPos . rows > 0 ) {
1200
+ const splitLines = StringPrototypeSplit ( this . line , '\n' ) ;
1201
+ this [ kMultilineMove ] ( - 1 , splitLines , cursorPos ) ;
1186
1202
return ;
1187
1203
}
1188
-
1204
+ this [ kPreviousCursorCols ] = - 1 ;
1189
1205
this [ kHistoryPrev ] ( ) ;
1190
1206
}
1191
1207
@@ -1296,6 +1312,7 @@ class Interface extends InterfaceConstructor {
1296
1312
const previousKey = this [ kPreviousKey ] ;
1297
1313
key ||= kEmptyObject ;
1298
1314
this [ kPreviousKey ] = key ;
1315
+ let shouldResetPreviousCursorCols = true ;
1299
1316
1300
1317
if ( ! key . meta || key . name !== 'y' ) {
1301
1318
// Reset yanking state unless we are doing yank pop.
@@ -1543,10 +1560,12 @@ class Interface extends InterfaceConstructor {
1543
1560
break ;
1544
1561
1545
1562
case 'up' :
1563
+ shouldResetPreviousCursorCols = false ;
1546
1564
this [ kMoveUpOrHistoryPrev ] ( ) ;
1547
1565
break ;
1548
1566
1549
1567
case 'down' :
1568
+ shouldResetPreviousCursorCols = false ;
1550
1569
this [ kMoveDownOrHistoryNext ] ( ) ;
1551
1570
break ;
1552
1571
@@ -1582,6 +1601,9 @@ class Interface extends InterfaceConstructor {
1582
1601
}
1583
1602
}
1584
1603
}
1604
+ if ( shouldResetPreviousCursorCols ) {
1605
+ this [ kPreviousCursorCols ] = - 1 ;
1606
+ }
1585
1607
}
1586
1608
1587
1609
/**
0 commit comments