Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for hyperlink OSC #468

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 38 additions & 20 deletions style.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -39,19 +40,21 @@ 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,
}
}

// Background returns a new style based on s, with the background color set
// 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,
}
}

Expand All @@ -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,
}
}

Expand Down Expand Up @@ -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,
}
}
118 changes: 61 additions & 57 deletions terminfo/f/foot/foot.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,65 +5,69 @@ package foot
import "github.com/gdamore/tcell/v2/terminfo"

func init() {
const stringTerminator = "\x1b\\"
const hyperlink = "\x1b]8;;"

// foot terminal emulator
terminfo.AddTerminfo(&terminfo.Terminfo{
Name: "foot",
Columns: 80,
Lines: 24,
Colors: 256,
Bell: "\a",
Clear: "\x1b[H\x1b[2J",
EnterCA: "\x1b[?1049h\x1b[22;0;0t",
ExitCA: "\x1b[?1049l\x1b[23;0;0t",
ShowCursor: "\x1b[?12l\x1b[?25h",
HideCursor: "\x1b[?25l",
AttrOff: "\x1b(B\x1b[m",
Underline: "\x1b[4m",
Bold: "\x1b[1m",
Dim: "\x1b[2m",
Italic: "\x1b[3m",
Blink: "\x1b[5m",
Reverse: "\x1b[7m",
EnterKeypad: "\x1b[?1h\x1b=",
ExitKeypad: "\x1b[?1l\x1b>",
SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38:5:%p1%d%;m",
SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48:5:%p1%d%;m",
SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38:5:%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48:5:%p2%d%;m",
ResetFgBg: "\x1b[39;49m",
AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
EnterAcs: "\x1b(0",
ExitAcs: "\x1b(B",
StrikeThrough: "\x1b[9m",
Mouse: "\x1b[M",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
CursorBack1: "\b",
CursorUp1: "\x1b[A",
KeyUp: "\x1bOA",
KeyDown: "\x1bOB",
KeyRight: "\x1bOC",
KeyLeft: "\x1bOD",
KeyInsert: "\x1b[2~",
KeyDelete: "\x1b[3~",
KeyBackspace: "\u007f",
KeyHome: "\x1bOH",
KeyEnd: "\x1bOF",
KeyPgUp: "\x1b[5~",
KeyPgDn: "\x1b[6~",
KeyF1: "\x1bOP",
KeyF2: "\x1bOQ",
KeyF3: "\x1bOR",
KeyF4: "\x1bOS",
KeyF5: "\x1b[15~",
KeyF6: "\x1b[17~",
KeyF7: "\x1b[18~",
KeyF8: "\x1b[19~",
KeyF9: "\x1b[20~",
KeyF10: "\x1b[21~",
KeyF11: "\x1b[23~",
KeyF12: "\x1b[24~",
KeyBacktab: "\x1b[Z",
Modifiers: 1,
AutoMargin: true,
Name: "foot",
Columns: 80,
Lines: 24,
Colors: 256,
Bell: "\a",
Clear: "\x1b[H\x1b[2J",
EnterCA: "\x1b[?1049h\x1b[22;0;0t",
ExitCA: "\x1b[?1049l\x1b[23;0;0t",
ShowCursor: "\x1b[?12l\x1b[?25h",
HideCursor: "\x1b[?25l",
AttrOff: "\x1b(B\x1b[m",
Underline: "\x1b[4m",
Bold: "\x1b[1m",
Dim: "\x1b[2m",
Italic: "\x1b[3m",
Blink: "\x1b[5m",
Reverse: "\x1b[7m",
EnterKeypad: "\x1b[?1h\x1b=",
ExitKeypad: "\x1b[?1l\x1b>",
SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38:5:%p1%d%;m",
SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48:5:%p1%d%;m",
SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38:5:%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48:5:%p2%d%;m",
ResetFgBg: "\x1b[39;49m",
AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
EnterAcs: "\x1b(0",
ExitAcs: "\x1b(B",
StrikeThrough: "\x1b[9m",
Mouse: "\x1b[M",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
CursorBack1: "\b",
CursorUp1: "\x1b[A",
KeyUp: "\x1bOA",
KeyDown: "\x1bOB",
KeyRight: "\x1bOC",
KeyLeft: "\x1bOD",
KeyInsert: "\x1b[2~",
KeyDelete: "\x1b[3~",
KeyBackspace: "\u007f",
KeyHome: "\x1bOH",
KeyEnd: "\x1bOF",
KeyPgUp: "\x1b[5~",
KeyPgDn: "\x1b[6~",
KeyF1: "\x1bOP",
KeyF2: "\x1bOQ",
KeyF3: "\x1bOR",
KeyF4: "\x1bOS",
KeyF5: "\x1b[15~",
KeyF6: "\x1b[17~",
KeyF7: "\x1b[18~",
KeyF8: "\x1b[19~",
KeyF9: "\x1b[20~",
KeyF10: "\x1b[21~",
KeyF11: "\x1b[23~",
KeyF12: "\x1b[24~",
KeyBacktab: "\x1b[Z",
StartHyperlink: hyperlink + "%s" + stringTerminator,
EndHyperlink: hyperlink + stringTerminator,
Modifiers: 1,
AutoMargin: true,
})
}
6 changes: 6 additions & 0 deletions terminfo/terminfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,8 @@ type Terminfo struct {
CursorSteadyUnderline string
CursorBlinkingBar string
CursorSteadyBar string
StartHyperlink string
EndHyperlink string
}

const (
Expand Down Expand Up @@ -726,6 +728,10 @@ func (t *Terminfo) TColor(fi, bi int) string {
return rv
}

func (t *Terminfo) TStartHyperlink(url string) string {
return fmt.Sprintf(t.StartHyperlink, url)
}

var (
dblock sync.Mutex
terminfos = make(map[string]*Terminfo)
Expand Down
5 changes: 5 additions & 0 deletions tscreen.go
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,11 @@ func (t *tScreen) drawCell(x, y int) int {
if attrs&AttrStrikeThrough != 0 {
t.TPuts(ti.StrikeThrough)
}
if style.hyperlink != "" && ti.StartHyperlink != "" {
t.TPuts(ti.TStartHyperlink(style.hyperlink))
} else if t.curstyle.hyperlink != "" && ti.StartHyperlink != "" {
t.TPuts(ti.EndHyperlink)
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am concerned that the style code checks may cause gross inefficiencies -- we have to emit this value when changing the style, and now this string has to be part of that.

This also increases the size of the style -- which might be ok, but I've got some efficiency concerns.

I need to look at this in more depth.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am concerned that the style code checks may cause gross inefficiencies -- we have to emit this value when changing the style, and now this string has to be part of that.

What do you mean? The new OSC is only emitted when the hyperlink changes -- which should happen exactly as many times as there are hyperlinks on screen. I don't think there's a better way to reduce the number of OSC sequences.

This also increases the size of the style -- which might be ok, but I've got some efficiency concerns.

I don't think this is a big issue -- are there any benchmarks? Do you have any suggestions to improve this?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure the statement about when it is emitted is strictly true.

For example, what if part of your link is covered by a different style.

I also want to make sure that the check for style equality still works -- there is definitely code that tries to minimize changing attributes, and I'm not sure what the interaction will be. Mostly I just need to dig into this to be sure this works as intended.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, what I really want to do is refactor this slightly to use string based capabilities in terminfo. I think that will be a fair bit cleaner. Other than that it looks pretty good.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, thanks for the feedback, I'll look into this.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still not loving the separate begin and end hyperlink bits... but it might be the best approach.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's also a problem with %s I think (possible conflation with other bits in the terminfo standard.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still not loving the separate begin and end hyperlink bits... but it might be the best approach.

I don't think there's a way around it. There can be arbitrary escape codes between the start of an hyperlink and its end, including e.g. color changes and other style changes. Having a single escape code for hyperlinks won't cut it.

There's also a problem with %s I think (possible conflation with other bits in the terminfo standard.)

Any suggestions to use something else? Would %v be better?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In terminfo, %s is the right thing to use here, but technically it should be used with a pop operation. I need to review the code to see the best way to achieve what we want.

t.curstyle = style
}
// now emit runes - taking care to not overrun width with a
Expand Down