mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-05-10 20:05:50 +02:00
Underline hyperlinks only on mouse hover (#3856)
- **PR Description** Followup to #3825: we decided there that we don't want to underline links in delta diffs by default, but only on mouse hover. This PR does that; it makes it possible to decide per view whether links should be underlined always, or only on hover. We set this to only on hover for the main views, so that links in diffs are not underlined (also affects the status view though), but all other links we want to underline always for better discoverability.
This commit is contained in:
commit
8a8490d97d
7 changed files with 120 additions and 10 deletions
2
go.mod
2
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
|
||||
|
|
4
go.sum
4
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=
|
||||
|
|
|
@ -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
|
||||
|
|
17
vendor/github.com/jesseduffield/gocui/gui.go
generated
vendored
17
vendor/github.com/jesseduffield/gocui/gui.go
generated
vendored
|
@ -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:
|
||||
}
|
||||
|
||||
|
|
7
vendor/github.com/jesseduffield/gocui/tcell_driver.go
generated
vendored
7
vendor/github.com/jesseduffield/gocui/tcell_driver.go
generated
vendored
|
@ -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 {
|
||||
|
|
97
vendor/github.com/jesseduffield/gocui/view.go
generated
vendored
97
vendor/github.com/jesseduffield/gocui/view.go
generated
vendored
|
@ -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
|
||||
}
|
||||
|
|
2
vendor/modules.txt
vendored
2
vendor/modules.txt
vendored
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue