diff --git a/go.mod b/go.mod index 4c08477..5bb6b70 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.20250114201644-43a5b4dd0af0 + github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20250123211610-443afa6fa0c1 github.com/charmbracelet/harmonica v0.2.0 github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20250114171829-b67eb015d607 github.com/charmbracelet/x/ansi v0.7.0 diff --git a/go.sum b/go.sum index 2346187..7a9bc21 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,14 @@ github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWp 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.20250114201644-43a5b4dd0af0 h1:BWjXQRSwBjoCpLeNu8zT93n+NHhZZhkQQLveXMmnkYc= github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20250114201644-43a5b4dd0af0/go.mod h1:hT2875Ank3ylgW13kqu6cjDc9XIk9sE5JsOFYdl09b8= +github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20250123204203-55f6f9f70bf6 h1:L2+Kl71AsucUpl32AqmbjVv/4Ha7dwlSFwqrU4sAeTE= +github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20250123204203-55f6f9f70bf6/go.mod h1:hT2875Ank3ylgW13kqu6cjDc9XIk9sE5JsOFYdl09b8= +github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20250123210727-80fa20da7d7b h1:QqN3KApDbHJl+B1lVSir6GyRbxH7EA6U1SCDoxz8xYU= +github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20250123210727-80fa20da7d7b/go.mod h1:hT2875Ank3ylgW13kqu6cjDc9XIk9sE5JsOFYdl09b8= +github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20250123210853-99e3bbf892cd h1:1WsMNlPUaDXgJprIvWg+ZsXmc4GiL4KsBEFNZ3ymKeA= +github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20250123210853-99e3bbf892cd/go.mod h1:hT2875Ank3ylgW13kqu6cjDc9XIk9sE5JsOFYdl09b8= +github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20250123211610-443afa6fa0c1 h1:tktnM4YimEWSYd58iZlPDB3Xz25/r94VYZZsHK5zWL0= +github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20250123211610-443afa6fa0c1/go.mod h1:hT2875Ank3ylgW13kqu6cjDc9XIk9sE5JsOFYdl09b8= github.com/charmbracelet/colorprofile v0.1.9 h1:5JnfvX+I9D6rRNu8xK3pgIqknaBVTXHU9pGu1jkZxLw= github.com/charmbracelet/colorprofile v0.1.9/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 0bd7700..d35c6ca 100644 --- a/textarea/textarea.go +++ b/textarea/textarea.go @@ -231,8 +231,8 @@ type Model struct { // when switching focus states. activeStyle *StyleState - // Cursor is the text area cursor. - Cursor cursor.Model + // VirtualCursor is the text area cursor. + VirtualCursor cursor.Model // CharLimit is the maximum number of characters this input element will // accept. If 0 or less, there's no limit. @@ -305,7 +305,7 @@ func New() Model { cache: memoization.NewMemoCache[line, [][]rune](maxLines), EndOfBufferCharacter: ' ', ShowLineNumbers: true, - Cursor: cur, + VirtualCursor: cur, KeyMap: DefaultKeyMap(), value: make([][]rune, minHeight, maxLines), @@ -600,7 +600,7 @@ func (m Model) Focused() bool { func (m *Model) Focus() tea.Cmd { m.focus = true m.activeStyle = &m.Styles.Focused - return m.Cursor.Focus() + return m.VirtualCursor.Focus() } // Blur removes the focus state on the model. When the model is blurred it can @@ -608,7 +608,7 @@ func (m *Model) Focus() tea.Cmd { func (m *Model) Blur() { m.focus = false m.activeStyle = &m.Styles.Blurred - m.Cursor.Blur() + m.VirtualCursor.Blur() } // Reset sets the input to its default state with no input. @@ -976,7 +976,7 @@ func (m *Model) SetHeight(h int) { // Update is the Bubble Tea update loop. func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { if !m.focus { - m.Cursor.Blur() + m.VirtualCursor.Blur() return m, nil } @@ -1098,10 +1098,10 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { cmds = append(cmds, cmd) newRow, newCol := m.cursorLineNumber(), m.col - m.Cursor, cmd = m.Cursor.Update(msg) - if (newRow != oldRow || newCol != oldCol) && m.Cursor.Mode() == cursor.CursorBlink { - m.Cursor.Blink = false - cmd = m.Cursor.BlinkCmd() + m.VirtualCursor, cmd = m.VirtualCursor.Update(msg) + if (newRow != oldRow || newCol != oldCol) && m.VirtualCursor.Mode() == cursor.CursorBlink { + m.VirtualCursor.Blink = false + cmd = m.VirtualCursor.BlinkCmd() } cmds = append(cmds, cmd) @@ -1115,7 +1115,7 @@ func (m Model) View() string { if m.Value() == "" && m.row == 0 && m.col == 0 && m.Placeholder != "" { return m.placeholderView() } - m.Cursor.TextStyle = m.activeStyle.computedCursorLine() + m.VirtualCursor.TextStyle = m.activeStyle.computedCursorLine() var ( s strings.Builder @@ -1184,11 +1184,11 @@ func (m Model) View() string { if m.row == l && lineInfo.RowOffset == wl { s.WriteString(style.Render(string(wrappedLine[:lineInfo.ColumnOffset]))) if m.col >= len(line) && lineInfo.CharOffset >= m.width { - m.Cursor.SetChar(" ") - s.WriteString(m.Cursor.View()) + m.VirtualCursor.SetChar(" ") + s.WriteString(m.VirtualCursor.View()) } else { - m.Cursor.SetChar(string(wrappedLine[lineInfo.ColumnOffset])) - s.WriteString(style.Render(m.Cursor.View())) + m.VirtualCursor.SetChar(string(wrappedLine[lineInfo.ColumnOffset])) + s.WriteString(style.Render(m.VirtualCursor.View())) s.WriteString(style.Render(string(wrappedLine[lineInfo.ColumnOffset+1:]))) } } else { @@ -1291,9 +1291,9 @@ func (m Model) placeholderView() string { // first line case i == 0: // first character of first line as cursor with character - m.Cursor.TextStyle = m.activeStyle.computedPlaceholder() - m.Cursor.SetChar(string(plines[0][0])) - s.WriteString(lineStyle.Render(m.Cursor.View())) + m.VirtualCursor.TextStyle = m.activeStyle.computedPlaceholder() + m.VirtualCursor.SetChar(string(plines[0][0])) + s.WriteString(lineStyle.Render(m.VirtualCursor.View())) // the rest of the first line s.WriteString(lineStyle.Render(style.Render(plines[0][1:] + strings.Repeat(" ", max(0, m.width-uniseg.StringWidth(plines[0])))))) @@ -1322,6 +1322,19 @@ func Blink() tea.Msg { return cursor.Blink() } +// Cursor returns the current cursor position accounting any +// soft-wrapped lines. +func (m Model) Cursor() *tea.Cursor { + lineInfo := m.LineInfo() + x, y := lineInfo.CharOffset, m.cursorLineNumber()-m.viewport.YOffset + + // TODO: sort out where these properties live. + c := tea.NewCursor(x, y) + c.Blink = true + c.Color = m.VirtualCursor.Style.GetForeground() + return c +} + func (m Model) memoizedWrap(runes []rune, width int) [][]rune { input := line{runes: runes, width: width} if v, ok := m.cache.Get(input); ok {