diff --git a/go.mod b/go.mod index 6c74148..0a3b0de 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( github.com/MakeNowJust/heredoc v1.0.0 github.com/atotto/clipboard v0.1.4 - github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20241121171714-fbd5423ea935 + github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20241126192050-a8ed96118b08 github.com/charmbracelet/harmonica v0.2.0 github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20241121164047-8448a9be4804 github.com/charmbracelet/x/ansi v0.5.1 diff --git a/go.sum b/go.sum index dfc4079..277a374 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,8 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= -github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20241121171714-fbd5423ea935 h1:S+hhEwWnJxDeZMtHqIHgGVilNWsez3xmFOpwSc9GbcE= -github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20241121171714-fbd5423ea935/go.mod h1:BbC4R+6e9TLjbskxrjISt/DDCn4OiB6v+ArqfYiPyyg= +github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20241126192050-a8ed96118b08 h1:8hwULvCHjF6JjaeosebMGbB06oCv46d4s+Lbs5ytAT4= +github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20241126192050-a8ed96118b08/go.mod h1:BbC4R+6e9TLjbskxrjISt/DDCn4OiB6v+ArqfYiPyyg= github.com/charmbracelet/colorprofile v0.1.8 h1:PywDeXsiAzlPtkiiKgMEVLvb6nlEuKrMj9+FJBtj4jU= github.com/charmbracelet/colorprofile v0.1.8/go.mod h1:+jpmObxZl1Dab3H3IMVIPSZTsKcFpjJUv97G0dLqM60= github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ= diff --git a/textarea/textarea.go b/textarea/textarea.go index b64a0ef..6510d85 100644 --- a/textarea/textarea.go +++ b/textarea/textarea.go @@ -293,9 +293,12 @@ type Model struct { // Cursor row. row int - // The bubble offset in the parent model. + // The bubble offset relative to the parent bubble. offsetX, offsetY int + // The last recorded real cursor position. + realCol, realRow int + // Last character offset, used to maintain state when the cursor is moved // vertically such that we can maintain the same navigating position. lastCharOffset int @@ -383,6 +386,11 @@ func DefaultDarkStyles() Styles { return DefaultStyles(true) } +// SetOffset sets the offset of the textarea relative to the parent bubble. +func (m *Model) SetOffset(x, y int) { + m.offsetX, m.offsetY = x, y +} + // SetValue sets the value of the text input. func (m *Model) SetValue(s string) { m.Reset() @@ -536,11 +544,6 @@ func (m Model) Line() int { return m.row } -// SetOffset sets the bubble offset in the parent model. -func (m *Model) SetOffset(x, y int) { - m.offsetX, m.offsetY = x, y -} - // CursorDown moves the cursor down by one line. // Returns whether or not the cursor blink should be reset. func (m *Model) CursorDown() { @@ -661,6 +664,8 @@ func (m *Model) Reset() { m.value = make([][]rune, minHeight, maxLines) m.col = 0 m.row = 0 + m.realCol = 0 + m.realRow = 0 m.viewport.GotoTop() m.SetCursor(0) } @@ -1026,15 +1031,16 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { } // Need to check for completion before, because key is configurable and might be double assigned - keyMsg, ok := msg.(tea.KeyMsg) - if ok && key.Matches(keyMsg, m.KeyMap.AcceptSuggestion) { - if m.canAcceptSuggestion() { - m.value = m.matchedSuggestions[m.currentSuggestionIndex] - m.format() - m.row = len(m.value) - 1 - m.CursorEnd() - } - } + // keyMsg, ok := msg.(tea.KeyMsg) + // if ok && key.Matches(keyMsg, m.KeyMap.AcceptSuggestion) { + // if m.canAcceptSuggestion() { + // m.value = m.matchedSuggestions[m.currentSuggestionIndex] + // m.format() + // m.row = len(m.value) - 1 + // m.CursorEnd() + // m.SetSuggestions(nil) + // } + // } // Used to determine if the cursor should blink. oldRow, oldCol := m.cursorLineNumber(), m.col @@ -1173,11 +1179,23 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { cmds = append(cmds, tea.SetCursorPosition(m.offsetX+newCol, m.offsetY+newRow)) m.Cursor, cmd = m.Cursor.Update(msg) - if (newRow != oldRow || newCol != oldCol) && m.Cursor.Mode() == cursor.CursorBlink { + if cmd != nil { + cmds = append(cmds, cmd) + } + + if m.Cursor.Mode() == cursor.CursorBlink && (newRow != oldRow || newCol != oldCol) { m.Cursor.Blink = false - cmd = m.Cursor.BlinkCmd() + cmds = append(cmds, m.Cursor.BlinkCmd()) + } + + // Ensure the real cursor is at the correct position. + row := m.cursorLineNumber() + lineInfo := m.LineInfo() + realCol, realRow := m.offsetX+lineInfo.CharOffset, m.offsetY+row-m.viewport.YOffset + if realCol != m.realCol || realRow != m.realRow { + m.realCol, m.realRow = realCol, realRow + cmds = append(cmds, tea.SetCursorPosition(realCol, realRow)) } - cmds = append(cmds, cmd) m.repositionView() @@ -1197,7 +1215,7 @@ func (m Model) suggestionView(offset int) string { var lines []string for _, line := range strings.Split(suggestion[len(m.Value())+offset:], "\n") { - lines = append(lines, m.style.Placeholder.Inline(true).Render(line)) + lines = append(lines, m.activeStyle.Placeholder.Inline(true).Render(line)) } if len(lines) > m.Height() { m.SetHeight(len(lines) + 1) @@ -1217,7 +1235,7 @@ func (m Model) View() string { style lipgloss.Style newLines int widestLineNumber int - lineInfo = m.LineInfo() + // lineInfo = m.LineInfo() ) displayLine := 0 @@ -1276,45 +1294,20 @@ func (m Model) View() string { wrappedLine = []rune(strings.TrimSuffix(string(wrappedLine), " ")) padding -= m.width - strwidth } - if m.row == l && lineInfo.RowOffset == wl { - ln := string(wrappedLine[:lineInfo.ColumnOffset]) - if m.SyntaxHighlighter == nil { - ln = style.Render(ln) - } else { - ln = m.SyntaxHighlighter(ln) - } - s.WriteString(ln) - if m.col >= len(line) && lineInfo.CharOffset >= m.width { - m.Cursor.SetChar(" ") - s.WriteString(m.Cursor.View()) - // XXX: suggestions? - } else { - m.Cursor.SetChar(string(wrappedLine[lineInfo.ColumnOffset])) - if m.canAcceptSuggestion() && len(m.matchedSuggestions) > 0 { - suggestion := m.matchedSuggestions[m.currentSuggestionIndex] - if len(suggestion) >= m.row { - suggestion = suggestion[m.row:] - } - m.Cursor.TextStyle = m.style.Placeholder - if len(suggestion) > m.row && len(suggestion[m.row]) > m.col { - m.Cursor.SetChar(string(suggestion[m.row][m.col])) - } - } - s.WriteString(style.Render(m.Cursor.View())) - s.WriteString(style.Render(string(wrappedLine[lineInfo.ColumnOffset+1:]))) - s.WriteString(m.suggestionView(1)) - // XXX: suggestions - } + ln = string(wrappedLine) + if m.SyntaxHighlighter == nil { + ln = style.Render(ln) } else { - ln := string(wrappedLine) - if m.SyntaxHighlighter == nil { - ln = style.Render(ln) - } else { - ln = m.SyntaxHighlighter(ln) - } - s.WriteString(ln) + ln = m.SyntaxHighlighter(ln) } + s.WriteString(ln) + + // if m.col < len(line) || lineInfo.CharOffset < m.width { + // if m.canAcceptSuggestion() && len(m.matchedSuggestions) > 0 { + // s.WriteString(m.suggestionView(1)) + // } + // } pad := strings.Repeat(" ", max(0, padding)) if m.SyntaxHighlighter == nil {