@@ -168,9 +168,8 @@ export const MultilineInput = forwardRef<
168168 const stickyColumnRef = useRef < number | null > ( null )
169169
170170 // Helper to get or set the sticky column for vertical navigation.
171- // Note: Empty dependency array is intentional - we don't want to recreate this
172- // on every cursor move since it reads cursorPosition from closure only when
173- // stickyColumnRef.current is null.
171+ // When stickyColumnRef.current is set, we return it (preserving column across
172+ // multiple up/down presses). When null, we calculate from current cursor position.
174173 const getOrSetStickyColumn = useCallback (
175174 ( lineStarts : number [ ] , cursorIsChar : boolean ) : number => {
176175 if ( stickyColumnRef . current != null ) {
@@ -186,7 +185,7 @@ export const MultilineInput = forwardRef<
186185 stickyColumnRef . current = Math . max ( 0 , column )
187186 return stickyColumnRef . current
188187 } ,
189- [ ] ,
188+ [ cursorPosition ] ,
190189 )
191190
192191 // Update last activity on value or cursor changes
@@ -640,7 +639,7 @@ export const MultilineInput = forwardRef<
640639 preventKeyDefault ( key )
641640 if ( handleSelectionDeletion ( ) ) return true
642641 const newValue = value . slice ( 0 , cursorPosition ) + value . slice ( lineEnd )
643- onChange ( { text : newValue , cursorPosition, lastEditDueToNav : true } )
642+ onChange ( { text : newValue , cursorPosition, lastEditDueToNav : false } )
644643 return true
645644 }
646645
@@ -718,11 +717,29 @@ export const MultilineInput = forwardRef<
718717 ( key : KeyEvent ) : boolean => {
719718 const lowerKeyName = ( key . name ?? '' ) . toLowerCase ( )
720719 const isAltLikeModifier = isAltModifier ( key )
721- const lineStart = findLineStart ( value , cursorPosition )
722- const lineEnd = findLineEnd ( value , cursorPosition )
720+ const logicalLineStart = findLineStart ( value , cursorPosition )
721+ const logicalLineEnd = findLineEnd ( value , cursorPosition )
723722 const wordStart = findPreviousWordBoundary ( value , cursorPosition )
724723 const wordEnd = findNextWordBoundary ( value , cursorPosition )
725724
725+ // Read lineInfo inside the callback to get current value (not stale from closure)
726+ const currentLineInfo = textRef . current
727+ ? ( ( textRef . current as any ) . textBufferView as TextBufferView ) ?. lineInfo
728+ : null
729+
730+ // Calculate visual line boundaries from lineInfo (accounts for word wrap)
731+ // Fall back to logical line boundaries if visual info is unavailable
732+ const lineStarts = currentLineInfo ?. lineStarts ?? [ ]
733+ const visualLineIndex = lineStarts . findLastIndex (
734+ ( start ) => start <= cursorPosition ,
735+ )
736+ const visualLineStart = visualLineIndex >= 0
737+ ? lineStarts [ visualLineIndex ]
738+ : logicalLineStart
739+ const visualLineEnd = lineStarts [ visualLineIndex + 1 ] !== undefined
740+ ? lineStarts [ visualLineIndex + 1 ] - 1
741+ : logicalLineEnd
742+
726743 // Alt+Left/B: Word left
727744 if (
728745 isAltLikeModifier &&
@@ -760,7 +777,7 @@ export const MultilineInput = forwardRef<
760777 preventKeyDefault ( key )
761778 onChange ( {
762779 text : value ,
763- cursorPosition : lineStart ,
780+ cursorPosition : visualLineStart ,
764781 lastEditDueToNav : false ,
765782 } )
766783 return true
@@ -775,7 +792,7 @@ export const MultilineInput = forwardRef<
775792 preventKeyDefault ( key )
776793 onChange ( {
777794 text : value ,
778- cursorPosition : lineEnd ,
795+ cursorPosition : visualLineEnd ,
779796 lastEditDueToNav : false ,
780797 } )
781798 return true
@@ -844,7 +861,6 @@ export const MultilineInput = forwardRef<
844861 // Up arrow (no modifiers)
845862 if ( key . name === 'up' && ! key . ctrl && ! key . meta && ! key . option ) {
846863 preventKeyDefault ( key )
847- const lineStarts = lineInfo ?. lineStarts ?? [ ]
848864 const desiredIndex = getOrSetStickyColumn ( lineStarts , ! shouldHighlight )
849865 onChange ( {
850866 text : value ,
@@ -863,7 +879,6 @@ export const MultilineInput = forwardRef<
863879 // Down arrow (no modifiers)
864880 if ( key . name === 'down' && ! key . ctrl && ! key . meta && ! key . option ) {
865881 preventKeyDefault ( key )
866- const lineStarts = lineInfo ?. lineStarts ?? [ ]
867882 const desiredIndex = getOrSetStickyColumn ( lineStarts , ! shouldHighlight )
868883 onChange ( {
869884 text : value ,
@@ -881,7 +896,7 @@ export const MultilineInput = forwardRef<
881896
882897 return false
883898 } ,
884- [ value , cursorPosition , onChange , moveCursor , lineInfo , shouldHighlight , getOrSetStickyColumn ] ,
899+ [ value , cursorPosition , onChange , moveCursor , shouldHighlight , getOrSetStickyColumn ] ,
885900 )
886901
887902 // Handle character input (regular chars and tab)
0 commit comments