@@ -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()
@@ -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")
@@ -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()