Support hyperlinks (OSC 8) in the main window

Close #2557
This commit is contained in:
Junegunn Choi 2024-08-14 23:04:05 +09:00
parent 581734c369
commit 387c6ef664
No known key found for this signature in database
GPG Key ID: 254BC280FEF9C627
5 changed files with 41 additions and 13 deletions

View File

@ -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 `--tmux bottom` when the status line is not at the bottom
- Fixed extra scroll offset in multi-line mode (`--read0` or `--wrap`) - Fixed extra scroll offset in multi-line mode (`--read0` or `--wrap`)
- Added fallback `ps` command for `kill` completion on Cygwin - Added fallback `ps` command for `kill` completion on Cygwin

View File

@ -16,6 +16,7 @@ type colorOffset struct {
offset [2]int32 offset [2]int32
color tui.ColorPair color tui.ColorPair
match bool match bool
url *url
} }
type Result struct { type Result struct {
@ -177,8 +178,11 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
if curr != 0 && idx > start { if curr != 0 && idx > start {
if curr < 0 { if curr < 0 {
color := colMatch color := colMatch
var url *url
if curr < -1 && theme.Colored { 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 // hl or hl+ only sets the foreground color, so colMatch is the
// combination of either [hl and bg] or [hl+ and bg+]. // 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{ 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 { } else {
ansi := itemColors[curr-1] ansi := itemColors[curr-1]
colors = append(colors, colorOffset{ colors = append(colors, colorOffset{
offset: [2]int32{int32(start), int32(idx)}, offset: [2]int32{int32(start), int32(idx)},
color: ansiToColorPair(ansi, colBase), color: ansiToColorPair(ansi, colBase),
match: false}) match: false,
url: ansi.color.url})
} }
} }
} }

View File

@ -2460,15 +2460,24 @@ func (t *Terminal) printColoredString(window tui.Window, text []rune, offsets []
var substr string var substr string
var prefixWidth int var prefixWidth int
maxOffset := int32(len(text)) maxOffset := int32(len(text))
var url *url
for _, offset := range offsets { for _, offset := range offsets {
b := util.Constrain32(offset.offset[0], index, maxOffset) b := util.Constrain32(offset.offset[0], index, maxOffset)
e := util.Constrain32(offset.offset[1], 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) substr, prefixWidth = t.processTabs(text[index:b], prefixWidth)
window.CPrint(colBase, substr) window.CPrint(colBase, substr)
if b < e { if b < e {
substr, prefixWidth = t.processTabs(text[b:e], prefixWidth) 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) window.CPrint(offset.color, substr)
} }
@ -2477,6 +2486,9 @@ func (t *Terminal) printColoredString(window tui.Window, text []rune, offsets []
break break
} }
} }
if url != nil {
window.LinkEnd()
}
if index < maxOffset { if index < maxOffset {
substr, _ = t.processTabs(text[index:], prefixWidth) substr, _ = t.processTabs(text[index:], prefixWidth)
window.CPrint(colBase, substr) window.CPrint(colBase, substr)

View File

@ -1030,13 +1030,13 @@ func cleanse(str string) string {
func (w *LightWindow) CPrint(pair ColorPair, text string) { func (w *LightWindow) CPrint(pair ColorPair, text string) {
_, code := w.csiColor(pair.Fg(), pair.Bg(), pair.Attr()) _, code := w.csiColor(pair.Fg(), pair.Bg(), pair.Attr())
w.stderrInternal(cleanse(text), false, code) w.stderrInternal(cleanse(text), false, code)
w.csi("m") w.csi("0m")
} }
func (w *LightWindow) cprint2(fg Color, bg Color, attr Attr, text string) { func (w *LightWindow) cprint2(fg Color, bg Color, attr Attr, text string) {
hasColors, code := w.csiColor(fg, bg, attr) hasColors, code := w.csiColor(fg, bg, attr)
if hasColors { if hasColors {
defer w.csi("m") defer w.csi("0m")
} }
w.stderrInternal(cleanse(text), false, code) 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 bg = w.bg
} }
if hasColors, resetCode := w.csiColor(fg, bg, attr); hasColors { 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, resetCode)
} }
return w.fill(text, w.setBg()) return w.fill(text, w.setBg())

View File

@ -604,6 +604,16 @@ func (w *TcellWindow) Print(text string) {
w.printString(text, w.normal) 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) { func (w *TcellWindow) printString(text string, pair ColorPair) {
lx := 0 lx := 0
a := pair.Attr() a := pair.Attr()
@ -618,6 +628,7 @@ func (w *TcellWindow) printString(text string, pair ColorPair) {
Blink(a&Attr(tcell.AttrBlink) != 0). Blink(a&Attr(tcell.AttrBlink) != 0).
Dim(a&Attr(tcell.AttrDim) != 0) Dim(a&Attr(tcell.AttrDim) != 0)
} }
style = w.withUrl(style)
gr := uniseg.NewGraphemes(text) gr := uniseg.NewGraphemes(text)
for gr.Next() { for gr.Next() {
@ -668,13 +679,7 @@ func (w *TcellWindow) fillString(text string, pair ColorPair) FillReturn {
Underline(a&Attr(tcell.AttrUnderline) != 0). Underline(a&Attr(tcell.AttrUnderline) != 0).
StrikeThrough(a&Attr(tcell.AttrStrikeThrough) != 0). StrikeThrough(a&Attr(tcell.AttrStrikeThrough) != 0).
Italic(a&Attr(tcell.AttrItalic) != 0) Italic(a&Attr(tcell.AttrItalic) != 0)
style = w.withUrl(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])
}
}
gr := uniseg.NewGraphemes(text) gr := uniseg.NewGraphemes(text)
Loop: Loop: