~taiite/senpai

cd28b242ff84f85e6536af9ee6343308de5885d3 — delthas 7 months ago 9336f97
Mark hyperlinks with the OSC hyperlink terminal escape

This makes multi-line links properly clickable.

See: https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
4 files changed, 88 insertions(+), 3 deletions(-)

M go.mod
M go.sum
M ui/buffers.go
M ui/style.go
M go.mod => go.mod +3 -0
@@ 8,4 8,7 @@ require (
	golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf
	golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6
	gopkg.in/yaml.v2 v2.3.0
	mvdan.cc/xurls/v2 v2.3.0
)

replace github.com/gdamore/tcell/v2 => github.com/hhirtz/tcell/v2 v2.3.12-0.20210807133752-5d743c3ab0c9

M go.sum => go.sum +14 -3
@@ 1,13 1,20 @@
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell/v2 v2.3.11 h1:ECO6WqHGbKZ3HrSL7bG/zArMCmLaNr5vcjjMVnLHpzc=
github.com/gdamore/tcell/v2 v2.3.11/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU=
github.com/hhirtz/tcell/v2 v2.3.12-0.20210807133752-5d743c3ab0c9 h1:YE0ZsDHfDGR0MeB6YLSGW8tjoxOXZKX3XbB0ytGDX4M=
github.com/hhirtz/tcell/v2 v2.3.12-0.20210807133752-5d743c3ab0c9/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M=


@@ 16,7 23,11 @@ golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 h1:Vv0JUPWTyeqUq42B2WJ1FeIDjjvGKoA2Ss+Ts0lAVbs=
golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
mvdan.cc/xurls/v2 v2.3.0 h1:59Olnbt67UKpxF1EwVBopJvkSUBmgtb468E4GVWIZ1I=
mvdan.cc/xurls/v2 v2.3.0/go.mod h1:AjuTy7gEiUArFMjgBBDU4SMxlfUYsRokpJQgNWOt3e4=

M ui/buffers.go => ui/buffers.go +6 -0
@@ 302,6 302,10 @@ func (bs *BufferList) AddLine(netID, title string, notify NotifyType, line Line)
	n := len(b.lines)
	line.At = line.At.UTC()

	if !line.Mergeable {
		line.Body = line.Body.ParseURLs()
	}

	if line.Mergeable && n != 0 && b.lines[n-1].Mergeable {
		l := &b.lines[n-1]
		newBody := new(StyledStringBuilder)


@@ 338,9 342,11 @@ func (bs *BufferList) AddLines(netID, title string, before, after []Line) {
	b := &bs.list[idx]

	for i := 0; i < len(before); i++ {
		before[i].Body = before[i].Body.ParseURLs()
		before[i].computeSplitPoints()
	}
	for i := 0; i < len(after); i++ {
		after[i].Body = after[i].Body.ParseURLs()
		after[i].computeSplitPoints()
	}


M ui/style.go => ui/style.go +65 -0
@@ 6,6 6,8 @@ import (
	"strings"
	"unicode/utf8"

	"mvdan.cc/xurls/v2"

	"github.com/gdamore/tcell/v2"
	"github.com/mattn/go-runewidth"
)


@@ 115,6 117,69 @@ func (s StyledString) Truncate(w int, tail StyledString) StyledString {
	return sb.StyledString()
}

var urlRegex = xurls.Relaxed()

func (s StyledString) ParseURLs() StyledString {
	styles := make([]rangedStyle, 0, len(s.styles))

	urls := urlRegex.FindAllStringIndex(s.string, -1)
	j := 0
	lastStyle := rangedStyle{
		Start: -1,
		Style: tcell.StyleDefault,
	}
	for i := 0; i < len(urls); i++ {
		u := urls[i]
		ub, ue := u[0], u[1]
		link := s.string[u[0]:u[1]] + "?a=b"
		// find last style starting before or at url begin
		for ; j < len(s.styles); j++ {
			st := s.styles[j]
			if st.Start > ub {
				break
			}
			if st.Start == ub {
				// a style already starts at this position, edit it
				lastStyle.Style = lastStyle.Style.Hyperlink(link)
			}
			lastStyle = st
			styles = append(styles, st)
		}
		if lastStyle.Start != ub {
			// no style existed at this position, add one from the last style
			styles = append(styles, rangedStyle{
				Start: ub,
				Style: lastStyle.Style.Hyperlink(link),
			})
		}
		// find last style starting before or at url end
		for ; j < len(s.styles); j++ {
			st := s.styles[j]
			if st.Start > ue {
				break
			}
			if st.Start < ue {
				st.Style = st.Style.Hyperlink(link)
			}
			lastStyle = st
			styles = append(styles, st)
		}
		if lastStyle.Start != ue {
			// no style existed at this position, add one from the last style without the hyperlink
			styles = append(styles, rangedStyle{
				Start: ue,
				Style: lastStyle.Style.Hyperlink(""),
			})
		}
	}
	styles = append(styles, s.styles[j:]...)

	return StyledString{
		string: s.string,
		styles: styles,
	}
}

func isDigit(c byte) bool {
	return '0' <= c && c <= '9'
}