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'
}