diff --git a/style.go b/style.go index 8359e28c..e63fdffc 100644 --- a/style.go +++ b/style.go @@ -23,9 +23,10 @@ package tcell // // To use Style, just declare a variable of its type. type Style struct { - fg Color - bg Color - attrs AttrMask + fg Color + bg Color + attrs AttrMask + hyperlink string } // StyleDefault represents a default style, based upon the context. @@ -39,9 +40,10 @@ var styleInvalid = Style{attrs: AttrInvalid} // as requested. ColorDefault can be used to select the global default. func (s Style) Foreground(c Color) Style { return Style{ - fg: c, - bg: s.bg, - attrs: s.attrs, + fg: c, + bg: s.bg, + attrs: s.attrs, + hyperlink: s.hyperlink, } } @@ -49,9 +51,10 @@ func (s Style) Foreground(c Color) Style { // as requested. ColorDefault can be used to select the global default. func (s Style) Background(c Color) Style { return Style{ - fg: s.fg, - bg: c, - attrs: s.attrs, + fg: s.fg, + bg: c, + attrs: s.attrs, + hyperlink: s.hyperlink, } } @@ -64,23 +67,26 @@ func (s Style) Decompose() (fg Color, bg Color, attr AttrMask) { func (s Style) setAttrs(attrs AttrMask, on bool) Style { if on { return Style{ - fg: s.fg, - bg: s.bg, - attrs: s.attrs | attrs, + fg: s.fg, + bg: s.bg, + attrs: s.attrs | attrs, + hyperlink: s.hyperlink, } } return Style{ - fg: s.fg, - bg: s.bg, - attrs: s.attrs &^ attrs, + fg: s.fg, + bg: s.bg, + attrs: s.attrs &^ attrs, + hyperlink: s.hyperlink, } } // Normal returns the style with all attributes disabled. func (s Style) Normal() Style { return Style{ - fg: s.fg, - bg: s.bg, + fg: s.fg, + bg: s.bg, + hyperlink: s.hyperlink, } } @@ -130,8 +136,20 @@ func (s Style) StrikeThrough(on bool) Style { // specified. func (s Style) Attributes(attrs AttrMask) Style { return Style{ - fg: s.fg, - bg: s.bg, - attrs: attrs, + fg: s.fg, + bg: s.bg, + attrs: attrs, + hyperlink: s.hyperlink, + } +} + +// Hyperlink returns a new style based on s, with its hyperlink set to the +// specified URL. An empty string disables the hyperlink. +func (s Style) Hyperlink(url string) Style { + return Style{ + fg: s.fg, + bg: s.bg, + attrs: s.attrs, + hyperlink: url, } } diff --git a/terminfo/f/foot/foot.go b/terminfo/f/foot/foot.go index f50f703f..18cad069 100644 --- a/terminfo/f/foot/foot.go +++ b/terminfo/f/foot/foot.go @@ -66,5 +66,8 @@ func init() { KeyBacktab: "\x1b[Z", Modifiers: 1, AutoMargin: true, + + Hyperlink: "\x1b]8;;", + StringTerminator: "\x1b\\", }) } diff --git a/terminfo/terminfo.go b/terminfo/terminfo.go index 193e06e3..3f511e3b 100644 --- a/terminfo/terminfo.go +++ b/terminfo/terminfo.go @@ -227,6 +227,8 @@ type Terminfo struct { CursorSteadyUnderline string CursorBlinkingBar string CursorSteadyBar string + StringTerminator string + Hyperlink string } const ( diff --git a/tscreen.go b/tscreen.go index 343e79e8..74cc7343 100644 --- a/tscreen.go +++ b/tscreen.go @@ -58,6 +58,17 @@ func LookupTerminfo(name string) (ti *terminfo.Terminfo, e error) { terminfo.AddTerminfo(ti) } + if vteVersion := os.Getenv("VTE_VERSION"); vteVersion != "" && ti.Hyperlink == "" && ti.StringTerminator == "" { + v, err := strconv.Atoi(vteVersion) + if err == nil && v > 5000 { + // hyperlinks are supported in VTE-based terminal on VTE >= 0.50.0. + // some terminals do not support it even when VTE does, but they will ignore the escape code in those cases. + // see https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda#terminal-emulators + ti.Hyperlink = "\x1b]8;;" + ti.StringTerminator = "\x1b\\" + } + } + return } @@ -749,6 +760,11 @@ func (t *tScreen) drawCell(x, y int) int { if attrs&AttrStrikeThrough != 0 { t.TPuts(ti.StrikeThrough) } + if style.hyperlink != "" && ti.Hyperlink != "" { + t.TPuts(ti.Hyperlink + style.hyperlink + ti.StringTerminator) + } else if t.curstyle.hyperlink != "" && ti.Hyperlink != "" { + t.TPuts(ti.Hyperlink + ti.StringTerminator) + } t.curstyle = style } // now emit runes - taking care to not overrun width with a