From 3b0c86e4013abb66f36108aedad4ef81fe2a06e2 Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Fri, 29 Nov 2024 00:26:12 +0900 Subject: [PATCH] Much faster image processing Fix #3984 --- CHANGELOG.md | 1 + src/terminal.go | 79 ++++++++++++++++++++++++++++++++++++++++---- src/terminal_test.go | 28 ++++++++++++++++ 3 files changed, 102 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0312b93..f385666 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG - Built-in walker improvements - `--walker-root` can take multiple directory arguments. e.g. `--walker-root include src lib` - `--walker-skip` can handle multi-component patterns. e.g. `--walker-skip target/build` +- Removed long processing delay when displaying images in the preview window - `FZF_PREVIEW_*` environment variables are exported to all child processes (#4098) - Bug fixes in fish scripts diff --git a/src/terminal.go b/src/terminal.go index b456e12..58c244e 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -51,7 +51,8 @@ var placeholder *regexp.Regexp var whiteSuffix *regexp.Regexp var offsetComponentRegex *regexp.Regexp var offsetTrimCharsRegex *regexp.Regexp -var passThroughRegex *regexp.Regexp +var passThroughBeginRegex *regexp.Regexp +var passThroughEndTmuxRegex *regexp.Regexp var ttyin *os.File const clearCode string = "\x1b[2J" @@ -74,7 +75,15 @@ func init() { // * https://sw.kovidgoyal.net/kitty/graphics-protocol // * https://en.wikipedia.org/wiki/Sixel // * https://iterm2.com/documentation-images.html - passThroughRegex = regexp.MustCompile(`\x1bPtmux;\x1b\x1b.*?[^\x1b]\x1b\\|\x1b(_G|P[0-9;]*q).*?\x1b\\\r?|\x1b]1337;.*?(\a|\x1b\\)`) + /* + passThroughRegex = regexp.MustCompile(` + \x1bPtmux;\x1b\x1b .*? [^\x1b]\x1b\\ + | \x1b(_G|P[0-9;]*q) .*? \x1b\\\r? + | \x1b]1337; .*? (\a|\x1b\\) + `) + */ + passThroughBeginRegex = regexp.MustCompile(`\x1bPtmux;\x1b\x1b|\x1b(_G|P[0-9;]*q)|\x1b]1337;`) + passThroughEndTmuxRegex = regexp.MustCompile(`[^\x1b]\x1b\\`) } type jumpMode int @@ -2622,6 +2631,67 @@ func (t *Terminal) makeImageBorder(width int, top bool) string { return v + strings.Repeat(" ", repeat) + v } +func findPassThrough(line string) []int { + loc := passThroughBeginRegex.FindStringIndex(line) + if loc == nil { + return nil + } + + rest := line[loc[0]:] + after := line[loc[1]:] + if strings.HasPrefix(rest, "\x1bPtmux") { // Tmux + eloc := passThroughEndTmuxRegex.FindStringIndex(after) + if eloc == nil { + return nil + } + return []int{loc[0], loc[1] + eloc[1]} + } else if strings.HasPrefix(rest, "\x1b]1337;") { // iTerm2 + index := loc[1] + for { + after := line[index:] + pos := strings.IndexAny(after, "\x1b\a") + if pos < 0 { + return nil + } + if after[pos] == '\a' { + return []int{loc[0], index + pos + 1} + } + if pos < len(after)-1 && after[pos+1] == '\\' { + return []int{loc[0], index + pos + 2} + } + index += pos + 1 + } + } + // Kitty + pos := strings.Index(after, "\x1b\\") + if pos < 0 { + return nil + } + if pos < len(after)-2 && after[pos+2] == '\r' { + return []int{loc[0], loc[1] + pos + 3} + } + return []int{loc[0], loc[1] + pos + 2} +} + +func extractPassThroughs(line string) ([]string, string) { + passThroughs := []string{} + transformed := "" + index := 0 + for { + rest := line[index:] + loc := findPassThrough(rest) + if loc == nil { + transformed += rest + break + } + passThroughs = append(passThroughs, rest[loc[0]:loc[1]]) + transformed += line[index : index+loc[0]] + index += loc[1] + } + + return passThroughs, transformed +} + func (t *Terminal) renderPreviewText(height int, lines []string, lineNo int, unchanged bool) { maxWidth := t.pwindow.Width() var ansi *ansiState @@ -2636,10 +2706,7 @@ Loop: ansi.lbg = -1 } - passThroughs := passThroughRegex.FindAllString(line, -1) - if passThroughs != nil { - line = passThroughRegex.ReplaceAllString(line, "") - } + passThroughs, line := extractPassThroughs(line) line = strings.TrimLeft(strings.TrimRight(line, "\r\n"), "\r") if lineNo >= height || t.pwindow.Y() == height-1 && t.pwindow.X() > 0 { diff --git a/src/terminal_test.go b/src/terminal_test.go index 317cd5f..44da2bc 100644 --- a/src/terminal_test.go +++ b/src/terminal_test.go @@ -507,6 +507,34 @@ func TestParsePlaceholder(t *testing.T) { } } +func TestExtractPassthroughs(t *testing.T) { + for _, middle := range []string{ + "\x1bPtmux;\x1b\x1bbar\x1b\\", + "\x1bPtmux;\x1b\x1bbar\x1bbar\x1b\\", + "\x1b]1337;bar\x1b\\", + "\x1b]1337;bar\x1bbar\x1b\\", + "\x1b]1337;bar\a", + "\x1b_Ga=T,f=32,s=1258,v=1295,c=74,r=35,m=1\x1b\\", + "\x1b_Ga=T,f=32,s=1258,v=1295,c=74,r=35,m=1\x1b\\\r", + "\x1b_Ga=T,f=32,s=1258,v=1295,c=74,r=35,m=1\x1bbar\x1b\\\r", + "\x1b_Gm=1;AAAAAAAAA=\x1b\\", + "\x1b_Gm=1;AAAAAAAAA=\x1b\\\r", + "\x1b_Gm=1;\x1bAAAAAAAAA=\x1b\\\r", + } { + line := "foo" + middle + "baz" + loc := findPassThrough(line) + if loc == nil || line[0:loc[0]] != "foo" || line[loc[1]:] != "baz" { + t.Error("failed to find passthrough") + } + garbage := "\x1bPtmux;\x1b]1337;\x1b_Ga=\x1b]1337;bar\x1b." + line = strings.Repeat("foo"+middle+middle+"baz", 3) + garbage + passthroughs, result := extractPassThroughs(line) + if result != "foobazfoobazfoobaz"+garbage || len(passthroughs) != 6 { + t.Error("failed to extract passthroughs") + } + } +} + /* utilities section */ // Item represents one line in fzf UI. Usually it is relative path to files and folders.