Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 1 addition & 1 deletion cmd/submit.go
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,7 @@ func executePRDecisions(g *git.Git, cfg *config.Config, root *tree.Node, decisio
if opts.DryRun {
fmt.Printf("%s Would update PR #%d base to %s\n", s.Muted("dry-run:"), d.prNum, s.Branch(parent))
} else {
fmt.Printf("Updating PR #%d for %s (base: %s)... ", d.prNum, s.Branch(b.Name), s.Branch(parent))
fmt.Printf("Updating %s for %s (base: %s)... ", s.Hyperlink(fmt.Sprintf("PR #%d", d.prNum), ghClient.PRURL(d.prNum)), s.Branch(b.Name), s.Branch(parent))
if err := ghClient.UpdatePRBase(d.prNum, parent); err != nil {
fmt.Println(s.Error("failed"))
fmt.Printf("%s failed to update PR #%d base: %v\n", s.WarningIcon(), d.prNum, err)
Expand Down
17 changes: 17 additions & 0 deletions internal/style/style.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,3 +191,20 @@ func (s *Style) WarningMessage(msg string) string {
func (s *Style) FailureMessage(msg string) string {
return fmt.Sprintf("%s %s", s.FailureIcon(), s.Error(msg))
}

// Hyperlink renders text as a terminal hyperlink to url using the OSC 8
// escape sequence when colors/TTY are enabled. When the terminal can't
// support it (NO_COLOR, piped output, dumb terminal), it falls back to
// "text (url)" so the URL stays visible to the user.
//
// Reference: https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
func (s *Style) Hyperlink(text, url string) string {
if !s.enabled || url == "" {
if url == "" {
return text
}
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

@mvanhorn This seems valid, but also I'm not too terribly concerned about piped use-cases.

return fmt.Sprintf("%s (%s)", text, url)
}
// OSC 8 hyperlink: ESC]8;;URLESC\TEXTESC]8;;ESC\
return fmt.Sprintf("\x1b]8;;%s\x1b\\%s\x1b]8;;\x1b\\", url, text)
}
40 changes: 40 additions & 0 deletions internal/style/style_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package style

import (
"strings"
"testing"
)

func TestHyperlinkColorsDisabled(t *testing.T) {
s := NewWithColor(false)
got := s.Hyperlink("PR #42", "https://github.com/owner/repo/pull/42")
want := "PR #42 (https://github.com/owner/repo/pull/42)"
if got != want {
t.Errorf("Hyperlink fallback: got %q, want %q", got, want)
}
}

func TestHyperlinkColorsEnabled(t *testing.T) {
s := NewWithColor(true)
got := s.Hyperlink("PR #42", "https://github.com/owner/repo/pull/42")
// OSC 8 sequence wraps the visible text
if !strings.Contains(got, "PR #42") {
t.Errorf("Hyperlink should contain visible text; got %q", got)
}
if !strings.Contains(got, "https://github.com/owner/repo/pull/42") {
t.Errorf("Hyperlink should contain URL; got %q", got)
}
if !strings.HasPrefix(got, "\x1b]8;;") {
t.Errorf("Hyperlink should start with OSC 8 sequence; got %q", got)
}
}

func TestHyperlinkEmptyURL(t *testing.T) {
for _, enabled := range []bool{false, true} {
s := NewWithColor(enabled)
got := s.Hyperlink("PR #42", "")
if got != "PR #42" {
t.Errorf("Hyperlink with empty URL (enabled=%v): got %q, want %q", enabled, got, "PR #42")
}
}
}
Loading