@@ -155,6 +155,8 @@ const kSavePreviousState = Symbol('_savePreviousState');
155155const kRestorePreviousState = Symbol ( '_restorePreviousState' ) ;
156156const kPreviousLine = Symbol ( '_previousLine' ) ;
157157const kPreviousCursor = Symbol ( '_previousCursor' ) ;
158+ const kPreviousCursorCols = Symbol ( '_previousCursorCols' ) ;
159+ const kMultilineMove = Symbol ( '_multilineMove' ) ;
158160const kPreviousPrevRows = Symbol ( '_previousPrevRows' ) ;
159161const kAddNewLineOnTTY = Symbol ( '_addNewLineOnTTY' ) ;
160162
@@ -245,6 +247,7 @@ function InterfaceConstructor(input, output, completer, terminal) {
245247 this [ kRedoStack ] = [ ] ;
246248 this . history = history ;
247249 this . historySize = historySize ;
250+ this [ kPreviousCursorCols ] = - 1 ;
248251
249252 // The kill ring is a global list of blocks of text that were previously
250253 // killed (deleted). If its size exceeds kMaxLengthOfKillRing, the oldest
@@ -1114,27 +1117,50 @@ class Interface extends InterfaceConstructor {
11141117 this [ kRefreshLine ] ( ) ;
11151118 }
11161119
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+ }
11221151 }
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 ) ;
11351161 return ;
11361162 }
1137-
1163+ this [ kPreviousCursorCols ] = - 1 ;
11381164 this [ kHistoryNext ] ( ) ;
11391165 }
11401166
@@ -1169,23 +1195,13 @@ class Interface extends InterfaceConstructor {
11691195 }
11701196
11711197 [ 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 ) ;
11861202 return ;
11871203 }
1188-
1204+ this [ kPreviousCursorCols ] = - 1 ;
11891205 this [ kHistoryPrev ] ( ) ;
11901206 }
11911207
@@ -1296,6 +1312,7 @@ class Interface extends InterfaceConstructor {
12961312 const previousKey = this [ kPreviousKey ] ;
12971313 key ||= kEmptyObject ;
12981314 this [ kPreviousKey ] = key ;
1315+ let shouldResetPreviousCursorCols = true ;
12991316
13001317 if ( ! key . meta || key . name !== 'y' ) {
13011318 // Reset yanking state unless we are doing yank pop.
@@ -1543,10 +1560,12 @@ class Interface extends InterfaceConstructor {
15431560 break ;
15441561
15451562 case 'up' :
1563+ shouldResetPreviousCursorCols = false ;
15461564 this [ kMoveUpOrHistoryPrev ] ( ) ;
15471565 break ;
15481566
15491567 case 'down' :
1568+ shouldResetPreviousCursorCols = false ;
15501569 this [ kMoveDownOrHistoryNext ] ( ) ;
15511570 break ;
15521571
@@ -1582,6 +1601,9 @@ class Interface extends InterfaceConstructor {
15821601 }
15831602 }
15841603 }
1604+ if ( shouldResetPreviousCursorCols ) {
1605+ this [ kPreviousCursorCols ] = - 1 ;
1606+ }
15851607 }
15861608
15871609 /**
0 commit comments