Skip to content

Commit ca3f730

Browse files
committed
Add supports for terminal hyperlinks in output
In supporting terminals (e.g. iTerm), the identifier field of table-based output like `issue/pr list` and `pr checks` is now a link that can be followed in a browser by Cmd-clicking it.
1 parent 6dba073 commit ca3f730

File tree

7 files changed

+43
-10
lines changed

7 files changed

+43
-10
lines changed

pkg/cmd/gist/view/view_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,7 @@ func Test_promptGists(t *testing.T) {
443443
}
444444

445445
io, _, _, _ := iostreams.Test()
446-
cs := iostreams.NewColorScheme(io.ColorEnabled(), io.ColorSupport256())
446+
cs := io.ColorScheme()
447447

448448
for _, tt := range tests {
449449
reg := &httpmock.Registry{}

pkg/cmd/issue/shared/display.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@ func PrintIssues(io *iostreams.IOStreams, prefix string, totalCount int, issues
2828
}
2929
now := time.Now()
3030
ago := now.Sub(issue.UpdatedAt)
31-
table.AddField(issueNum, nil, cs.ColorFromString(prShared.ColorForState(issue.State)))
31+
colorFunc := cs.ColorFromString(prShared.ColorForState(issue.State))
32+
issueURL := issue.URL
33+
table.AddField(issueNum, nil, func(t string) string {
34+
return colorFunc(cs.Hyperlink(t, issueURL))
35+
})
3236
if !table.IsTTY() {
3337
table.AddField(issue.State, nil, nil)
3438
}

pkg/cmd/pr/checks/checks.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,9 +190,18 @@ func checksRun(opts *ChecksOptions) error {
190190
for _, o := range outputs {
191191
if isTerminal {
192192
tp.AddField(o.mark, nil, o.markColor)
193-
tp.AddField(o.name, nil, nil)
193+
var linkFunc func(string) string
194+
if opts.IO.IsLinkEnabled() {
195+
url := o.link
196+
linkFunc = func(t string) string {
197+
return cs.Hyperlink(t, url)
198+
}
199+
}
200+
tp.AddField(o.name, nil, linkFunc)
194201
tp.AddField(o.elapsed, nil, nil)
195-
tp.AddField(o.link, nil, nil)
202+
if !opts.IO.IsLinkEnabled() {
203+
tp.AddField(o.link, nil, nil)
204+
}
196205
} else {
197206
tp.AddField(o.name, nil, nil)
198207
tp.AddField(o.bucket, nil, nil)

pkg/cmd/pr/list/list.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,11 @@ func listRun(opts *ListOptions) error {
131131
if table.IsTTY() {
132132
prNum = "#" + prNum
133133
}
134-
table.AddField(prNum, nil, cs.ColorFromString(shared.ColorForPR(pr)))
134+
prURL := pr.URL
135+
colorFunc := cs.ColorFromString(shared.ColorForPR(pr))
136+
table.AddField(prNum, nil, func(t string) string {
137+
return colorFunc(cs.Hyperlink(t, prURL))
138+
})
135139
table.AddField(text.ReplaceExcessiveWhitespace(pr.Title), nil, nil)
136140
table.AddField(pr.HeadLabel(), nil, cs.Cyan)
137141
if !table.IsTTY() {

pkg/iostreams/color.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,18 @@ func Is256ColorSupported() bool {
4444
strings.Contains(colorterm, "truecolor")
4545
}
4646

47-
func NewColorScheme(enabled, is256enabled bool) *ColorScheme {
47+
func NewColorScheme(enabled, is256enabled, isLinkEnabled bool) *ColorScheme {
4848
return &ColorScheme{
4949
enabled: enabled,
5050
is256enabled: is256enabled,
51+
linkEnabled: isLinkEnabled,
5152
}
5253
}
5354

5455
type ColorScheme struct {
5556
enabled bool
5657
is256enabled bool
58+
linkEnabled bool
5759
}
5860

5961
func (c *ColorScheme) Bold(t string) string {
@@ -202,3 +204,11 @@ func (c *ColorScheme) ColorFromString(s string) func(string) string {
202204

203205
return fn
204206
}
207+
208+
func (c *ColorScheme) Hyperlink(text, url string) string {
209+
if !c.linkEnabled {
210+
return text
211+
}
212+
// https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
213+
return fmt.Sprintf("\x1b]8;;%s\x1b\\%s\x1b]8;;\x1b\\", url, text)
214+
}

pkg/iostreams/iostreams.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type IOStreams struct {
3030
colorEnabled bool
3131
is256enabled bool
3232
terminalTheme string
33+
linkEnabled bool
3334

3435
progressIndicatorEnabled bool
3536
progressIndicator *spinner.Spinner
@@ -91,6 +92,10 @@ func (s *IOStreams) TerminalTheme() string {
9192
return s.terminalTheme
9293
}
9394

95+
func (s *IOStreams) IsLinkEnabled() bool {
96+
return s.linkEnabled
97+
}
98+
9499
func (s *IOStreams) SetStdinTTY(isTTY bool) {
95100
s.stdinTTYOverride = true
96101
s.stdinIsTTY = isTTY
@@ -252,7 +257,7 @@ func (s *IOStreams) TerminalWidth() int {
252257
}
253258

254259
func (s *IOStreams) ColorScheme() *ColorScheme {
255-
return NewColorScheme(s.ColorEnabled(), s.ColorSupport256())
260+
return NewColorScheme(s.ColorEnabled(), s.ColorSupport256(), s.IsLinkEnabled())
256261
}
257262

258263
func (s *IOStreams) ReadUserFile(fn string) ([]byte, error) {
@@ -295,6 +300,7 @@ func System() *IOStreams {
295300
ErrOut: colorable.NewColorable(os.Stderr),
296301
colorEnabled: EnvColorForced() || (!EnvColorDisabled() && stdoutIsTTY),
297302
is256enabled: Is256ColorSupported(),
303+
linkEnabled: os.Getenv("GH_HYPERLINK") != "",
298304
pagerCommand: pagerCommand,
299305
}
300306

utils/table_printer.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,15 +109,15 @@ func (t *ttyTablePrinter) Render() error {
109109
}
110110
}
111111
truncVal := field.TruncateFunc(colWidths[col], field.Text)
112+
if field.ColorFunc != nil {
113+
truncVal = field.ColorFunc(truncVal)
114+
}
112115
if col < numCols-1 {
113116
// pad value with spaces on the right
114117
if padWidth := colWidths[col] - text.DisplayWidth(field.Text); padWidth > 0 {
115118
truncVal += strings.Repeat(" ", padWidth)
116119
}
117120
}
118-
if field.ColorFunc != nil {
119-
truncVal = field.ColorFunc(truncVal)
120-
}
121121
_, err := fmt.Fprint(t.out, truncVal)
122122
if err != nil {
123123
return err

0 commit comments

Comments
 (0)