From c3433b7e8b80948961b321b7648ccad02a7d3c83 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 17 Oct 2024 16:47:15 -0400 Subject: [PATCH] feat: cellrenderer: support setting cursor position --- cell_renderer.go | 112 +++++++++++++---------------------------------- cursor.go | 14 ++++++ go.mod | 2 +- go.sum | 4 +- 4 files changed, 48 insertions(+), 84 deletions(-) diff --git a/cell_renderer.go b/cell_renderer.go index 84c43bcf48..80fc45bee6 100644 --- a/cell_renderer.go +++ b/cell_renderer.go @@ -11,59 +11,7 @@ import ( "github.com/charmbracelet/x/cellbuf" ) -// moveCursorMsg represents a message to move the cursor. -type moveCursorMsg struct { - x, y int -} - -// MoveCursor moves the cursor to the given position. -func MoveCursor(x, y int) Msg { - return moveCursorMsg{x, y} -} - -// cursorUpMsg represents a message to move the cursor up. -type cursorUpMsg int - -// CursorUp moves the cursor up by n cells. -func CursorUp(n int) Msg { - if n <= 0 { - return nil - } - return cursorUpMsg(n) -} - -// cursorDownMsg represents a message to move the cursor down. -type cursorDownMsg int - -// CursorDown moves the cursor down by n cells. -func CursorDown(n int) Msg { - if n <= 0 { - return nil - } - return cursorDownMsg(n) -} - -// cursorLeftMsg represents a message to move the cursor left. -type cursorLeftMsg int - -// CursorLeft moves the cursor left by n cells. -func CursorLeft(n int) Msg { - if n <= 0 { - return nil - } - return cursorLeftMsg(n) -} - -// cursorRightMsg represents a message to move the cursor right. -type cursorRightMsg int - -// CursorRight moves the cursor right by n cells. -func CursorRight(n int) Msg { - if n <= 0 { - return nil - } - return cursorRightMsg(n) -} +var undefPoint = image.Pt(-1, -1) // cursor represents a terminal cursor. type cursor struct { @@ -121,6 +69,8 @@ type cellRenderer struct { scrs [2]screen // Both inline and alt-screen scr *screen // Points to the current used screen + finalCur image.Point // The final cursor position + method cellbuf.WidthMethod pen cellbuf.Style link cellbuf.Link @@ -139,7 +89,8 @@ type cellRenderer struct { func newCellRenderer() *cellRenderer { r := &cellRenderer{ // TODO: Update this if Grapheme Clustering is supported. - method: cellbuf.WcWidth, + method: cellbuf.WcWidth, + finalCur: undefPoint, } r.reset() return r @@ -199,7 +150,8 @@ func (c *cellRenderer) flush() error { c.mtx.Lock() defer c.mtx.Unlock() - if c.frame == *c.lastRender && c.lastHeight == c.scr.Height() { + if c.finalCur == c.scr.cur.Point && len(c.queueAbove) == 0 && + c.frame == *c.lastRender && c.lastHeight == c.scr.Height() { return nil } @@ -228,6 +180,17 @@ func (c *cellRenderer) flush() error { c.changes() + // XXX: We need to move the cursor to the final position before rendering + // the frame to avoid flickering. + shouldHideCursor := !c.cursorHidden + if c.finalCur != image.Pt(-1, -1) { + shouldMove := c.finalCur != c.scr.cur.Point + shouldHideCursor = shouldHideCursor && shouldMove + if shouldMove { + c.moveCursor(c.finalCur.X, c.finalCur.Y) + } + } + c.scr.dirty = make(map[int]int) c.lastHeight = cellbuf.Height(c.frame) *c.lastRender = c.frame @@ -237,7 +200,7 @@ func (c *cellRenderer) flush() error { return nil } - if !c.cursorHidden { + if shouldHideCursor { // Hide the cursor while rendering to avoid flickering. render = ansi.HideCursor + render + ansi.ShowCursor } @@ -270,7 +233,7 @@ func (c *cellRenderer) reset() { // alt-screen buffer cursor always starts from where the main buffer cursor // is. We need to set it to (-1,-1) to force the cursor to be moved to the // origin on the first render. - c.scrs[1].cur.Point = image.Pt(-1, -1) + c.scrs[1].cur.Point = undefPoint if c.altScreen { c.scr = &c.scrs[1] c.lastRender = &c.lastRenders[1] @@ -377,32 +340,19 @@ func (c *cellRenderer) update(msg Msg) { c.cursorHidden = true } - case moveCursorMsg: - c.moveCursor(msg.x, msg.y) - - case cursorUpMsg: - y := c.scr.cur.Y - int(msg) - if y >= 0 { - c.scr.cur.Y = y - } - - case cursorDownMsg: - y := c.scr.cur.Y + int(msg) - if y < c.scr.Height() { - c.scr.cur.Y = y - } - - case cursorLeftMsg: - x := c.scr.cur.X - int(msg) - if x >= 0 { - c.scr.cur.X = x + case setCursorPosMsg: + x, y := msg.x, msg.y + if x < 0 { + x = c.scr.cur.X + } else if x >= c.scr.Width() { + x = c.scr.Width() - 1 } - - case cursorRightMsg: - x := c.scr.cur.X + int(msg) - if x < c.scr.Width() { - c.scr.cur.X = x + if y < 0 { + y = c.scr.cur.Y + } else if y >= c.scr.Height() { + y = c.scr.Height() - 1 } + c.finalCur = image.Pt(x, y) } } diff --git a/cursor.go b/cursor.go index cfb2bc1a5f..463ef5016e 100644 --- a/cursor.go +++ b/cursor.go @@ -44,3 +44,17 @@ func SetCursorStyle(style CursorStyle, steady bool) Cmd { return setCursorStyle(style) } } + +// setCursorPosMsg represents a message to set the cursor position. +type setCursorPosMsg struct { + x, y int +} + +// SetCursorPosition sets the cursor position to the specified relative +// coordinates. Using -1 for either x or y will not change the cursor position +// for that axis. +func SetCursorPosition(x, y int) Cmd { + return func() Msg { + return setCursorPosMsg{x, y} + } +} diff --git a/go.mod b/go.mod index 9bce9afb30..9cd357b7d6 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.18 require ( github.com/charmbracelet/x/ansi v0.3.2 - github.com/charmbracelet/x/cellbuf v0.0.0-20241015153255-110a4e49aee6 + github.com/charmbracelet/x/cellbuf v0.0.0-20241017161146-26fb54ba205f github.com/charmbracelet/x/term v0.2.0 github.com/charmbracelet/x/windows v0.2.0 github.com/muesli/cancelreader v0.2.2 diff --git a/go.sum b/go.sum index 3e9a47f790..f1de334c01 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/charmbracelet/x/ansi v0.3.2 h1:wsEwgAN+C9U06l9dCVMX0/L3x7ptvY1qmjMwyfE6USY= github.com/charmbracelet/x/ansi v0.3.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= -github.com/charmbracelet/x/cellbuf v0.0.0-20241015153255-110a4e49aee6 h1:z6Jq8MwJxOqzNSLEyAfRrHvUGFU7SgTFgZrqFHAJIu4= -github.com/charmbracelet/x/cellbuf v0.0.0-20241015153255-110a4e49aee6/go.mod h1:mFpvlGowTd0Fiv4TdoKyGZaZdigSUHtBJralbADonwE= +github.com/charmbracelet/x/cellbuf v0.0.0-20241017161146-26fb54ba205f h1:Am9Pfoi7x0XbXJiuQVc4MHDFb3oZeFHyN7JmgdHt0GM= +github.com/charmbracelet/x/cellbuf v0.0.0-20241017161146-26fb54ba205f/go.mod h1:mFpvlGowTd0Fiv4TdoKyGZaZdigSUHtBJralbADonwE= github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0= github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0= github.com/charmbracelet/x/wcwidth v0.0.0-20241011142426-46044092ad91 h1:D5OO0lVavz7A+Swdhp62F9gbkibxmz9B2hZ/jVdMPf0=