feat: hyperlinks

Signed-off-by: drew <me@andrinoff.com>
This commit is contained in:
drew 2026-08-13 15:16:46 +04:00
commit 9d3e8e40df
No known key found for this signature in database
GPG key ID: 877E0EA9036E900E
4 changed files with 49 additions and 4 deletions

2
go.sum
View file

@ -2,8 +2,6 @@ charm.land/bubbles/v2 v2.1.1 h1:7r55WzBxpo/R3z98hGmY7KKPd3ET6vsf0Fb9sDHOV60=
charm.land/bubbles/v2 v2.1.1/go.mod h1:GE6M31gaWZVXzGw73OeuTTgy4lX+OtkH0E5ymnNsHxo=
charm.land/bubbletea/v2 v2.0.8 h1:SxTJMhCAI3lbPmy4SgX5LWZ24AdINr4I6UEqzZvYJuY=
charm.land/bubbletea/v2 v2.0.8/go.mod h1:2SkdgoTXluXJHOUwAoRlRXF/28vklb1rFl6GcgV1/ss=
charm.land/glamour/v2 v2.0.1 h1:xl+r00A4aJWU0z8fgwKd9fQQ4rsphqGUzuEiXZP5n+c=
charm.land/glamour/v2 v2.0.1/go.mod h1:jo9z8XqVKPeEFMVdvCRLGk++RyJ3CdUwgNr7EvXLw3k=
charm.land/lipgloss/v2 v2.0.6 h1:EaGKeuA8FvF+v2BT5VmZd2LoYLaMZJXA5n34th8nCIQ=
charm.land/lipgloss/v2 v2.0.6/go.mod h1:ipDDJNSGa1hlwDtSfW1s2/xR8Vdhbut4PXh2zEKZd0Q=
github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE=

View file

@ -16,6 +16,7 @@ import (
"mvdan.cc/sh/v3/shell"
"charm.land/glamour/v2"
glamsi "charm.land/glamour/v2/ansi"
"charm.land/glamour/v2/styles"
"charm.land/glow/v3/ui"
"charm.land/glow/v3/utils"
@ -290,12 +291,16 @@ func executeCLI(cmd *cobra.Command, src *source, w io.Writer) error {
isCode := !utils.IsMarkdownFile(src.URL)
// initialize glamour
r, err := glamour.NewTermRenderer(
options := []glamour.TermRendererOption{
utils.GlamourStyle(style, isCode),
glamour.WithWordWrap(int(width)), //nolint:gosec
glamour.WithBaseURL(baseURL),
glamour.WithPreservedNewLines(),
)
}
if utils.SupportsHyperlinks(os.Environ()) {
options = append(options, glamour.WithHyperlinkMode(glamsi.HyperlinkModeInline))
}
r, err := glamour.NewTermRenderer(options...)
if err != nil {
return fmt.Errorf("unable to create renderer: %w", err)
}

View file

@ -3,6 +3,7 @@ package ui
import (
"fmt"
"math"
"os"
"path/filepath"
"strings"
"time"
@ -10,6 +11,7 @@ import (
"charm.land/bubbles/v2/viewport"
tea "charm.land/bubbletea/v2"
"charm.land/glamour/v2"
glamsi "charm.land/glamour/v2/ansi"
"charm.land/glow/v3/utils"
"charm.land/lipgloss/v2"
"github.com/atotto/clipboard"
@ -369,6 +371,10 @@ func glamourRender(m pagerModel, markdown string) (string, error) {
glamour.WithWordWrap(width),
}
if utils.SupportsHyperlinks(os.Environ()) {
options = append(options, glamour.WithHyperlinkMode(glamsi.HyperlinkModeInline))
}
if m.common.cfg.PreserveNewLines {
options = append(options, glamour.WithPreservedNewLines())
}

View file

@ -111,3 +111,39 @@ func GlamourStyle(style string, isCode bool) glamour.TermRendererOption {
return glamour.WithStyles(styleConfig)
}
// SupportsHyperlinks reports whether the terminal is known to support OSC 8
// hyperlinks, based on environment variables.
func SupportsHyperlinks(env []string) bool {
get := func(key string) string {
for _, e := range env {
if k, v, ok := strings.Cut(e, "="); ok && k == key {
return v
}
}
return ""
}
// Explicit opt-out.
if get("TERM_PROGRAM") == "Apple_Terminal" {
return false
}
// Terminals known to support OSC 8 hyperlinks.
switch get("TERM_PROGRAM") {
case "iTerm.app", "WezTerm", "kitty", "alacritty", "Hyper", "ghostty", "vscode", "Tabby", " Rio":
return true
}
if get("WT_SESSION") != "" { // Windows Terminal
return true
}
if get("KONSOLE_VERSION") != "" { // Konsole
return true
}
if get("VTE_VERSION") != "" { // GNOME Terminal, Tilix, etc.
return true
}
if get("TERM") == "xterm-kitty" {
return true
}
return false
}