From eda2c095456821c82360e4c52b50f96a79461676 Mon Sep 17 00:00:00 2001 From: Kujtim Hoxha Date: Thu, 11 Dec 2025 21:41:24 +0100 Subject: [PATCH] chore: add cursor support --- internal/terminal/terminal.go | 35 ++++++++++++++----- .../tui/components/dialogs/lazygit/lazygit.go | 22 ++++++------ .../dialogs/termdialog/termdialog.go | 21 +++++++++++ 3 files changed, 59 insertions(+), 19 deletions(-) diff --git a/internal/terminal/terminal.go b/internal/terminal/terminal.go index 86758e72f093978b16b739b6ac9b1dcfcc4d5135..de7b97aa54d9404ad905f9e67b690eefaa7400d8 100644 --- a/internal/terminal/terminal.go +++ b/internal/terminal/terminal.go @@ -53,10 +53,11 @@ type Terminal struct { vterm *vt.Emulator cmd *exec.Cmd - width int - height int - mouseMode uv.MouseMode - refreshRate time.Duration + width int + height int + mouseMode uv.MouseMode + cursorVisible bool + refreshRate time.Duration started bool closed bool @@ -84,9 +85,10 @@ func New(cfg Config) *Terminal { } return &Terminal{ - ctx: ctx, - cmd: cmd, - refreshRate: refreshRate, + ctx: ctx, + cmd: cmd, + refreshRate: refreshRate, + cursorVisible: true, // Cursor is visible by default } } @@ -150,7 +152,7 @@ func (t *Terminal) Start() error { return nil } -// setupCallbacks configures vterm callbacks to track mouse mode. +// setupCallbacks configures vterm callbacks to track mouse mode and cursor visibility. func (t *Terminal) setupCallbacks() { t.vterm.SetCallbacks(vt.Callbacks{ EnableMode: func(mode ansi.Mode) { @@ -169,6 +171,9 @@ func (t *Terminal) setupCallbacks() { t.mouseMode = uv.MouseModeNone } }, + CursorVisibility: func(visible bool) { + t.cursorVisible = visible + }, }) } @@ -265,6 +270,20 @@ func (t *Terminal) Render() string { return t.vterm.Render() } +// CursorPosition returns the current cursor position in the terminal. +// Returns (-1, -1) if the terminal is not started, closed, or cursor is hidden. +func (t *Terminal) CursorPosition() (x, y int) { + t.mu.RLock() + defer t.mu.RUnlock() + + if t.vterm == nil || !t.started || t.closed || !t.cursorVisible { + return -1, -1 + } + + pos := t.vterm.CursorPosition() + return pos.X, pos.Y +} + // Started returns whether the terminal has been started. func (t *Terminal) Started() bool { t.mu.RLock() diff --git a/internal/tui/components/dialogs/lazygit/lazygit.go b/internal/tui/components/dialogs/lazygit/lazygit.go index 635dae6a57efcfdfe238612a087c4a48cf7c8efe..ff2447a6bc523a8044fd002a94cc2240c4c6a823 100644 --- a/internal/tui/components/dialogs/lazygit/lazygit.go +++ b/internal/tui/components/dialogs/lazygit/lazygit.go @@ -93,17 +93,17 @@ func createThemedConfig() string { defaultFgColor: - default `, - colorToHex(t.BorderFocus), // Active border: purple (Charple) - colorToHex(t.Border), // Inactive border: gray (Charcoal) - colorToHex(t.Info), // Search border: blue (Malibu) - calmer than warning - colorToHex(t.FgMuted), // Options text: muted gray (Squid) - matches help text - colorToHex(t.Primary), // Selected line bg: purple (Charple) - matches TextSelected - colorToHex(t.BgSubtle), // Inactive selected: subtle gray (Charcoal) - colorToHex(t.Success), // Cherry-picked fg: green (Guac) - positive action - colorToHex(t.BgSubtle), // Cherry-picked bg: subtle (Charcoal) - colorToHex(t.Info), // Marked base fg: blue (Malibu) - distinct from cherry - colorToHex(t.BgSubtle), // Marked base bg: subtle (Charcoal) - colorToHex(t.Error), // Unstaged changes: red (Sriracha) + colorToHex(t.BorderFocus), // Active border: purple (Charple) + colorToHex(t.Border), // Inactive border: gray (Charcoal) + colorToHex(t.Info), // Search border: blue (Malibu) - calmer than warning + colorToHex(t.FgMuted), // Options text: muted gray (Squid) - matches help text + colorToHex(t.Primary), // Selected line bg: purple (Charple) - matches TextSelected + colorToHex(t.BgSubtle), // Inactive selected: subtle gray (Charcoal) + colorToHex(t.Success), // Cherry-picked fg: green (Guac) - positive action + colorToHex(t.BgSubtle), // Cherry-picked bg: subtle (Charcoal) + colorToHex(t.Info), // Marked base fg: blue (Malibu) - distinct from cherry + colorToHex(t.BgSubtle), // Marked base bg: subtle (Charcoal) + colorToHex(t.Error), // Unstaged changes: red (Sriracha) ) f, err := os.CreateTemp("", "crush-lazygit-*.yml") diff --git a/internal/tui/components/dialogs/termdialog/termdialog.go b/internal/tui/components/dialogs/termdialog/termdialog.go index 1949cc34d430561b2a6ac9843845f861661d37e5..b3c85d7f6360436a2e3820e42a8f2e54314b9c40 100644 --- a/internal/tui/components/dialogs/termdialog/termdialog.go +++ b/internal/tui/components/dialogs/termdialog/termdialog.go @@ -221,6 +221,27 @@ func (d *Dialog) ID() dialogs.DialogID { return d.id } +// Cursor returns the cursor position adjusted for the dialog's screen position. +// Returns nil if the terminal cursor is hidden or not available. +func (d *Dialog) Cursor() *tea.Cursor { + x, y := d.term.CursorPosition() + if x < 0 || y < 0 { + return nil + } + + t := styles.CurrentTheme() + row, col := d.Position() + cursor := tea.NewCursor(x, y) + // Adjust for dialog position: border (1) + header height + cursor.X += col + 1 + cursor.Y += row + 1 + headerHeight + // Match the app's cursor style + cursor.Color = t.Secondary + cursor.Shape = tea.CursorBlock + cursor.Blink = true + return cursor +} + func (d *Dialog) Close() tea.Cmd { _ = d.term.Close()