@@ -246,7 +246,6 @@ func (m *model) Update(msg tea.Msg) (layout.Model, tea.Cmd) {
246246 // Insert paste content into the inline edit textarea
247247 if m .inlineEditMsgIndex >= 0 {
248248 m .inlineEditTextarea .InsertString (msg .Content )
249- m .updateInlineEditTextareaHeight ()
250249 m .invalidateItem (m .inlineEditMsgIndex )
251250 m .renderDirty = true
252251 }
@@ -388,7 +387,6 @@ func (m *model) handleKeyPress(msg tea.KeyPressMsg) (layout.Model, tea.Cmd) {
388387 // Forward to textarea for newline insertion
389388 var cmd tea.Cmd
390389 m .inlineEditTextarea , cmd = m .inlineEditTextarea .Update (msg )
391- m .updateInlineEditTextareaHeight ()
392390 m .invalidateItem (m .inlineEditMsgIndex )
393391 m .renderDirty = true
394392 return m , cmd
@@ -407,7 +405,6 @@ func (m *model) handleKeyPress(msg tea.KeyPressMsg) (layout.Model, tea.Cmd) {
407405 // Forward all other keys to the textarea
408406 var cmd tea.Cmd
409407 m .inlineEditTextarea , cmd = m .inlineEditTextarea .Update (msg )
410- m .updateInlineEditTextareaHeight ()
411408 m .invalidateItem (m .inlineEditMsgIndex )
412409 m .renderDirty = true
413410 return m , cmd
@@ -964,55 +961,38 @@ func (m *model) renderInlineEditTextarea() string {
964961 m .inlineEditTextarea .SetWidth (innerWidth )
965962 }
966963
964+ // The textarea is set to a large height to prevent internal viewport scrolling
965+ // which causes cursor positioning bugs in multi-line content. We trim the
966+ // end-of-buffer padding lines from the rendered output.
967+ view := m .inlineEditTextarea .View ()
968+ view = trimEndOfBufferLines (view )
969+
967970 // Add a minimal edit indicator at the bottom left with extra padding
968971 editHint := styles .MutedStyle .Render ("[editing]" )
969972
970- content := m . inlineEditTextarea . View () + "\n \n " + editHint
973+ content := view + "\n \n " + editHint
971974 return editStyle .Width (m .contentWidth ()).Render (content )
972975}
973976
974- // updateInlineEditTextareaHeight recalculates and sets the textarea height based on current content.
975- func (m * model ) updateInlineEditTextareaHeight () {
976- if m .inlineEditMsgIndex < 0 {
977- return
978- }
977+ // trimEndOfBufferLines removes trailing end-of-buffer padding lines from a
978+ // textarea's rendered View output. The textarea pads its view to fill its
979+ // configured height; these padding lines contain only whitespace (after
980+ // stripping ANSI sequences) and appear after the actual content.
981+ func trimEndOfBufferLines (view string ) string {
982+ lines := strings .Split (view , "\n " )
979983
980- editStyle := styles .UserMessageStyle
981- innerWidth := m .contentWidth () - editStyle .GetHorizontalFrameSize ()
982- if innerWidth <= 0 {
983- return
984- }
985-
986- content := m .inlineEditTextarea .Value ()
987- lineCount := 0
988- for line := range strings .SplitSeq (content , "\n " ) {
989- lineWidth := ansi .StringWidth (line )
990- if lineWidth == 0 {
991- lineCount ++
992- } else {
993- lineCount += (lineWidth + innerWidth - 1 ) / innerWidth
994- }
984+ // Trim trailing lines that are visually empty (whitespace-only after ANSI strip).
985+ // Content lines always contain visible text or cursor escape sequences.
986+ last := len (lines )
987+ for last > 0 && strings .TrimSpace (ansi .Strip (lines [last - 1 ])) == "" {
988+ last --
995989 }
996990
997- newHeight := max (1 , lineCount )
998- if m .inlineEditTextarea .Height () == newHeight {
999- return
991+ if last == 0 {
992+ return view
1000993 }
1001994
1002- // Save cursor position
1003- cursorRow := m .inlineEditTextarea .Line ()
1004- cursorCol := m .inlineEditTextarea .LineInfo ().ColumnOffset
1005-
1006- m .inlineEditTextarea .SetHeight (newHeight )
1007-
1008- // Reset viewport scroll state by moving to start then restoring position
1009- // NOTE(krissetto): This is a workaround because the textarea's internal viewport
1010- // scrolling is not updated when the height is changed.
1011- m .inlineEditTextarea .MoveToBegin ()
1012- for range cursorRow {
1013- m .inlineEditTextarea .CursorDown ()
1014- }
1015- m .inlineEditTextarea .SetCursorColumn (cursorCol )
995+ return strings .Join (lines [:last ], "\n " )
1016996}
1017997
1018998func (m * model ) needsSeparator (index int ) bool {
@@ -1704,23 +1684,10 @@ func (m *model) StartInlineEdit(msgIndex, sessionPosition int, content string) t
17041684 ta .SetWidth (innerWidth )
17051685 }
17061686
1707- // Calculate appropriate height based on content
1708- // Count lines and account for word wrapping
1709- lineCount := 0
1710- if innerWidth > 0 {
1711- for line := range strings .SplitSeq (content , "\n " ) {
1712- lineWidth := ansi .StringWidth (line )
1713- if lineWidth == 0 {
1714- // Empty line counts as 1 line
1715- lineCount ++
1716- } else {
1717- // Account for word wrapping: ceil(lineWidth / innerWidth)
1718- lineCount += (lineWidth + innerWidth - 1 ) / innerWidth
1719- }
1720- }
1721- }
1722- // Set height to match content (minimum 1 line)
1723- ta .SetHeight (max (1 , lineCount ))
1687+ // Set a generous height so the textarea's internal viewport never scrolls.
1688+ // This prevents cursor positioning bugs with multi-line content. The actual
1689+ // rendered output is trimmed in renderInlineEditTextarea to remove padding.
1690+ ta .SetHeight (max (1 , m .height ))
17241691
17251692 // Remove the default prompt/placeholder styling for a cleaner look
17261693 ta .Prompt = ""
0 commit comments