From 8d37f48744050b1b8c926e07f2fff93af4f210e3 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sat, 24 Aug 2024 17:45:51 +0200 Subject: [PATCH 1/2] Bump gocui --- go.mod | 2 +- go.sum | 4 +- vendor/github.com/jesseduffield/gocui/gui.go | 17 +++- .../jesseduffield/gocui/tcell_driver.go | 7 +- vendor/github.com/jesseduffield/gocui/view.go | 97 ++++++++++++++++++- vendor/modules.txt | 2 +- 6 files changed, 119 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index a93d04e3eb7..36350778cde 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/integrii/flaggy v1.4.0 github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d - github.com/jesseduffield/gocui v0.3.1-0.20240824094505-8cce5f5d2511 + github.com/jesseduffield/gocui v0.3.1-0.20240824154427-0fc91d5098e4 github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e diff --git a/go.sum b/go.sum index 4efe0c3b158..9793cd69ba9 100644 --- a/go.sum +++ b/go.sum @@ -188,8 +188,8 @@ github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 h1:EQP2Tv8T github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk= github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d h1:bO+OmbreIv91rCe8NmscRwhFSqkDJtzWCPV4Y+SQuXE= github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o= -github.com/jesseduffield/gocui v0.3.1-0.20240824094505-8cce5f5d2511 h1:FN3QrzVxV3lM/SdvBCz2lUtfW0VOKLUMHj5xYWdv3Mc= -github.com/jesseduffield/gocui v0.3.1-0.20240824094505-8cce5f5d2511/go.mod h1:XtEbqCbn45keRXEu+OMZkjN5gw6AEob59afsgHjokZ8= +github.com/jesseduffield/gocui v0.3.1-0.20240824154427-0fc91d5098e4 h1:2su9wjacqT/WxvNrzzdvA6rBJa6n/yZ/jvaS1r60HfM= +github.com/jesseduffield/gocui v0.3.1-0.20240824154427-0fc91d5098e4/go.mod h1:XtEbqCbn45keRXEu+OMZkjN5gw6AEob59afsgHjokZ8= github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0= github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo= github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY= diff --git a/vendor/github.com/jesseduffield/gocui/gui.go b/vendor/github.com/jesseduffield/gocui/gui.go index 9d848d93ded..c239d25a519 100644 --- a/vendor/github.com/jesseduffield/gocui/gui.go +++ b/vendor/github.com/jesseduffield/gocui/gui.go @@ -180,6 +180,8 @@ type Gui struct { suspended bool taskManager *TaskManager + + lastHoverView *View } type NewGuiOpts struct { @@ -836,7 +838,7 @@ func (g *Gui) processRemainingEvents() error { // etc.) func (g *Gui) handleEvent(ev *GocuiEvent) error { switch ev.Type { - case eventKey, eventMouse: + case eventKey, eventMouse, eventMouseMove: return g.onKey(ev) case eventError: return ev.Err @@ -1395,6 +1397,19 @@ func (g *Gui) onKey(ev *GocuiEvent) error { return err } + case eventMouseMove: + mx, my := ev.MouseX, ev.MouseY + v, err := g.VisibleViewByPosition(mx, my) + if err != nil { + break + } + if g.lastHoverView != nil && g.lastHoverView != v { + g.lastHoverView.lastHoverPosition = nil + g.lastHoverView.hoveredHyperlink = nil + } + g.lastHoverView = v + v.onMouseMove(mx, my) + default: } diff --git a/vendor/github.com/jesseduffield/gocui/tcell_driver.go b/vendor/github.com/jesseduffield/gocui/tcell_driver.go index 6665432c56a..96e816b2f95 100644 --- a/vendor/github.com/jesseduffield/gocui/tcell_driver.go +++ b/vendor/github.com/jesseduffield/gocui/tcell_driver.go @@ -176,6 +176,7 @@ const ( eventKey eventResize eventMouse + eventMouseMove // only used when no button is down, otherwise it's eventMouse eventFocus eventInterrupt eventError @@ -387,7 +388,11 @@ func (g *Gui) pollEvent() GocuiEvent { if !wheeling { switch dragState { case NOT_DRAGGING: - return GocuiEvent{Type: eventNone} + return GocuiEvent{ + Type: eventMouseMove, + MouseX: x, + MouseY: y, + } // if we haven't released the left mouse button and we've moved the cursor then we're dragging case MAYBE_DRAGGING: if x != lastX || y != lastY { diff --git a/vendor/github.com/jesseduffield/gocui/view.go b/vendor/github.com/jesseduffield/gocui/view.go index 8933c2c7464..07dc08f25df 100644 --- a/vendor/github.com/jesseduffield/gocui/view.go +++ b/vendor/github.com/jesseduffield/gocui/view.go @@ -56,6 +56,13 @@ type View struct { // tained is true if the viewLines must be updated tainted bool + // the last position that the mouse was hovering over; nil if the mouse is outside of + // this view, or not hovering over a cell + lastHoverPosition *pos + + // the location of the hyperlink that the mouse is currently hovering over; nil if none + hoveredHyperlink *SearchPosition + // internal representation of the view's buffer. We will keep viewLines around // from a previous render until we explicitly set them to nil, allowing us to // render the same content twice without flicker. Wherever we want to render @@ -180,6 +187,14 @@ type View struct { // if true, the user can scroll all the way past the last item until it appears at the top of the view CanScrollPastBottom bool + + // if true, the view will underline hyperlinks only when the cursor is on + // them; otherwise, they will always be underlined + UnderlineHyperLinksOnlyOnHover bool +} + +type pos struct { + x, y int } // call this in the event of a view resize, or if you want to render new content @@ -188,6 +203,7 @@ type View struct { func (v *View) clearViewLines() { v.tainted = true v.viewLines = nil + v.clearHover() } type searcher struct { @@ -532,6 +548,10 @@ func (v *View) setRune(x, y int, ch rune, fgColor, bgColor Attribute) error { } } + if v.isHoveredHyperlink(x, y) { + fgColor |= AttrUnderline + } + // Don't display NUL characters if ch == 0 { ch = ' ' @@ -756,6 +776,7 @@ func (v *View) WriteRunes(p []rune) { // writeRunes copies slice of runes into internal lines buffer. func (v *View) writeRunes(p []rune) { v.tainted = true + v.clearHover() // Fill with empty cells, if writing outside current view buffer v.makeWriteable(v.wx, v.wy) @@ -1164,7 +1185,7 @@ func (v *View) draw() error { if bgColor == ColorDefault { bgColor = v.BgColor } - if c.hyperlink != "" { + if c.hyperlink != "" && !v.UnderlineHyperLinksOnlyOnHover { fgColor |= AttrUnderline } @@ -1236,6 +1257,15 @@ func (v *View) isPatternMatchedRune(x, y int) (bool, bool) { return false, false } +func (v *View) isHoveredHyperlink(x, y int) bool { + if v.UnderlineHyperLinksOnlyOnHover && v.hoveredHyperlink != nil { + adjustedY := y + v.oy + adjustedX := x + v.ox + return adjustedY == v.hoveredHyperlink.Y && adjustedX >= v.hoveredHyperlink.XStart && adjustedX < v.hoveredHyperlink.XEnd + } + return false +} + // realPosition returns the position in the internal buffer corresponding to the // point (x, y) of the view. func (v *View) realPosition(vx, vy int) (x, y int, err error) { @@ -1406,6 +1436,7 @@ func (v *View) SetHighlight(y int, on bool) error { } v.tainted = true v.lines[y] = cells + v.clearHover() return nil } @@ -1672,8 +1703,12 @@ func (v *View) ScrollUp(amount int) { amount = v.oy } - v.oy -= amount - v.cy += amount + if amount != 0 { + v.oy -= amount + v.cy += amount + + v.clearHover() + } } // ensures we don't scroll past the end of the view's content @@ -1682,6 +1717,8 @@ func (v *View) ScrollDown(amount int) { if adjustedAmount > 0 { v.oy += adjustedAmount v.cy -= adjustedAmount + + v.clearHover() } } @@ -1690,12 +1727,18 @@ func (v *View) ScrollLeft(amount int) { if newOx < 0 { newOx = 0 } - v.ox = newOx + if newOx != v.ox { + v.ox = newOx + + v.clearHover() + } } // not applying any limits to this func (v *View) ScrollRight(amount int) { v.ox += amount + + v.clearHover() } func (v *View) adjustDownwardScrollAmount(scrollHeight int) int { @@ -1769,3 +1812,49 @@ func containsColoredTextInLine(fgColorStr string, text string, line []cell) bool return strings.Contains(currentMatch, text) } + +func (v *View) onMouseMove(x int, y int) { + if v.Editable || !v.UnderlineHyperLinksOnlyOnHover { + return + } + + // newCx and newCy are relative to the view port, i.e. to the visible area of the view + newCx := x - v.x0 - 1 + newCy := y - v.y0 - 1 + // newX and newY are relative to the view's content, independent of its scroll position + newX := newCx + v.ox + newY := newCy + v.oy + + if newY >= 0 && newY <= len(v.viewLines)-1 && newX >= 0 && newX <= len(v.viewLines[newY].line)-1 { + if v.lastHoverPosition == nil || v.lastHoverPosition.x != newX || v.lastHoverPosition.y != newY { + v.hoveredHyperlink = v.findHyperlinkAt(newX, newY) + } + v.lastHoverPosition = &pos{x: newX, y: newY} + } else { + v.lastHoverPosition = nil + v.hoveredHyperlink = nil + } +} + +func (v *View) findHyperlinkAt(x, y int) *SearchPosition { + linkStr := v.viewLines[y].line[x].hyperlink + if linkStr == "" { + return nil + } + + xStart := x + for xStart > 0 && v.viewLines[y].line[xStart-1].hyperlink == linkStr { + xStart-- + } + xEnd := x + 1 + for xEnd < len(v.viewLines[y].line) && v.viewLines[y].line[xEnd].hyperlink == linkStr { + xEnd++ + } + + return &SearchPosition{XStart: xStart, XEnd: xEnd, Y: y} +} + +func (v *View) clearHover() { + v.hoveredHyperlink = nil + v.lastHoverPosition = nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index f052e5c4322..e08e23a02c9 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -172,7 +172,7 @@ github.com/jesseduffield/go-git/v5/utils/merkletrie/filesystem github.com/jesseduffield/go-git/v5/utils/merkletrie/index github.com/jesseduffield/go-git/v5/utils/merkletrie/internal/frame github.com/jesseduffield/go-git/v5/utils/merkletrie/noder -# github.com/jesseduffield/gocui v0.3.1-0.20240824094505-8cce5f5d2511 +# github.com/jesseduffield/gocui v0.3.1-0.20240824154427-0fc91d5098e4 ## explicit; go 1.12 github.com/jesseduffield/gocui # github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 From 68c7f9840acd81f3f762cabc6c7e5f5c7a7bce7b Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sat, 24 Aug 2024 12:16:07 +0200 Subject: [PATCH 2/2] Set main views to underline hyperlinks only on mouse hover Note that this doesn't only affect the diff views, which are the ones where we want this, but also the status view, where the plan was to keep them underlined always for better discoverability. We could make this configurable dynamically (by adding another flag to ViewUpdateOpts), but actually I think it's fine this way. --- pkg/gui/views.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/gui/views.go b/pkg/gui/views.go index e76ed24d3c4..f561eac3753 100644 --- a/pkg/gui/views.go +++ b/pkg/gui/views.go @@ -117,6 +117,7 @@ func (gui *Gui) createAllViews() error { view.Title = gui.c.Tr.DiffTitle view.Wrap = true view.IgnoreCarriageReturns = true + view.UnderlineHyperLinksOnlyOnHover = true } gui.Views.Staging.Title = gui.c.Tr.UnstagedChanges