mirror of
https://github.com/zoriya/bubbles.git
synced 2026-08-16 01:43:21 +00:00
feat(textarea): add Cursor method (#712)
* feat(textarea): add CursorPosition method This returns the current cursor position accounting any soft-wrapped lines and multi-rune characters. * chore(textarea): redefine how real cursor properties are accessed (#714) --------- Co-authored-by: Christian Rocha <christian@rocha.is>
This commit is contained in:
co-authored by
Christian Rocha
parent
0c83e6f4c8
commit
518ff7d0d0
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
+31
-18
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user