Much faster image processing

Fix #3984
This commit is contained in:
Junegunn Choi 2024-11-29 00:26:12 +09:00
parent 61d10d8ffa
commit 3b0c86e401
No known key found for this signature in database
GPG Key ID: 254BC280FEF9C627
3 changed files with 102 additions and 6 deletions

View File

@ -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

View File

@ -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 {

View File

@ -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.