Refactor the code to remove global variables

This commit is contained in:
Junegunn Choi 2024-05-07 16:58:17 +09:00
parent c5fb0c43f9
commit 4bedd33c59
No known key found for this signature in database
GPG Key ID: 254BC280FEF9C627
7 changed files with 85 additions and 76 deletions

View File

@ -312,7 +312,7 @@ func parseAnsiCode(s string, delimiter byte) (int, byte, string) {
// Inlined version of strconv.Atoi() that only handles positive // Inlined version of strconv.Atoi() that only handles positive
// integers and does not allocate on error. // integers and does not allocate on error.
code := 0 code := 0
for _, ch := range sbytes(s) { for _, ch := range stringBytes(s) {
ch -= '0' ch -= '0'
if ch > 9 { if ch > 9 {
return -1, delimiter, remaining return -1, delimiter, remaining

View File

@ -4,7 +4,6 @@ package fzf
import ( import (
"sync" "sync"
"time" "time"
"unsafe"
"github.com/junegunn/fzf/src/util" "github.com/junegunn/fzf/src/util"
) )
@ -18,19 +17,6 @@ Matcher -> EvtSearchFin -> Terminal (update list)
Matcher -> EvtHeader -> Terminal (update header) Matcher -> EvtHeader -> Terminal (update header)
*/ */
func ustring(data []byte) string {
return unsafe.String(unsafe.SliceData(data), len(data))
}
func sbytes(data string) []byte {
return unsafe.Slice(unsafe.StringData(data), len(data))
}
type quitSignal struct {
code int
err error
}
// Run starts fzf // Run starts fzf
func Run(opts *Options) (int, error) { func Run(opts *Options) (int, error) {
if err := postProcessOptions(opts); err != nil { if err := postProcessOptions(opts); err != nil {
@ -62,16 +48,16 @@ func Run(opts *Options) (int, error) {
if opts.Theme.Colored { if opts.Theme.Colored {
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) { ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
prevLineAnsiState = lineAnsiState prevLineAnsiState = lineAnsiState
trimmed, offsets, newState := extractColor(ustring(data), lineAnsiState, nil) trimmed, offsets, newState := extractColor(byteString(data), lineAnsiState, nil)
lineAnsiState = newState lineAnsiState = newState
return util.ToChars(sbytes(trimmed)), offsets return util.ToChars(stringBytes(trimmed)), offsets
} }
} else { } else {
// When color is disabled but ansi option is given, // When color is disabled but ansi option is given,
// we simply strip out ANSI codes from the input // we simply strip out ANSI codes from the input
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) { ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
trimmed, _, _ := extractColor(ustring(data), nil, nil) trimmed, _, _ := extractColor(byteString(data), nil, nil)
return util.ToChars(sbytes(trimmed)), nil return util.ToChars(stringBytes(trimmed)), nil
} }
} }
} }
@ -83,7 +69,7 @@ func Run(opts *Options) (int, error) {
if len(opts.WithNth) == 0 { if len(opts.WithNth) == 0 {
chunkList = NewChunkList(func(item *Item, data []byte) bool { chunkList = NewChunkList(func(item *Item, data []byte) bool {
if len(header) < opts.HeaderLines { if len(header) < opts.HeaderLines {
header = append(header, ustring(data)) header = append(header, byteString(data))
eventBox.Set(EvtHeader, header) eventBox.Set(EvtHeader, header)
return false return false
} }
@ -94,7 +80,7 @@ func Run(opts *Options) (int, error) {
}) })
} else { } else {
chunkList = NewChunkList(func(item *Item, data []byte) bool { chunkList = NewChunkList(func(item *Item, data []byte) bool {
tokens := Tokenize(ustring(data), opts.Delimiter) tokens := Tokenize(byteString(data), opts.Delimiter)
if opts.Ansi && opts.Theme.Colored && len(tokens) > 1 { if opts.Ansi && opts.Theme.Colored && len(tokens) > 1 {
var ansiState *ansiState var ansiState *ansiState
if prevLineAnsiState != nil { if prevLineAnsiState != nil {
@ -118,7 +104,7 @@ func Run(opts *Options) (int, error) {
eventBox.Set(EvtHeader, header) eventBox.Set(EvtHeader, header)
return false return false
} }
item.text, item.colors = ansiProcessor(sbytes(transformed)) item.text, item.colors = ansiProcessor(stringBytes(transformed))
item.text.TrimTrailingWhitespaces() item.text.TrimTrailingWhitespaces()
item.text.Index = itemIndex item.text.Index = itemIndex
item.origText = &data item.origText = &data
@ -241,7 +227,7 @@ func Run(opts *Options) (int, error) {
// Event coordination // Event coordination
reading := true reading := true
ticks := 0 ticks := 0
var nextCommand *string var nextCommand *commandSpec
var nextEnviron []string var nextEnviron []string
eventBox.Watch(EvtReadNew) eventBox.Watch(EvtReadNew)
total := 0 total := 0
@ -262,7 +248,7 @@ func Run(opts *Options) (int, error) {
useSnapshot := false useSnapshot := false
var snapshot []*Chunk var snapshot []*Chunk
var count int var count int
restart := func(command string, environ []string) { restart := func(command commandSpec, environ []string) {
reading = true reading = true
chunkList.Clear() chunkList.Clear()
itemIndex = 0 itemIndex = 0
@ -328,7 +314,7 @@ func Run(opts *Options) (int, error) {
matcher.Reset(snapshot, input(), false, !reading, sort, snapshotRevision) matcher.Reset(snapshot, input(), false, !reading, sort, snapshotRevision)
case EvtSearchNew: case EvtSearchNew:
var command *string var command *commandSpec
var environ []string var environ []string
var changed bool var changed bool
switch val := value.(type) { switch val := value.(type) {

35
src/functions.go Normal file
View File

@ -0,0 +1,35 @@
package fzf
import (
"os"
"strings"
"unsafe"
)
func writeTemporaryFile(data []string, printSep string) string {
f, err := os.CreateTemp("", "fzf-preview-*")
if err != nil {
// Unable to create temporary file
// FIXME: Should we terminate the program?
return ""
}
defer f.Close()
f.WriteString(strings.Join(data, printSep))
f.WriteString(printSep)
return f.Name()
}
func removeFiles(files []string) {
for _, filename := range files {
os.Remove(filename)
}
}
func stringBytes(data string) []byte {
return unsafe.Slice(unsafe.StringData(data), len(data))
}
func byteString(data []byte) string {
return unsafe.String(unsafe.SliceData(data), len(data))
}

View File

@ -86,11 +86,12 @@ func (r *Reader) terminate() {
r.mutex.Unlock() r.mutex.Unlock()
} }
func (r *Reader) restart(command string, environ []string) { func (r *Reader) restart(command commandSpec, environ []string) {
r.event = int32(EvtReady) r.event = int32(EvtReady)
r.startEventPoller() r.startEventPoller()
success := r.readFromCommand(command, environ) success := r.readFromCommand(command.command, environ)
r.fin(success) r.fin(success)
removeFiles(command.tempFiles)
} }
func (r *Reader) readChannel(inputChan chan string) bool { func (r *Reader) readChannel(inputChan chan string) bool {

View File

@ -50,8 +50,6 @@ var placeholder *regexp.Regexp
var whiteSuffix *regexp.Regexp var whiteSuffix *regexp.Regexp
var offsetComponentRegex *regexp.Regexp var offsetComponentRegex *regexp.Regexp
var offsetTrimCharsRegex *regexp.Regexp var offsetTrimCharsRegex *regexp.Regexp
var activeTempFiles []string
var activeTempFilesMutex sync.Mutex
var passThroughRegex *regexp.Regexp var passThroughRegex *regexp.Regexp
const clearCode string = "\x1b[2J" const clearCode string = "\x1b[2J"
@ -64,8 +62,6 @@ func init() {
whiteSuffix = regexp.MustCompile(`\s*$`) whiteSuffix = regexp.MustCompile(`\s*$`)
offsetComponentRegex = regexp.MustCompile(`([+-][0-9]+)|(-?/[1-9][0-9]*)`) offsetComponentRegex = regexp.MustCompile(`([+-][0-9]+)|(-?/[1-9][0-9]*)`)
offsetTrimCharsRegex = regexp.MustCompile(`[^0-9/+-]`) offsetTrimCharsRegex = regexp.MustCompile(`[^0-9/+-]`)
activeTempFiles = []string{}
activeTempFilesMutex = sync.Mutex{}
// Parts of the preview output that should be passed through to the terminal // Parts of the preview output that should be passed through to the terminal
// * https://github.com/tmux/tmux/wiki/FAQ#what-is-the-passthrough-escape-sequence-and-how-do-i-use-it // * https://github.com/tmux/tmux/wiki/FAQ#what-is-the-passthrough-escape-sequence-and-how-do-i-use-it
@ -115,6 +111,16 @@ func (s *resumableState) Set(flag bool) {
} }
} }
type commandSpec struct {
command string
tempFiles []string
}
type quitSignal struct {
code int
err error
}
type previewer struct { type previewer struct {
version int64 version int64
lines []string lines []string
@ -507,7 +513,7 @@ type placeholderFlags struct {
type searchRequest struct { type searchRequest struct {
sort bool sort bool
sync bool sync bool
command *string command *commandSpec
environ []string environ []string
changed bool changed bool
} }
@ -2530,32 +2536,6 @@ func hasPreviewFlags(template string) (slot bool, plus bool, forceUpdate bool) {
return return
} }
func writeTemporaryFile(data []string, printSep string) string {
f, err := os.CreateTemp("", "fzf-preview-*")
if err != nil {
// Unable to create temporary file
// FIXME: Should we terminate the program?
return ""
}
defer f.Close()
f.WriteString(strings.Join(data, printSep))
f.WriteString(printSep)
activeTempFilesMutex.Lock()
activeTempFiles = append(activeTempFiles, f.Name())
activeTempFilesMutex.Unlock()
return f.Name()
}
func cleanTemporaryFiles() {
activeTempFilesMutex.Lock()
for _, filename := range activeTempFiles {
os.Remove(filename)
}
activeTempFiles = []string{}
activeTempFilesMutex.Unlock()
}
type replacePlaceholderParams struct { type replacePlaceholderParams struct {
template string template string
stripAnsi bool stripAnsi bool
@ -2569,7 +2549,7 @@ type replacePlaceholderParams struct {
executor *util.Executor executor *util.Executor
} }
func (t *Terminal) replacePlaceholder(template string, forcePlus bool, input string, list []*Item) string { func (t *Terminal) replacePlaceholder(template string, forcePlus bool, input string, list []*Item) (string, []string) {
return replacePlaceholder(replacePlaceholderParams{ return replacePlaceholder(replacePlaceholderParams{
template: template, template: template,
stripAnsi: t.ansi, stripAnsi: t.ansi,
@ -2590,8 +2570,9 @@ func (t *Terminal) evaluateScrollOffset() int {
} }
// We only need the current item to calculate the scroll offset // We only need the current item to calculate the scroll offset
offsetExpr := offsetTrimCharsRegex.ReplaceAllString( replaced, tempFiles := t.replacePlaceholder(t.previewOpts.scroll, false, "", []*Item{t.currentItem(), nil})
t.replacePlaceholder(t.previewOpts.scroll, false, "", []*Item{t.currentItem(), nil}), "") removeFiles(tempFiles)
offsetExpr := offsetTrimCharsRegex.ReplaceAllString(replaced, "")
atoi := func(s string) int { atoi := func(s string) int {
n, e := strconv.Atoi(s) n, e := strconv.Atoi(s)
@ -2619,7 +2600,8 @@ func (t *Terminal) evaluateScrollOffset() int {
return util.Max(0, base) return util.Max(0, base)
} }
func replacePlaceholder(params replacePlaceholderParams) string { func replacePlaceholder(params replacePlaceholderParams) (string, []string) {
tempFiles := []string{}
current := params.allItems[:1] current := params.allItems[:1]
selected := params.allItems[1:] selected := params.allItems[1:]
if current[0] == nil { if current[0] == nil {
@ -2630,7 +2612,7 @@ func replacePlaceholder(params replacePlaceholderParams) string {
} }
// replace placeholders one by one // replace placeholders one by one
return placeholder.ReplaceAllStringFunc(params.template, func(match string) string { replaced := placeholder.ReplaceAllStringFunc(params.template, func(match string) string {
escaped, match, flags := parsePlaceholder(match) escaped, match, flags := parsePlaceholder(match)
// this function implements the effects a placeholder has on items // this function implements the effects a placeholder has on items
@ -2713,10 +2695,14 @@ func replacePlaceholder(params replacePlaceholderParams) string {
} }
if flags.file { if flags.file {
return writeTemporaryFile(replacements, params.printsep) file := writeTemporaryFile(replacements, params.printsep)
tempFiles = append(tempFiles, file)
return file
} }
return strings.Join(replacements, " ") return strings.Join(replacements, " ")
}) })
return replaced, tempFiles
} }
func (t *Terminal) redraw() { func (t *Terminal) redraw() {
@ -2733,7 +2719,7 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
if !valid && !capture { if !valid && !capture {
return line return line
} }
command := t.replacePlaceholder(template, forcePlus, string(t.input), list) command, tempFiles := t.replacePlaceholder(template, forcePlus, string(t.input), list)
cmd := t.executor.ExecCommand(command, false) cmd := t.executor.ExecCommand(command, false)
cmd.Env = t.environ() cmd.Env = t.environ()
t.executing.Set(true) t.executing.Set(true)
@ -2764,7 +2750,7 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
} }
} }
t.executing.Set(false) t.executing.Set(false)
cleanTemporaryFiles() removeFiles(tempFiles)
return line return line
} }
@ -3042,7 +3028,7 @@ func (t *Terminal) Loop() error {
// We don't display preview window if no match // We don't display preview window if no match
if items[0] != nil { if items[0] != nil {
_, query := t.Input() _, query := t.Input()
command := t.replacePlaceholder(commandTemplate, false, string(query), items) command, tempFiles := t.replacePlaceholder(commandTemplate, false, string(query), items)
cmd := t.executor.ExecCommand(command, true) cmd := t.executor.ExecCommand(command, true)
if pwindowSize.Lines > 0 { if pwindowSize.Lines > 0 {
lines := fmt.Sprintf("LINES=%d", pwindowSize.Lines) lines := fmt.Sprintf("LINES=%d", pwindowSize.Lines)
@ -3164,12 +3150,11 @@ func (t *Terminal) Loop() error {
finishChan <- true // Tell Goroutine 3 to stop finishChan <- true // Tell Goroutine 3 to stop
<-reapChan // Goroutine 2 and 3 finished <-reapChan // Goroutine 2 and 3 finished
<-reapChan <-reapChan
removeFiles(tempFiles)
} else { } else {
// Failed to start the command. Report the error immediately. // Failed to start the command. Report the error immediately.
t.reqBox.Set(reqPreviewDisplay, previewResult{version, []string{err.Error()}, 0, ""}) t.reqBox.Set(reqPreviewDisplay, previewResult{version, []string{err.Error()}, 0, ""})
} }
cleanTemporaryFiles()
} else { } else {
t.reqBox.Set(reqPreviewDisplay, previewResult{version, nil, 0, ""}) t.reqBox.Set(reqPreviewDisplay, previewResult{version, nil, 0, ""})
} }
@ -3343,7 +3328,7 @@ func (t *Terminal) Loop() error {
pbarDragging := false pbarDragging := false
wasDown := false wasDown := false
for looping { for looping {
var newCommand *string var newCommand *commandSpec
var reloadSync bool var reloadSync bool
changed := false changed := false
beof := false beof := false
@ -3468,7 +3453,8 @@ func (t *Terminal) Loop() error {
case actBecome: case actBecome:
valid, list := t.buildPlusList(a.a, false) valid, list := t.buildPlusList(a.a, false)
if valid { if valid {
command := t.replacePlaceholder(a.a, false, string(t.input), list) // We do not remove temp files in this case
command, _ := t.replacePlaceholder(a.a, false, string(t.input), list)
t.tui.Close() t.tui.Close()
if t.history != nil { if t.history != nil {
t.history.append(string(t.input)) t.history.append(string(t.input))
@ -4140,8 +4126,8 @@ func (t *Terminal) Loop() error {
valid = !slot || forceUpdate valid = !slot || forceUpdate
} }
if valid { if valid {
command := t.replacePlaceholder(a.a, false, string(t.input), list) command, tempFiles := t.replacePlaceholder(a.a, false, string(t.input), list)
newCommand = &command newCommand = &commandSpec{command, tempFiles}
reloadSync = a.t == actReloadSync reloadSync = a.t == actReloadSync
t.reading = true t.reading = true
} }

View File

@ -13,7 +13,7 @@ import (
) )
func replacePlaceholderTest(template string, stripAnsi bool, delimiter Delimiter, printsep string, forcePlus bool, query string, allItems []*Item) string { func replacePlaceholderTest(template string, stripAnsi bool, delimiter Delimiter, printsep string, forcePlus bool, query string, allItems []*Item) string {
return replacePlaceholder(replacePlaceholderParams{ replaced, _ := replacePlaceholder(replacePlaceholderParams{
template: template, template: template,
stripAnsi: stripAnsi, stripAnsi: stripAnsi,
delimiter: delimiter, delimiter: delimiter,
@ -25,6 +25,7 @@ func replacePlaceholderTest(template string, stripAnsi bool, delimiter Delimiter
prompt: "prompt", prompt: "prompt",
executor: util.NewExecutor(""), executor: util.NewExecutor(""),
}) })
return replaced
} }
func TestReplacePlaceholder(t *testing.T) { func TestReplacePlaceholder(t *testing.T) {

View File

@ -91,7 +91,7 @@ func withPrefixLengths(tokens []string, begin int) []Token {
prefixLength := begin prefixLength := begin
for idx := range tokens { for idx := range tokens {
chars := util.ToChars(sbytes(tokens[idx])) chars := util.ToChars(stringBytes(tokens[idx]))
ret[idx] = Token{&chars, int32(prefixLength)} ret[idx] = Token{&chars, int32(prefixLength)}
prefixLength += chars.Length() prefixLength += chars.Length()
} }
@ -187,7 +187,7 @@ func Transform(tokens []Token, withNth []Range) []Token {
if r.begin == r.end { if r.begin == r.end {
idx := r.begin idx := r.begin
if idx == rangeEllipsis { if idx == rangeEllipsis {
chars := util.ToChars(sbytes(joinTokens(tokens))) chars := util.ToChars(stringBytes(joinTokens(tokens)))
parts = append(parts, &chars) parts = append(parts, &chars)
} else { } else {
if idx < 0 { if idx < 0 {