From 6cb0861b618f7412d91238d3596d13267a4213c6 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Thu, 6 Feb 2025 15:54:01 -0300 Subject: [PATCH 1/3] feat: real cursor in textinput Signed-off-by: Carlos Alexandro Becker --- textinput/textinput.go | 135 +++++++++++++++++++++++++++++++++-------- 1 file changed, 111 insertions(+), 24 deletions(-) diff --git a/textinput/textinput.go b/textinput/textinput.go index 907288b2..776944d4 100644 --- a/textinput/textinput.go +++ b/textinput/textinput.go @@ -1,8 +1,10 @@ package textinput import ( + "image/color" "reflect" "strings" + "time" "unicode" "github.com/atotto/clipboard" @@ -92,7 +94,19 @@ type Model struct { Placeholder string EchoMode EchoMode EchoCharacter rune - Cursor cursor.Model + + // VirtualCursor determines whether or not to use the virtual cursor. If + // set to false, use [Model.Cursor] to return a real cursor for rendering. + VirtualCursor bool + cursor cursor.Model + + // ColumnOffset is the number of columns that the cursor is offset from the + // start of the line. + ColumnOffset int + + // RowOffset is the number of rows that the cursor is offset from the start + // of the line. + RowOffset int // Styles. These will be applied as inline styles. // @@ -102,6 +116,7 @@ type Model struct { TextStyle lipgloss.Style PlaceholderStyle lipgloss.Style CompletionStyle lipgloss.Style + CursorStyle CursorStyle // CharLimit is the maximum amount of characters this input element will // accept. If 0 or less, there's no limit. @@ -158,7 +173,7 @@ func New() Model { PlaceholderStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("240")), ShowSuggestions: false, CompletionStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("240")), - Cursor: cursor.New(), + cursor: cursor.New(), KeyMap: DefaultKeyMap(), suggestions: [][]rune{}, @@ -239,14 +254,14 @@ func (m Model) Focused() bool { // receive keyboard input and the cursor will be shown. func (m *Model) Focus() tea.Cmd { m.focus = true - return m.Cursor.Focus() + return m.cursor.Focus() } // Blur removes the focus state on the model. When the model is blurred it can // not receive keyboard input and the cursor will be hidden. func (m *Model) Blur() { m.focus = false - m.Cursor.Blur() + m.cursor.Blur() } // Reset sets the input to its default state with no input. @@ -636,12 +651,14 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { var cmds []tea.Cmd var cmd tea.Cmd - m.Cursor, cmd = m.Cursor.Update(msg) - cmds = append(cmds, cmd) + if m.VirtualCursor { + m.cursor, cmd = m.cursor.Update(msg) + cmds = append(cmds, cmd) - if oldPos != m.pos && m.Cursor.Mode() == cursor.CursorBlink { - m.Cursor.Blink = false - cmds = append(cmds, m.Cursor.BlinkCmd()) + if oldPos != m.pos && m.cursor.Mode() == cursor.CursorBlink { + m.cursor.Blink = false + cmds = append(cmds, m.cursor.BlinkCmd()) + } } m.handleOverflow() @@ -663,25 +680,29 @@ func (m Model) View() string { if pos < len(value) { //nolint:nestif char := m.echoTransform(string(value[pos])) - m.Cursor.SetChar(char) - v += m.Cursor.View() // cursor and text under it + if m.VirtualCursor { + m.cursor.SetChar(char) + v += m.cursor.View() // cursor and text under it + } v += styleText(m.echoTransform(string(value[pos+1:]))) // text after cursor v += m.completionView(0) // suggested completion } else { if m.canAcceptSuggestion() { suggestion := m.matchedSuggestions[m.currentSuggestionIndex] if len(value) < len(suggestion) { - m.Cursor.TextStyle = m.CompletionStyle - m.Cursor.SetChar(m.echoTransform(string(suggestion[pos]))) - v += m.Cursor.View() + if m.VirtualCursor { + m.cursor.TextStyle = m.CompletionStyle + m.cursor.SetChar(m.echoTransform(string(suggestion[pos]))) + v += m.cursor.View() + } v += m.completionView(1) - } else { - m.Cursor.SetChar(" ") - v += m.Cursor.View() + } else if m.VirtualCursor { + m.cursor.SetChar(" ") + v += m.cursor.View() } - } else { - m.Cursor.SetChar(" ") - v += m.Cursor.View() + } else if m.VirtualCursor { + m.cursor.SetChar(" ") + v += m.cursor.View() } } @@ -696,7 +717,11 @@ func (m Model) View() string { v += styleText(strings.Repeat(" ", padding)) } - return m.PromptStyle.Render(m.Prompt) + v + return m.promptView() + v +} + +func (m Model) promptView() string { + return m.PromptStyle.Render(m.Prompt) } // placeholderView returns the prompt and placeholder view, if any. @@ -709,9 +734,11 @@ func (m Model) placeholderView() string { p := make([]rune, m.Width()+1) copy(p, []rune(m.Placeholder)) - m.Cursor.TextStyle = m.PlaceholderStyle - m.Cursor.SetChar(string(p[:1])) - v += m.Cursor.View() + if m.VirtualCursor { + m.cursor.TextStyle = m.PlaceholderStyle + m.cursor.SetChar(string(p[:1])) + v += m.cursor.View() + } // If the entire placeholder is already set and no padding is needed, finish if m.Width() < 1 && len(p) <= 1 { @@ -862,3 +889,63 @@ func (m Model) validate(v []rune) error { } return nil } + +// Cursor returns a [tea.Cursor] for rendering a real cursor in a Bubble Tea +// program. +// +// Example: +// +// // In your top-level View function: +// f := tea.NewFrame(m.textarea.View()) +// f.Cursor = m.textarea.Cursor() +// f.Cursor.Position.X += offsetX +// f.Cursor.Position.Y += offsetY +// +// Note that you will almost certainly also need to adjust the offset +// position of the textarea to properly set the cursor position. +// +// If you're using a real cursor, you should also set [Model.VirtualCursor] to +// false. +func (m Model) Cursor() *tea.Cursor { + w := lipgloss.Width + + xOffset := m.Position() + + w(m.promptView()) + + m.ColumnOffset + + yOffset := m.RowOffset + + c := tea.NewCursor(xOffset, yOffset) + c.Blink = m.CursorStyle.Blink + c.Color = m.CursorStyle.Color + c.Shape = m.CursorStyle.Shape + return c +} + +// CursorStyle is the style for real and virtual cursors. +type CursorStyle struct { + // Style styles the cursor block. + // + // For real cursors, the foreground color set here will be used as the + // cursor color. + Color color.Color + + // Shape is the cursor shape. The following shapes are available: + // + // - tea.CursorBlock + // - tea.CursorUnderline + // - tea.CursorBar + // + // This is only used for real cursors. + Shape tea.CursorShape + + // CursorBlink determines whether or not the cursor should blink. + Blink bool + + // BlinkSpeed is the speed at which the virtual cursor blinks. This has no + // effect on real cursors as well as no effect if the cursor is set not to + // [CursorBlink]. + // + // By default, the blink speed is set to about 500ms. + BlinkSpeed time.Duration +} From 9aba422ab9cf67f381b9d1c0bc4f9a916c04daa4 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Fri, 7 Feb 2025 10:10:16 -0300 Subject: [PATCH 2/3] fix: virtual cursor --- list/list.go | 2 - list/style.go | 19 ++-- textarea/textarea.go | 11 +-- textinput/styles.go | 96 ++++++++++++++++++++ textinput/textinput.go | 194 +++++++++++++++++++---------------------- 5 files changed, 200 insertions(+), 122 deletions(-) create mode 100644 textinput/styles.go diff --git a/list/list.go b/list/list.go index 8f0a5979..951a5334 100644 --- a/list/list.go +++ b/list/list.go @@ -207,8 +207,6 @@ func New(items []Item, delegate ItemDelegate, width, height int) Model { filterInput := textinput.New() filterInput.Prompt = "Filter: " - filterInput.PromptStyle = styles.FilterPrompt - filterInput.Cursor.Style = styles.FilterCursor filterInput.CharLimit = 64 filterInput.Focus() diff --git a/list/style.go b/list/style.go index 60b1c889..be7a329d 100644 --- a/list/style.go +++ b/list/style.go @@ -1,6 +1,7 @@ package list import ( + "github.com/charmbracelet/bubbles/v2/textinput" "github.com/charmbracelet/lipgloss/v2" ) @@ -12,11 +13,10 @@ const ( // Styles contains style definitions for this list component. By default, these // values are generated by DefaultStyles. type Styles struct { - TitleBar lipgloss.Style - Title lipgloss.Style - Spinner lipgloss.Style - FilterPrompt lipgloss.Style - FilterCursor lipgloss.Style + TitleBar lipgloss.Style + Title lipgloss.Style + Spinner lipgloss.Style + Filter textinput.Styles // Default styling for matched characters in a filter. This can be // overridden by delegates. @@ -57,11 +57,12 @@ func DefaultStyles(isDark bool) (s Styles) { s.Spinner = lipgloss.NewStyle(). Foreground(lightDark(lipgloss.Color("#8E8E8E"), lipgloss.Color("#747373"))) - s.FilterPrompt = lipgloss.NewStyle(). + prompt := lipgloss.NewStyle(). Foreground(lightDark(lipgloss.Color("#04B575"), lipgloss.Color("#ECFD65"))) - - s.FilterCursor = lipgloss.NewStyle(). - Foreground(lightDark(lipgloss.Color("#EE6FF8"), lipgloss.Color("#EE6FF8"))) + s.Filter = textinput.DefaultStyles(isDark) + s.Filter.Cursor.Color = lightDark(lipgloss.Color("#EE6FF8"), lipgloss.Color("#EE6FF8")) + s.Filter.Blurred.Prompt = prompt + s.Filter.Focused.Prompt = prompt s.DefaultFilterCharacterMatch = lipgloss.NewStyle().Underline(true) diff --git a/textarea/textarea.go b/textarea/textarea.go index b054dee2..7b4f298a 100644 --- a/textarea/textarea.go +++ b/textarea/textarea.go @@ -413,15 +413,10 @@ func (m *Model) updateVirtualCursorStyle() { // By default, the blink speed of the cursor is set to a default // internally. - if m.Styles.Cursor.BlinkSpeed > 0 { - m.virtualCursor.BlinkSpeed = m.Styles.Cursor.BlinkSpeed - } - - if !m.VirtualCursor { - m.virtualCursor.SetMode(cursor.CursorHide) - return - } if m.Styles.Cursor.Blink { + if m.Styles.Cursor.BlinkSpeed > 0 { + m.virtualCursor.BlinkSpeed = m.Styles.Cursor.BlinkSpeed + } m.virtualCursor.SetMode(cursor.CursorBlink) return } diff --git a/textinput/styles.go b/textinput/styles.go new file mode 100644 index 00000000..736217d4 --- /dev/null +++ b/textinput/styles.go @@ -0,0 +1,96 @@ +package textinput + +import ( + "image/color" + "time" + + tea "github.com/charmbracelet/bubbletea/v2" + "github.com/charmbracelet/lipgloss/v2" +) + +// DefaultStyles returns the default styles for focused and blurred states for +// the textarea. +func DefaultStyles(isDark bool) Styles { + lightDark := lipgloss.LightDark(isDark) + + var s Styles + s.Focused = StyleState{ + Placeholder: lipgloss.NewStyle().Foreground(lipgloss.Color("240")), + Suggestion: lipgloss.NewStyle().Foreground(lipgloss.Color("240")), + Prompt: lipgloss.NewStyle().Foreground(lipgloss.Color("7")), + Text: lipgloss.NewStyle(), + } + s.Blurred = StyleState{ + Placeholder: lipgloss.NewStyle().Foreground(lipgloss.Color("240")), + Suggestion: lipgloss.NewStyle().Foreground(lipgloss.Color("240")), + Prompt: lipgloss.NewStyle().Foreground(lipgloss.Color("7")), + Text: lipgloss.NewStyle().Foreground(lightDark(lipgloss.Color("245"), lipgloss.Color("7"))), + } + s.Cursor = CursorStyle{ + Color: lipgloss.Color("7"), + Shape: tea.CursorBlock, + Blink: true, + } + return s +} + +// DefaultLightStyles returns the default styles for a light background. +func DefaultLightStyles() Styles { + return DefaultStyles(false) +} + +// DefaultDarkStyles returns the default styles for a dark background. +func DefaultDarkStyles() Styles { + return DefaultStyles(true) +} + +// Styles are the styles for the textarea, separated into focused and blurred +// states. The appropriate styles will be chosen based on the focus state of +// the textarea. +type Styles struct { + Focused StyleState + Blurred StyleState + Cursor CursorStyle +} + +// StyleState that will be applied to the text area. +// +// StyleState can be applied to focused and unfocused states to change the styles +// depending on the focus state. +// +// For an introduction to styling with Lip Gloss see: +// https://github.com/charmbracelet/lipgloss +type StyleState struct { + Text lipgloss.Style + Placeholder lipgloss.Style + Suggestion lipgloss.Style + Prompt lipgloss.Style +} + +// CursorStyle is the style for real and virtual cursors. +type CursorStyle struct { + // Style styles the cursor block. + // + // For real cursors, the foreground color set here will be used as the + // cursor color. + Color color.Color + + // Shape is the cursor shape. The following shapes are available: + // + // - tea.CursorBlock + // - tea.CursorUnderline + // - tea.CursorBar + // + // This is only used for real cursors. + Shape tea.CursorShape + + // CursorBlink determines whether or not the cursor should blink. + Blink bool + + // BlinkSpeed is the speed at which the virtual cursor blinks. This has no + // effect on real cursors as well as no effect if the cursor is set not to + // [CursorBlink]. + // + // By default, the blink speed is set to about 500ms. + BlinkSpeed time.Duration +} diff --git a/textinput/textinput.go b/textinput/textinput.go index 776944d4..50627ac7 100644 --- a/textinput/textinput.go +++ b/textinput/textinput.go @@ -1,10 +1,8 @@ package textinput import ( - "image/color" "reflect" "strings" - "time" "unicode" "github.com/atotto/clipboard" @@ -98,25 +96,19 @@ type Model struct { // VirtualCursor determines whether or not to use the virtual cursor. If // set to false, use [Model.Cursor] to return a real cursor for rendering. VirtualCursor bool - cursor cursor.Model + virtualCursor cursor.Model // ColumnOffset is the number of columns that the cursor is offset from the // start of the line. ColumnOffset int // RowOffset is the number of rows that the cursor is offset from the start - // of the line. + // of the screen. RowOffset int - // Styles. These will be applied as inline styles. - // - // For an introduction to styling with Lip Gloss see: - // https://github.com/charmbracelet/lipgloss - PromptStyle lipgloss.Style - TextStyle lipgloss.Style - PlaceholderStyle lipgloss.Style - CompletionStyle lipgloss.Style - CursorStyle CursorStyle + // Styling. FocusedStyle and BlurredStyle are used to style the textarea in + // focused and blurred states. + Styles Styles // CharLimit is the maximum amount of characters this input element will // accept. If 0 or less, there's no limit. @@ -167,19 +159,17 @@ type Model struct { // New creates a new model with default settings. func New() Model { return Model{ - Prompt: "> ", - EchoCharacter: '*', - CharLimit: 0, - PlaceholderStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("240")), - ShowSuggestions: false, - CompletionStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("240")), - cursor: cursor.New(), - KeyMap: DefaultKeyMap(), - - suggestions: [][]rune{}, - value: nil, - focus: false, - pos: 0, + Prompt: "> ", + EchoCharacter: '*', + CharLimit: 0, + Styles: DefaultDarkStyles(), + ShowSuggestions: false, + virtualCursor: cursor.New(), + KeyMap: DefaultKeyMap(), + suggestions: [][]rune{}, + value: nil, + focus: false, + pos: 0, } } @@ -254,14 +244,14 @@ func (m Model) Focused() bool { // receive keyboard input and the cursor will be shown. func (m *Model) Focus() tea.Cmd { m.focus = true - return m.cursor.Focus() + return m.virtualCursor.Focus() } // Blur removes the focus state on the model. When the model is blurred it can // not receive keyboard input and the cursor will be hidden. func (m *Model) Blur() { m.focus = false - m.cursor.Blur() + m.virtualCursor.Blur() } // Reset sets the input to its default state with no input. @@ -565,6 +555,7 @@ func (m Model) echoTransform(v string) string { // Update is the Bubble Tea update loop. func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { + m.updateVirtualCursorStyle() if !m.focus { return m, nil } @@ -651,14 +642,12 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { var cmds []tea.Cmd var cmd tea.Cmd - if m.VirtualCursor { - m.cursor, cmd = m.cursor.Update(msg) - cmds = append(cmds, cmd) + m.virtualCursor, cmd = m.virtualCursor.Update(msg) + cmds = append(cmds, cmd) - if oldPos != m.pos && m.cursor.Mode() == cursor.CursorBlink { - m.cursor.Blink = false - cmds = append(cmds, m.cursor.BlinkCmd()) - } + if oldPos != m.pos && m.virtualCursor.Mode() == cursor.CursorBlink { + m.virtualCursor.Blink = false + cmds = append(cmds, m.virtualCursor.BlinkCmd()) } m.handleOverflow() @@ -672,7 +661,9 @@ func (m Model) View() string { return m.placeholderView() } - styleText := m.TextStyle.Inline(true).Render + styles := m.activeStyle() + + styleText := styles.Text.Inline(true).Render value := m.value[m.offset:m.offsetRight] pos := max(0, m.pos-m.offset) @@ -680,29 +671,25 @@ func (m Model) View() string { if pos < len(value) { //nolint:nestif char := m.echoTransform(string(value[pos])) - if m.VirtualCursor { - m.cursor.SetChar(char) - v += m.cursor.View() // cursor and text under it - } + m.virtualCursor.SetChar(char) + v += m.virtualCursor.View() // cursor and text under it v += styleText(m.echoTransform(string(value[pos+1:]))) // text after cursor v += m.completionView(0) // suggested completion } else { if m.canAcceptSuggestion() { suggestion := m.matchedSuggestions[m.currentSuggestionIndex] if len(value) < len(suggestion) { - if m.VirtualCursor { - m.cursor.TextStyle = m.CompletionStyle - m.cursor.SetChar(m.echoTransform(string(suggestion[pos]))) - v += m.cursor.View() - } + m.virtualCursor.TextStyle = styles.Suggestion + m.virtualCursor.SetChar(m.echoTransform(string(suggestion[pos]))) + v += m.virtualCursor.View() v += m.completionView(1) - } else if m.VirtualCursor { - m.cursor.SetChar(" ") - v += m.cursor.View() + } else { + m.virtualCursor.SetChar(" ") + v += m.virtualCursor.View() } - } else if m.VirtualCursor { - m.cursor.SetChar(" ") - v += m.cursor.View() + } else { + m.virtualCursor.SetChar(" ") + v += m.virtualCursor.View() } } @@ -721,28 +708,27 @@ func (m Model) View() string { } func (m Model) promptView() string { - return m.PromptStyle.Render(m.Prompt) + return m.activeStyle().Prompt.Render(m.Prompt) } // placeholderView returns the prompt and placeholder view, if any. func (m Model) placeholderView() string { var ( - v string - style = m.PlaceholderStyle.Inline(true).Render + v string + styles = m.activeStyle() + render = styles.Placeholder.Render ) p := make([]rune, m.Width()+1) copy(p, []rune(m.Placeholder)) - if m.VirtualCursor { - m.cursor.TextStyle = m.PlaceholderStyle - m.cursor.SetChar(string(p[:1])) - v += m.cursor.View() - } + m.virtualCursor.TextStyle = styles.Placeholder + m.virtualCursor.SetChar(string(p[:1])) + v += m.virtualCursor.View() // If the entire placeholder is already set and no padding is needed, finish if m.Width() < 1 && len(p) <= 1 { - return m.PromptStyle.Render(m.Prompt) + v + return styles.Prompt.Render(m.Prompt) + v } // If Width is set then size placeholder accordingly @@ -757,14 +743,14 @@ func (m Model) placeholderView() string { availWidth = 0 } // append placeholder[len] - cursor, append padding - v += style(string(p[1:minWidth])) - v += style(strings.Repeat(" ", availWidth)) + v += render(string(p[1:minWidth])) + v += render(strings.Repeat(" ", availWidth)) } else { // if there is no width, the placeholder can be any length - v += style(string(p[1:])) + v += render(string(p[1:])) } - return m.PromptStyle.Render(m.Prompt) + v + return styles.Prompt.Render(m.Prompt) + v } // Blink is a command used to initialize cursor blinking. @@ -789,16 +775,14 @@ func clamp(v, low, high int) int { } func (m Model) completionView(offset int) string { - var ( - value = m.value - style = m.PlaceholderStyle.Inline(true).Render - ) - - if m.canAcceptSuggestion() { - suggestion := m.matchedSuggestions[m.currentSuggestionIndex] - if len(value) < len(suggestion) { - return style(string(suggestion[len(value)+offset:])) - } + if !m.canAcceptSuggestion() { + return "" + } + value := m.value + suggestion := m.matchedSuggestions[m.currentSuggestionIndex] + if len(value) < len(suggestion) { + return m.activeStyle().Suggestion.Inline(true). + Render(string(suggestion[len(value)+offset:])) } return "" } @@ -915,37 +899,41 @@ func (m Model) Cursor() *tea.Cursor { yOffset := m.RowOffset + style := m.Styles.Cursor c := tea.NewCursor(xOffset, yOffset) - c.Blink = m.CursorStyle.Blink - c.Color = m.CursorStyle.Color - c.Shape = m.CursorStyle.Shape + c.Blink = style.Blink + c.Color = style.Color + c.Shape = style.Shape return c } -// CursorStyle is the style for real and virtual cursors. -type CursorStyle struct { - // Style styles the cursor block. - // - // For real cursors, the foreground color set here will be used as the - // cursor color. - Color color.Color - - // Shape is the cursor shape. The following shapes are available: - // - // - tea.CursorBlock - // - tea.CursorUnderline - // - tea.CursorBar - // - // This is only used for real cursors. - Shape tea.CursorShape - - // CursorBlink determines whether or not the cursor should blink. - Blink bool - - // BlinkSpeed is the speed at which the virtual cursor blinks. This has no - // effect on real cursors as well as no effect if the cursor is set not to - // [CursorBlink]. - // - // By default, the blink speed is set to about 500ms. - BlinkSpeed time.Duration +// updateVirtualCursorStyle sets styling on the virtual cursor based on the +// textarea's style settings. +func (m *Model) updateVirtualCursorStyle() { + if !m.VirtualCursor { + m.virtualCursor.SetMode(cursor.CursorHide) + return + } + + m.virtualCursor.Style = lipgloss.NewStyle().Foreground(m.Styles.Cursor.Color) + + // By default, the blink speed of the cursor is set to a default + // internally. + if m.Styles.Cursor.Blink { + if m.Styles.Cursor.BlinkSpeed > 0 { + m.virtualCursor.BlinkSpeed = m.Styles.Cursor.BlinkSpeed + } + m.virtualCursor.SetMode(cursor.CursorBlink) + return + } + m.virtualCursor.SetMode(cursor.CursorStatic) +} + +// activeStyle returns the appropriate set of styles to use depending on +// whether the textarea is focused or blurred. +func (m Model) activeStyle() *StyleState { + if m.focus { + return &m.Styles.Focused + } + return &m.Styles.Blurred } From 9daf6840c519f6fcb8993abf968335a0294bef25 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Fri, 7 Feb 2025 10:11:52 -0300 Subject: [PATCH 3/3] fix: offsets --- textinput/textinput.go | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/textinput/textinput.go b/textinput/textinput.go index 50627ac7..aa13acbb 100644 --- a/textinput/textinput.go +++ b/textinput/textinput.go @@ -98,14 +98,6 @@ type Model struct { VirtualCursor bool virtualCursor cursor.Model - // ColumnOffset is the number of columns that the cursor is offset from the - // start of the line. - ColumnOffset int - - // RowOffset is the number of rows that the cursor is offset from the start - // of the screen. - RowOffset int - // Styling. FocusedStyle and BlurredStyle are used to style the textarea in // focused and blurred states. Styles Styles @@ -894,13 +886,10 @@ func (m Model) Cursor() *tea.Cursor { w := lipgloss.Width xOffset := m.Position() + - w(m.promptView()) + - m.ColumnOffset - - yOffset := m.RowOffset + w(m.promptView()) style := m.Styles.Cursor - c := tea.NewCursor(xOffset, yOffset) + c := tea.NewCursor(xOffset, 0) c.Blink = style.Blink c.Color = style.Color c.Shape = style.Shape