diff --git a/cursor.go b/cursor.go index 17a580f327..cfb2bc1a5f 100644 --- a/cursor.go +++ b/cursor.go @@ -8,3 +8,39 @@ type CursorPositionMsg struct { // Column is the column number. Column int } + +// CursorStyle is a style that represents the terminal cursor. +type CursorStyle int + +// Cursor styles. +const ( + CursorBlock CursorStyle = iota + CursorUnderline + CursorBar +) + +// setCursorStyle is an internal message that sets the cursor style. This matches the +// ANSI escape sequence values for cursor styles. This includes: +// +// 0: Blinking block +// 1: Blinking block (default) +// 2: Steady block +// 3: Blinking underline +// 4: Steady underline +// 5: Blinking bar (xterm) +// 6: Steady bar (xterm) +type setCursorStyle int + +// SetCursorStyle is a command that sets the terminal cursor style. Steady +// determines if the cursor should blink or not. +func SetCursorStyle(style CursorStyle, steady bool) Cmd { + // We're using the ANSI escape sequence values for cursor styles. + // We need to map both [style] and [steady] to the correct value. + style = (style * 2) + 1 + if steady { + style++ + } + return func() Msg { + return setCursorStyle(style) + } +} diff --git a/examples/cursor-style/main.go b/examples/cursor-style/main.go new file mode 100644 index 0000000000..1a5dd99234 --- /dev/null +++ b/examples/cursor-style/main.go @@ -0,0 +1,61 @@ +package main + +import ( + "fmt" + "os" + + tea "github.com/charmbracelet/bubbletea/v2" +) + +type model struct { + style tea.CursorStyle + steady bool +} + +func (m model) Init() (tea.Model, tea.Cmd) { + return m, tea.ShowCursor +} + +func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmd tea.Cmd + switch msg := msg.(type) { + case tea.KeyPressMsg: + switch msg.String() { + case "ctrl+q", "q": + return m, tea.Quit + case "left": + if m.style == tea.CursorBlock && !m.steady { + break + } + if !m.steady { + m.style-- + } + m.steady = !m.steady + cmd = tea.SetCursorStyle(m.style, m.steady) + case "right": + if m.style == tea.CursorBar && m.steady { + break + } + if m.steady { + m.style++ + } + m.steady = !m.steady + cmd = tea.SetCursorStyle(m.style, m.steady) + } + } + return m, cmd +} + +func (m model) View() string { + return "Press left/right to change the cursor style, q or ctrl+c to quit." + + "\n\n" + + " <- This is a cursor" +} + +func main() { + p := tea.NewProgram(model{}) + if _, err := p.Run(); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v", err) + os.Exit(1) + } +} diff --git a/tea.go b/tea.go index c0ff16b135..22a07bc8db 100644 --- a/tea.go +++ b/tea.go @@ -429,6 +429,9 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) { p.suspend() } + case setCursorStyle: + p.execute(ansi.SetCursorStyle(int(msg))) + case modeReportMsg: switch msg.Mode { case graphemeClustering: