mirror of
https://github.com/Llewellynvdm/fzf.git
synced 2025-04-05 16:51:50 +00:00
parent
2c8a96bb27
commit
d90a969c00
38
src/ansi.go
38
src/ansi.go
@ -1,6 +1,7 @@
|
|||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
@ -13,22 +14,28 @@ type ansiOffset struct {
|
|||||||
color ansiState
|
color ansiState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type url struct {
|
||||||
|
uri string
|
||||||
|
params string
|
||||||
|
}
|
||||||
|
|
||||||
type ansiState struct {
|
type ansiState struct {
|
||||||
fg tui.Color
|
fg tui.Color
|
||||||
bg tui.Color
|
bg tui.Color
|
||||||
attr tui.Attr
|
attr tui.Attr
|
||||||
lbg tui.Color
|
lbg tui.Color
|
||||||
|
url *url
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ansiState) colored() bool {
|
func (s *ansiState) colored() bool {
|
||||||
return s.fg != -1 || s.bg != -1 || s.attr > 0 || s.lbg >= 0
|
return s.fg != -1 || s.bg != -1 || s.attr > 0 || s.lbg >= 0 || s.url != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ansiState) equals(t *ansiState) bool {
|
func (s *ansiState) equals(t *ansiState) bool {
|
||||||
if t == nil {
|
if t == nil {
|
||||||
return !s.colored()
|
return !s.colored()
|
||||||
}
|
}
|
||||||
return s.fg == t.fg && s.bg == t.bg && s.attr == t.attr && s.lbg == t.lbg
|
return s.fg == t.fg && s.bg == t.bg && s.attr == t.attr && s.lbg == t.lbg && s.url == t.url
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ansiState) ToString() string {
|
func (s *ansiState) ToString() string {
|
||||||
@ -60,7 +67,11 @@ func (s *ansiState) ToString() string {
|
|||||||
}
|
}
|
||||||
ret += toAnsiString(s.fg, 30) + toAnsiString(s.bg, 40)
|
ret += toAnsiString(s.fg, 30) + toAnsiString(s.bg, 40)
|
||||||
|
|
||||||
return "\x1b[" + strings.TrimSuffix(ret, ";") + "m"
|
ret = "\x1b[" + strings.TrimSuffix(ret, ";") + "m"
|
||||||
|
if s.url != nil {
|
||||||
|
ret = fmt.Sprintf("\x1b]8;%s;%s\x1b\\%s\x1b]8;;\x1b", s.url.params, s.url.uri, ret)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func toAnsiString(color tui.Color, offset int) string {
|
func toAnsiString(color tui.Color, offset int) string {
|
||||||
@ -98,10 +109,19 @@ func matchOperatingSystemCommand(s string) int {
|
|||||||
if s[i] == '\x07' {
|
if s[i] == '\x07' {
|
||||||
return i + 1
|
return i + 1
|
||||||
}
|
}
|
||||||
|
// `\x1b]8;PARAMS;URI\x1b\\TITLE\x1b]8;;\x1b`
|
||||||
|
// ------
|
||||||
if s[i] == '\x1b' && i < len(s)-1 && s[i+1] == '\\' {
|
if s[i] == '\x1b' && i < len(s)-1 && s[i+1] == '\\' {
|
||||||
return i + 2
|
return i + 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `\x1b]8;PARAMS;URI\x1b\\TITLE\x1b]8;;\x1b`
|
||||||
|
// ------------
|
||||||
|
if i < len(s) && s[:i+1] == "\x1b]8;;\x1b" {
|
||||||
|
return i + 1
|
||||||
|
}
|
||||||
|
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -328,13 +348,21 @@ func parseAnsiCode(s string, delimiter byte) (int, byte, string) {
|
|||||||
func interpretCode(ansiCode string, prevState *ansiState) ansiState {
|
func interpretCode(ansiCode string, prevState *ansiState) ansiState {
|
||||||
var state ansiState
|
var state ansiState
|
||||||
if prevState == nil {
|
if prevState == nil {
|
||||||
state = ansiState{-1, -1, 0, -1}
|
state = ansiState{-1, -1, 0, -1, nil}
|
||||||
} else {
|
} else {
|
||||||
state = ansiState{prevState.fg, prevState.bg, prevState.attr, prevState.lbg}
|
state = ansiState{prevState.fg, prevState.bg, prevState.attr, prevState.lbg, prevState.url}
|
||||||
}
|
}
|
||||||
if ansiCode[0] != '\x1b' || ansiCode[1] != '[' || ansiCode[len(ansiCode)-1] != 'm' {
|
if ansiCode[0] != '\x1b' || ansiCode[1] != '[' || ansiCode[len(ansiCode)-1] != 'm' {
|
||||||
if prevState != nil && strings.HasSuffix(ansiCode, "0K") {
|
if prevState != nil && strings.HasSuffix(ansiCode, "0K") {
|
||||||
state.lbg = prevState.bg
|
state.lbg = prevState.bg
|
||||||
|
} else if ansiCode == "\x1b]8;;\x1b" { // End of a hyperlink
|
||||||
|
state.url = nil
|
||||||
|
} else if strings.HasPrefix(ansiCode, "\x1b]8;") && strings.HasSuffix(ansiCode, "\x1b\\") {
|
||||||
|
if paramsEnd := strings.IndexRune(ansiCode[4:], ';'); paramsEnd >= 0 {
|
||||||
|
params := ansiCode[4 : 4+paramsEnd]
|
||||||
|
uri := ansiCode[5+paramsEnd : len(ansiCode)-2]
|
||||||
|
state.url = &url{uri: uri, params: params}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
@ -124,10 +124,10 @@ func TestColorOffset(t *testing.T) {
|
|||||||
item := Result{
|
item := Result{
|
||||||
item: &Item{
|
item: &Item{
|
||||||
colors: &[]ansiOffset{
|
colors: &[]ansiOffset{
|
||||||
{[2]int32{0, 20}, ansiState{1, 5, 0, -1}},
|
{[2]int32{0, 20}, ansiState{1, 5, 0, -1, nil}},
|
||||||
{[2]int32{22, 27}, ansiState{2, 6, tui.Bold, -1}},
|
{[2]int32{22, 27}, ansiState{2, 6, tui.Bold, -1, nil}},
|
||||||
{[2]int32{30, 32}, ansiState{3, 7, 0, -1}},
|
{[2]int32{30, 32}, ansiState{3, 7, 0, -1, nil}},
|
||||||
{[2]int32{33, 40}, ansiState{4, 8, tui.Bold, -1}}}}}
|
{[2]int32{33, 40}, ansiState{4, 8, tui.Bold, -1, nil}}}}}
|
||||||
|
|
||||||
colBase := tui.NewColorPair(89, 189, tui.AttrUndefined)
|
colBase := tui.NewColorPair(89, 189, tui.AttrUndefined)
|
||||||
colMatch := tui.NewColorPair(99, 199, tui.AttrUndefined)
|
colMatch := tui.NewColorPair(99, 199, tui.AttrUndefined)
|
||||||
|
@ -1068,7 +1068,7 @@ func (t *Terminal) parsePrompt(prompt string) (func(), int) {
|
|||||||
// // unless the part has a non-default ANSI state
|
// // unless the part has a non-default ANSI state
|
||||||
loc := whiteSuffix.FindStringIndex(trimmed)
|
loc := whiteSuffix.FindStringIndex(trimmed)
|
||||||
if loc != nil {
|
if loc != nil {
|
||||||
blankState := ansiOffset{[2]int32{int32(loc[0]), int32(loc[1])}, ansiState{-1, -1, tui.AttrClear, -1}}
|
blankState := ansiOffset{[2]int32{int32(loc[0]), int32(loc[1])}, ansiState{-1, -1, tui.AttrClear, -1, nil}}
|
||||||
if item.colors != nil {
|
if item.colors != nil {
|
||||||
lastColor := (*item.colors)[len(*item.colors)-1]
|
lastColor := (*item.colors)[len(*item.colors)-1]
|
||||||
if lastColor.offset[1] < int32(loc[1]) {
|
if lastColor.offset[1] < int32(loc[1]) {
|
||||||
@ -2668,12 +2668,21 @@ Loop:
|
|||||||
|
|
||||||
var fillRet tui.FillReturn
|
var fillRet tui.FillReturn
|
||||||
prefixWidth := 0
|
prefixWidth := 0
|
||||||
|
var url *url
|
||||||
_, _, ansi = extractColor(line, ansi, func(str string, ansi *ansiState) bool {
|
_, _, ansi = extractColor(line, ansi, func(str string, ansi *ansiState) bool {
|
||||||
trimmed := []rune(str)
|
trimmed := []rune(str)
|
||||||
isTrimmed := false
|
isTrimmed := false
|
||||||
if !t.previewOpts.wrap {
|
if !t.previewOpts.wrap {
|
||||||
trimmed, isTrimmed = t.trimRight(trimmed, maxWidth-t.pwindow.X())
|
trimmed, isTrimmed = t.trimRight(trimmed, maxWidth-t.pwindow.X())
|
||||||
}
|
}
|
||||||
|
if url == nil && ansi != nil && ansi.url != nil {
|
||||||
|
url = ansi.url
|
||||||
|
t.pwindow.LinkBegin(url.uri, url.params)
|
||||||
|
}
|
||||||
|
if url != nil && (ansi == nil || ansi.url == nil) {
|
||||||
|
url = nil
|
||||||
|
t.pwindow.LinkEnd()
|
||||||
|
}
|
||||||
str, width := t.processTabs(trimmed, prefixWidth)
|
str, width := t.processTabs(trimmed, prefixWidth)
|
||||||
if width > prefixWidth {
|
if width > prefixWidth {
|
||||||
prefixWidth = width
|
prefixWidth = width
|
||||||
@ -2687,6 +2696,9 @@ Loop:
|
|||||||
return !isTrimmed &&
|
return !isTrimmed &&
|
||||||
(fillRet == tui.FillContinue || t.previewOpts.wrap && fillRet == tui.FillNextLine)
|
(fillRet == tui.FillContinue || t.previewOpts.wrap && fillRet == tui.FillNextLine)
|
||||||
})
|
})
|
||||||
|
if url != nil {
|
||||||
|
t.pwindow.LinkEnd()
|
||||||
|
}
|
||||||
t.previewer.scrollable = t.previewer.scrollable || t.pwindow.Y() == height-1 && t.pwindow.X() == t.pwindow.Width()
|
t.previewer.scrollable = t.previewer.scrollable || t.pwindow.Y() == height-1 && t.pwindow.X() == t.pwindow.Width()
|
||||||
if fillRet == tui.FillNextLine {
|
if fillRet == tui.FillNextLine {
|
||||||
continue
|
continue
|
||||||
|
@ -1118,6 +1118,14 @@ func (w *LightWindow) setBg() string {
|
|||||||
return "\x1b[m"
|
return "\x1b[m"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) LinkBegin(uri string, params string) {
|
||||||
|
w.renderer.queued.WriteString("\x1b]8;" + params + ";" + uri + "\x1b\\")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) LinkEnd() {
|
||||||
|
w.renderer.queued.WriteString("\x1b]8;;\x1b\\")
|
||||||
|
}
|
||||||
|
|
||||||
func (w *LightWindow) Fill(text string) FillReturn {
|
func (w *LightWindow) Fill(text string) FillReturn {
|
||||||
w.Move(w.posy, w.posx)
|
w.Move(w.posy, w.posx)
|
||||||
code := w.setBg()
|
code := w.setBg()
|
||||||
|
@ -4,6 +4,7 @@ package tui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
@ -49,6 +50,8 @@ type TcellWindow struct {
|
|||||||
lastY int
|
lastY int
|
||||||
moveCursor bool
|
moveCursor bool
|
||||||
borderStyle BorderStyle
|
borderStyle BorderStyle
|
||||||
|
uri *string
|
||||||
|
params *string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) Top() int {
|
func (w *TcellWindow) Top() int {
|
||||||
@ -666,6 +669,13 @@ func (w *TcellWindow) fillString(text string, pair ColorPair) FillReturn {
|
|||||||
StrikeThrough(a&Attr(tcell.AttrStrikeThrough) != 0).
|
StrikeThrough(a&Attr(tcell.AttrStrikeThrough) != 0).
|
||||||
Italic(a&Attr(tcell.AttrItalic) != 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])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
gr := uniseg.NewGraphemes(text)
|
gr := uniseg.NewGraphemes(text)
|
||||||
Loop:
|
Loop:
|
||||||
for gr.Next() {
|
for gr.Next() {
|
||||||
@ -716,6 +726,16 @@ func (w *TcellWindow) Fill(str string) FillReturn {
|
|||||||
return w.fillString(str, w.normal)
|
return w.fillString(str, w.normal)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *TcellWindow) LinkBegin(uri string, params string) {
|
||||||
|
w.uri = &uri
|
||||||
|
w.params = ¶ms
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *TcellWindow) LinkEnd() {
|
||||||
|
w.uri = nil
|
||||||
|
w.params = nil
|
||||||
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
|
func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
|
||||||
if fg == colDefault {
|
if fg == colDefault {
|
||||||
fg = w.normal.Fg()
|
fg = w.normal.Fg()
|
||||||
|
@ -564,6 +564,8 @@ type Window interface {
|
|||||||
CPrint(color ColorPair, text string)
|
CPrint(color ColorPair, text string)
|
||||||
Fill(text string) FillReturn
|
Fill(text string) FillReturn
|
||||||
CFill(fg Color, bg Color, attr Attr, text string) FillReturn
|
CFill(fg Color, bg Color, attr Attr, text string) FillReturn
|
||||||
|
LinkBegin(uri string, params string)
|
||||||
|
LinkEnd()
|
||||||
Erase()
|
Erase()
|
||||||
EraseMaybe() bool
|
EraseMaybe() bool
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user