From 387c6ef664688582ca1be950e2fe260699aa8862 Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Wed, 14 Aug 2024 23:04:05 +0900 Subject: [PATCH] Support hyperlinks (OSC 8) in the main window Close #2557 --- CHANGELOG.md | 6 ++++++ src/result.go | 11 ++++++++--- src/terminal.go | 12 ++++++++++++ src/tui/light.go | 6 +++--- src/tui/tcell.go | 19 ++++++++++++------- 5 files changed, 41 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 461b7171a10..4ccb85c8763 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,12 @@ CHANGELOG # ... ' ``` +- Hyperlinks (OSC 8) are now supported in the preview window and in the main window + ```sh + printf '<< \e]8;;http://github.com/junegunn/fzf\e\\Link to \e[32mfz\e[0mf\e]8;;\e\\ >>' | fzf --ansi + + fzf --preview "printf '<< \e]8;;http://github.com/junegunn/fzf\e\\Link to \e[32mfz\e[0mf\e]8;;\e\\ >>'" + ``` - Fixed `--tmux bottom` when the status line is not at the bottom - Fixed extra scroll offset in multi-line mode (`--read0` or `--wrap`) - Added fallback `ps` command for `kill` completion on Cygwin diff --git a/src/result.go b/src/result.go index e1c208014b2..f10db19b40d 100644 --- a/src/result.go +++ b/src/result.go @@ -16,6 +16,7 @@ type colorOffset struct { offset [2]int32 color tui.ColorPair match bool + url *url } type Result struct { @@ -177,8 +178,11 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme, if curr != 0 && idx > start { if curr < 0 { color := colMatch + var url *url if curr < -1 && theme.Colored { - origColor := ansiToColorPair(itemColors[-curr-2], colMatch) + ansi := itemColors[-curr-2] + url = ansi.color.url + origColor := ansiToColorPair(ansi, colMatch) // hl or hl+ only sets the foreground color, so colMatch is the // combination of either [hl and bg] or [hl+ and bg+]. // @@ -194,13 +198,14 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme, } } colors = append(colors, colorOffset{ - offset: [2]int32{int32(start), int32(idx)}, color: color, match: true}) + offset: [2]int32{int32(start), int32(idx)}, color: color, match: true, url: url}) } else { ansi := itemColors[curr-1] colors = append(colors, colorOffset{ offset: [2]int32{int32(start), int32(idx)}, color: ansiToColorPair(ansi, colBase), - match: false}) + match: false, + url: ansi.color.url}) } } } diff --git a/src/terminal.go b/src/terminal.go index 62d5ae92124..1359f35c759 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -2460,15 +2460,24 @@ func (t *Terminal) printColoredString(window tui.Window, text []rune, offsets [] var substr string var prefixWidth int maxOffset := int32(len(text)) + var url *url for _, offset := range offsets { b := util.Constrain32(offset.offset[0], index, maxOffset) e := util.Constrain32(offset.offset[1], index, maxOffset) + if url != nil && offset.url == nil { + url = nil + window.LinkEnd() + } substr, prefixWidth = t.processTabs(text[index:b], prefixWidth) window.CPrint(colBase, substr) if b < e { substr, prefixWidth = t.processTabs(text[b:e], prefixWidth) + if url == nil && offset.url != nil { + url = offset.url + window.LinkBegin(url.uri, url.params) + } window.CPrint(offset.color, substr) } @@ -2477,6 +2486,9 @@ func (t *Terminal) printColoredString(window tui.Window, text []rune, offsets [] break } } + if url != nil { + window.LinkEnd() + } if index < maxOffset { substr, _ = t.processTabs(text[index:], prefixWidth) window.CPrint(colBase, substr) diff --git a/src/tui/light.go b/src/tui/light.go index 187ac6670bf..80488bf78c1 100644 --- a/src/tui/light.go +++ b/src/tui/light.go @@ -1030,13 +1030,13 @@ func cleanse(str string) string { func (w *LightWindow) CPrint(pair ColorPair, text string) { _, code := w.csiColor(pair.Fg(), pair.Bg(), pair.Attr()) w.stderrInternal(cleanse(text), false, code) - w.csi("m") + w.csi("0m") } func (w *LightWindow) cprint2(fg Color, bg Color, attr Attr, text string) { hasColors, code := w.csiColor(fg, bg, attr) if hasColors { - defer w.csi("m") + defer w.csi("0m") } w.stderrInternal(cleanse(text), false, code) } @@ -1141,7 +1141,7 @@ func (w *LightWindow) CFill(fg Color, bg Color, attr Attr, text string) FillRetu bg = w.bg } if hasColors, resetCode := w.csiColor(fg, bg, attr); hasColors { - defer w.csi("m") + defer w.csi("0m") return w.fill(text, resetCode) } return w.fill(text, w.setBg()) diff --git a/src/tui/tcell.go b/src/tui/tcell.go index d80cd58d3de..455dcf1395c 100644 --- a/src/tui/tcell.go +++ b/src/tui/tcell.go @@ -604,6 +604,16 @@ func (w *TcellWindow) Print(text string) { w.printString(text, w.normal) } +func (w *TcellWindow) withUrl(style tcell.Style) tcell.Style { + if w.uri != nil { + style = style.Url(*w.uri) + if md := regexp.MustCompile(`id=([^:]+)`).FindStringSubmatch(*w.params); len(md) > 1 { + style = style.UrlId(md[1]) + } + } + return style +} + func (w *TcellWindow) printString(text string, pair ColorPair) { lx := 0 a := pair.Attr() @@ -618,6 +628,7 @@ func (w *TcellWindow) printString(text string, pair ColorPair) { Blink(a&Attr(tcell.AttrBlink) != 0). Dim(a&Attr(tcell.AttrDim) != 0) } + style = w.withUrl(style) gr := uniseg.NewGraphemes(text) for gr.Next() { @@ -668,13 +679,7 @@ func (w *TcellWindow) fillString(text string, pair ColorPair) FillReturn { Underline(a&Attr(tcell.AttrUnderline) != 0). StrikeThrough(a&Attr(tcell.AttrStrikeThrough) != 0). Italic(a&Attr(tcell.AttrItalic) != 0) - - if w.uri != nil { - style = style.Url(*w.uri) - if md := regexp.MustCompile(`id=([^:]+)`).FindStringSubmatch(*w.params); len(md) > 1 { - style = style.UrlId(md[1]) - } - } + style = w.withUrl(style) gr := uniseg.NewGraphemes(text) Loop: