Refactor the code so that fzf can be used as a library

pull/3769/head
Junegunn Choi 4 weeks ago
parent 065b9e6fb2
commit d2554e2dbe
No known key found for this signature in database
GPG Key ID: 254BC280FEF9C627

@ -3,10 +3,12 @@ package main
import ( import (
_ "embed" _ "embed"
"fmt" "fmt"
"os"
"strings" "strings"
fzf "github.com/junegunn/fzf/src" fzf "github.com/junegunn/fzf/src"
"github.com/junegunn/fzf/src/protector" "github.com/junegunn/fzf/src/protector"
"github.com/junegunn/fzf/src/util"
) )
var version string = "0.51" var version string = "0.51"
@ -33,9 +35,19 @@ func printScript(label string, content []byte) {
fmt.Println("### end: " + label + " ###") fmt.Println("### end: " + label + " ###")
} }
func errorExit(msg string) {
os.Stderr.WriteString(msg + "\n")
os.Exit(fzf.ExitError)
}
func main() { func main() {
protector.Protect() protector.Protect()
options := fzf.ParseOptions()
options, err := fzf.ParseOptions(true, os.Args[1:])
if err != nil {
errorExit(err.Error())
return
}
if options.Bash { if options.Bash {
printScript("key-bindings.bash", bashKeyBindings) printScript("key-bindings.bash", bashKeyBindings)
printScript("completion.bash", bashCompletion) printScript("completion.bash", bashCompletion)
@ -51,5 +63,9 @@ func main() {
fmt.Println("fzf_key_bindings") fmt.Println("fzf_key_bindings")
return return
} }
fzf.Run(options, version, revision) code, err := fzf.Run(options, version, revision)
if err != nil {
os.Stderr.WriteString(err.Error() + "\n")
}
util.Exit(code)
} }

@ -143,6 +143,7 @@ func TestOSExitNotAllowed(t *testing.T) {
t.Skip("skipping: short test") t.Skip("skipping: short test")
} }
allowed := map[string]int{ allowed := map[string]int{
"main.go": 1, // os.Exit allowed 1 time in "main.go"
"src/util/atexit.go": 1, // os.Exit allowed 1 time in "atexit.go" "src/util/atexit.go": 1, // os.Exit allowed 1 time in "atexit.go"
} }
var errOsExit bool var errOsExit bool

@ -37,92 +37,93 @@ func _() {
_ = x[actDeleteChar-26] _ = x[actDeleteChar-26]
_ = x[actDeleteCharEof-27] _ = x[actDeleteCharEof-27]
_ = x[actEndOfLine-28] _ = x[actEndOfLine-28]
_ = x[actForwardChar-29] _ = x[actFatal-29]
_ = x[actForwardWord-30] _ = x[actForwardChar-30]
_ = x[actKillLine-31] _ = x[actForwardWord-31]
_ = x[actKillWord-32] _ = x[actKillLine-32]
_ = x[actUnixLineDiscard-33] _ = x[actKillWord-33]
_ = x[actUnixWordRubout-34] _ = x[actUnixLineDiscard-34]
_ = x[actYank-35] _ = x[actUnixWordRubout-35]
_ = x[actBackwardKillWord-36] _ = x[actYank-36]
_ = x[actSelectAll-37] _ = x[actBackwardKillWord-37]
_ = x[actDeselectAll-38] _ = x[actSelectAll-38]
_ = x[actToggle-39] _ = x[actDeselectAll-39]
_ = x[actToggleSearch-40] _ = x[actToggle-40]
_ = x[actToggleAll-41] _ = x[actToggleSearch-41]
_ = x[actToggleDown-42] _ = x[actToggleAll-42]
_ = x[actToggleUp-43] _ = x[actToggleDown-43]
_ = x[actToggleIn-44] _ = x[actToggleUp-44]
_ = x[actToggleOut-45] _ = x[actToggleIn-45]
_ = x[actToggleTrack-46] _ = x[actToggleOut-46]
_ = x[actToggleTrackCurrent-47] _ = x[actToggleTrack-47]
_ = x[actToggleHeader-48] _ = x[actToggleTrackCurrent-48]
_ = x[actTrackCurrent-49] _ = x[actToggleHeader-49]
_ = x[actUntrackCurrent-50] _ = x[actTrackCurrent-50]
_ = x[actDown-51] _ = x[actUntrackCurrent-51]
_ = x[actUp-52] _ = x[actDown-52]
_ = x[actPageUp-53] _ = x[actUp-53]
_ = x[actPageDown-54] _ = x[actPageUp-54]
_ = x[actPosition-55] _ = x[actPageDown-55]
_ = x[actHalfPageUp-56] _ = x[actPosition-56]
_ = x[actHalfPageDown-57] _ = x[actHalfPageUp-57]
_ = x[actOffsetUp-58] _ = x[actHalfPageDown-58]
_ = x[actOffsetDown-59] _ = x[actOffsetUp-59]
_ = x[actJump-60] _ = x[actOffsetDown-60]
_ = x[actJumpAccept-61] _ = x[actJump-61]
_ = x[actPrintQuery-62] _ = x[actJumpAccept-62]
_ = x[actRefreshPreview-63] _ = x[actPrintQuery-63]
_ = x[actReplaceQuery-64] _ = x[actRefreshPreview-64]
_ = x[actToggleSort-65] _ = x[actReplaceQuery-65]
_ = x[actShowPreview-66] _ = x[actToggleSort-66]
_ = x[actHidePreview-67] _ = x[actShowPreview-67]
_ = x[actTogglePreview-68] _ = x[actHidePreview-68]
_ = x[actTogglePreviewWrap-69] _ = x[actTogglePreview-69]
_ = x[actTransform-70] _ = x[actTogglePreviewWrap-70]
_ = x[actTransformBorderLabel-71] _ = x[actTransform-71]
_ = x[actTransformHeader-72] _ = x[actTransformBorderLabel-72]
_ = x[actTransformPreviewLabel-73] _ = x[actTransformHeader-73]
_ = x[actTransformPrompt-74] _ = x[actTransformPreviewLabel-74]
_ = x[actTransformQuery-75] _ = x[actTransformPrompt-75]
_ = x[actPreview-76] _ = x[actTransformQuery-76]
_ = x[actChangePreview-77] _ = x[actPreview-77]
_ = x[actChangePreviewWindow-78] _ = x[actChangePreview-78]
_ = x[actPreviewTop-79] _ = x[actChangePreviewWindow-79]
_ = x[actPreviewBottom-80] _ = x[actPreviewTop-80]
_ = x[actPreviewUp-81] _ = x[actPreviewBottom-81]
_ = x[actPreviewDown-82] _ = x[actPreviewUp-82]
_ = x[actPreviewPageUp-83] _ = x[actPreviewDown-83]
_ = x[actPreviewPageDown-84] _ = x[actPreviewPageUp-84]
_ = x[actPreviewHalfPageUp-85] _ = x[actPreviewPageDown-85]
_ = x[actPreviewHalfPageDown-86] _ = x[actPreviewHalfPageUp-86]
_ = x[actPrevHistory-87] _ = x[actPreviewHalfPageDown-87]
_ = x[actPrevSelected-88] _ = x[actPrevHistory-88]
_ = x[actPut-89] _ = x[actPrevSelected-89]
_ = x[actNextHistory-90] _ = x[actPut-90]
_ = x[actNextSelected-91] _ = x[actNextHistory-91]
_ = x[actExecute-92] _ = x[actNextSelected-92]
_ = x[actExecuteSilent-93] _ = x[actExecute-93]
_ = x[actExecuteMulti-94] _ = x[actExecuteSilent-94]
_ = x[actSigStop-95] _ = x[actExecuteMulti-95]
_ = x[actFirst-96] _ = x[actSigStop-96]
_ = x[actLast-97] _ = x[actFirst-97]
_ = x[actReload-98] _ = x[actLast-98]
_ = x[actReloadSync-99] _ = x[actReload-99]
_ = x[actDisableSearch-100] _ = x[actReloadSync-100]
_ = x[actEnableSearch-101] _ = x[actDisableSearch-101]
_ = x[actSelect-102] _ = x[actEnableSearch-102]
_ = x[actDeselect-103] _ = x[actSelect-103]
_ = x[actUnbind-104] _ = x[actDeselect-104]
_ = x[actRebind-105] _ = x[actUnbind-105]
_ = x[actBecome-106] _ = x[actRebind-106]
_ = x[actResponse-107] _ = x[actBecome-107]
_ = x[actShowHeader-108] _ = x[actResponse-108]
_ = x[actHideHeader-109] _ = x[actShowHeader-109]
_ = x[actHideHeader-110]
} }
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactResponseactShowHeaderactHideHeader" const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactResponseactShowHeaderactHideHeader"
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 242, 256, 277, 292, 306, 320, 333, 350, 358, 371, 387, 399, 413, 427, 438, 449, 467, 484, 491, 510, 522, 536, 545, 560, 572, 585, 596, 607, 619, 633, 654, 669, 684, 701, 708, 713, 722, 733, 744, 757, 772, 783, 796, 803, 816, 829, 846, 861, 874, 888, 902, 918, 938, 950, 973, 991, 1015, 1033, 1050, 1060, 1076, 1098, 1111, 1127, 1139, 1153, 1169, 1187, 1207, 1229, 1243, 1258, 1264, 1278, 1293, 1303, 1319, 1334, 1344, 1352, 1359, 1368, 1381, 1397, 1412, 1421, 1432, 1441, 1450, 1459, 1470, 1483, 1496} var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 242, 256, 277, 292, 306, 320, 333, 350, 358, 371, 387, 399, 407, 421, 435, 446, 457, 475, 492, 499, 518, 530, 544, 553, 568, 580, 593, 604, 615, 627, 641, 662, 677, 692, 709, 716, 721, 730, 741, 752, 765, 780, 791, 804, 811, 824, 837, 854, 869, 882, 896, 910, 926, 946, 958, 981, 999, 1023, 1041, 1058, 1068, 1084, 1106, 1119, 1135, 1147, 1161, 1177, 1195, 1215, 1237, 1251, 1266, 1272, 1286, 1301, 1311, 1327, 1342, 1352, 1360, 1367, 1376, 1389, 1405, 1420, 1429, 1440, 1449, 1458, 1467, 1478, 1491, 1504}
func (i actionType) String() string { func (i actionType) String() string {
if i < 0 || i >= actionType(len(_actionType_index)-1) { if i < 0 || i >= actionType(len(_actionType_index)-1) {

@ -67,9 +67,9 @@ const (
) )
const ( const (
exitCancel = -1 ExitCancel = -1
exitOk = 0 ExitOk = 0
exitNoMatch = 1 ExitNoMatch = 1
exitError = 2 ExitError = 2
exitInterrupt = 130 ExitInterrupt = 130
) )

@ -28,7 +28,7 @@ func sbytes(data string) []byte {
} }
// Run starts fzf // Run starts fzf
func Run(opts *Options, version string, revision string) { func Run(opts *Options, version string, revision string) (int, error) {
defer util.RunAtExitFuncs() defer util.RunAtExitFuncs()
sort := opts.Sort > 0 sort := opts.Sort > 0
@ -40,7 +40,7 @@ func Run(opts *Options, version string, revision string) {
} else { } else {
fmt.Println(version) fmt.Println(version)
} }
util.Exit(exitOk) return ExitOk, nil
} }
// Event channel // Event channel
@ -197,9 +197,9 @@ func Run(opts *Options, version string, revision string) {
} }
} }
if found { if found {
util.Exit(exitOk) return ExitOk, nil
} }
util.Exit(exitNoMatch) return ExitNoMatch, nil
} }
// Synchronous search // Synchronous search
@ -210,9 +210,13 @@ func Run(opts *Options, version string, revision string) {
// Go interactive // Go interactive
go matcher.Loop() go matcher.Loop()
defer matcher.Stop()
// Terminal I/O // Terminal I/O
terminal := NewTerminal(opts, eventBox, executor) terminal, err := NewTerminal(opts, eventBox, executor)
if err != nil {
return ExitError, err
}
maxFit := 0 // Maximum number of items that can fit on screen maxFit := 0 // Maximum number of items that can fit on screen
padHeight := 0 padHeight := 0
heightUnknown := opts.Height.auto heightUnknown := opts.Height.auto
@ -258,7 +262,10 @@ func Run(opts *Options, version string, revision string) {
header = make([]string, 0, opts.HeaderLines) header = make([]string, 0, opts.HeaderLines)
go reader.restart(command, environ) go reader.restart(command, environ)
} }
for {
exitCode := ExitOk
running := true
for running {
delay := true delay := true
ticks++ ticks++
input := func() []rune { input := func() []rune {
@ -278,7 +285,9 @@ func Run(opts *Options, version string, revision string) {
if reading { if reading {
reader.terminate() reader.terminate()
} }
util.Exit(value.(int)) exitCode = value.(int)
running = false
return
case EvtReadNew, EvtReadFin: case EvtReadNew, EvtReadFin:
if evt == EvtReadFin && nextCommand != nil { if evt == EvtReadFin && nextCommand != nil {
restart(*nextCommand, nextEnviron) restart(*nextCommand, nextEnviron)
@ -378,10 +387,11 @@ func Run(opts *Options, version string, revision string) {
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
opts.Printer(val.Get(i).item.AsString(opts.Ansi)) opts.Printer(val.Get(i).item.AsString(opts.Ansi))
} }
if count > 0 { if count == 0 {
util.Exit(exitOk) exitCode = ExitNoMatch
} }
util.Exit(exitNoMatch) running = false
return
} }
determine(val.final) determine(val.final)
} }
@ -399,4 +409,5 @@ func Run(opts *Options, version string, revision string) {
time.Sleep(dur) time.Sleep(dur)
} }
} }
return exitCode, nil
} }

@ -35,6 +35,7 @@ type Matcher struct {
const ( const (
reqRetry util.EventType = iota reqRetry util.EventType = iota
reqReset reqReset
reqStop
) )
// NewMatcher returns a new Matcher // NewMatcher returns a new Matcher
@ -60,8 +61,13 @@ func (m *Matcher) Loop() {
for { for {
var request MatchRequest var request MatchRequest
stop := false
m.reqBox.Wait(func(events *util.Events) { m.reqBox.Wait(func(events *util.Events) {
for _, val := range *events { for t, val := range *events {
if t == reqStop {
stop = true
return
}
switch val := val.(type) { switch val := val.(type) {
case MatchRequest: case MatchRequest:
request = val request = val
@ -71,6 +77,9 @@ func (m *Matcher) Loop() {
} }
events.Clear() events.Clear()
}) })
if stop {
break
}
if request.sort != m.sort || request.revision != m.revision { if request.sort != m.sort || request.revision != m.revision {
m.sort = request.sort m.sort = request.sort
@ -236,3 +245,7 @@ func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final
} }
m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort && pattern.sortable, revision}) m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort && pattern.sortable, revision})
} }
func (m *Matcher) Stop() {
m.reqBox.Set(reqStop, MatchRequest{})
}

File diff suppressed because it is too large Load Diff

@ -3,9 +3,11 @@
package fzf package fzf
import "errors"
func (o *Options) initProfiling() error { func (o *Options) initProfiling() error {
if o.CPUProfile != "" || o.MEMProfile != "" || o.BlockProfile != "" || o.MutexProfile != "" { if o.CPUProfile != "" || o.MEMProfile != "" || o.BlockProfile != "" || o.MutexProfile != "" {
errorExit("error: profiling not supported: FZF must be built with '-tags=pprof' to enable profiling") return errors.New("error: profiling not supported: FZF must be built with '-tags=pprof' to enable profiling")
} }
return nil return nil
} }

@ -80,7 +80,7 @@ func TestDelimiterRegexRegexCaret(t *testing.T) {
func TestSplitNth(t *testing.T) { func TestSplitNth(t *testing.T) {
{ {
ranges := splitNth("..") ranges, _ := splitNth("..")
if len(ranges) != 1 || if len(ranges) != 1 ||
ranges[0].begin != rangeEllipsis || ranges[0].begin != rangeEllipsis ||
ranges[0].end != rangeEllipsis { ranges[0].end != rangeEllipsis {
@ -88,7 +88,7 @@ func TestSplitNth(t *testing.T) {
} }
} }
{ {
ranges := splitNth("..3,1..,2..3,4..-1,-3..-2,..,2,-2,2..-2,1..-1") ranges, _ := splitNth("..3,1..,2..3,4..-1,-3..-2,..,2,-2,2..-2,1..-1")
if len(ranges) != 10 || if len(ranges) != 10 ||
ranges[0].begin != rangeEllipsis || ranges[0].end != 3 || ranges[0].begin != rangeEllipsis || ranges[0].end != 3 ||
ranges[1].begin != rangeEllipsis || ranges[1].end != rangeEllipsis || ranges[1].begin != rangeEllipsis || ranges[1].end != rangeEllipsis ||
@ -137,7 +137,7 @@ func TestIrrelevantNth(t *testing.T) {
} }
func TestParseKeys(t *testing.T) { func TestParseKeys(t *testing.T) {
pairs := parseKeyChords("ctrl-z,alt-z,f2,@,Alt-a,!,ctrl-G,J,g,ctrl-alt-a,ALT-enter,alt-SPACE", "") pairs, _ := parseKeyChords("ctrl-z,alt-z,f2,@,Alt-a,!,ctrl-G,J,g,ctrl-alt-a,ALT-enter,alt-SPACE", "")
checkEvent := func(e tui.Event, s string) { checkEvent := func(e tui.Event, s string) {
if pairs[e] != s { if pairs[e] != s {
t.Errorf("%s != %s", pairs[e], s) t.Errorf("%s != %s", pairs[e], s)
@ -163,7 +163,7 @@ func TestParseKeys(t *testing.T) {
checkEvent(tui.AltKey(' '), "alt-SPACE") checkEvent(tui.AltKey(' '), "alt-SPACE")
// Synonyms // Synonyms
pairs = parseKeyChords("enter,Return,space,tab,btab,esc,up,down,left,right", "") pairs, _ = parseKeyChords("enter,Return,space,tab,btab,esc,up,down,left,right", "")
if len(pairs) != 9 { if len(pairs) != 9 {
t.Error(9) t.Error(9)
} }
@ -177,7 +177,7 @@ func TestParseKeys(t *testing.T) {
check(tui.Left, "left") check(tui.Left, "left")
check(tui.Right, "right") check(tui.Right, "right")
pairs = parseKeyChords("Tab,Ctrl-I,PgUp,page-up,pgdn,Page-Down,Home,End,Alt-BS,Alt-BSpace,shift-left,shift-right,btab,shift-tab,return,Enter,bspace", "") pairs, _ = parseKeyChords("Tab,Ctrl-I,PgUp,page-up,pgdn,Page-Down,Home,End,Alt-BS,Alt-BSpace,shift-left,shift-right,btab,shift-tab,return,Enter,bspace", "")
if len(pairs) != 11 { if len(pairs) != 11 {
t.Error(11) t.Error(11)
} }
@ -206,40 +206,40 @@ func TestParseKeysWithComma(t *testing.T) {
} }
} }
pairs := parseKeyChords(",", "") pairs, _ := parseKeyChords(",", "")
checkN(len(pairs), 1) checkN(len(pairs), 1)
check(pairs, tui.Key(','), ",") check(pairs, tui.Key(','), ",")
pairs = parseKeyChords(",,a,b", "") pairs, _ = parseKeyChords(",,a,b", "")
checkN(len(pairs), 3) checkN(len(pairs), 3)
check(pairs, tui.Key('a'), "a") check(pairs, tui.Key('a'), "a")
check(pairs, tui.Key('b'), "b") check(pairs, tui.Key('b'), "b")
check(pairs, tui.Key(','), ",") check(pairs, tui.Key(','), ",")
pairs = parseKeyChords("a,b,,", "") pairs, _ = parseKeyChords("a,b,,", "")
checkN(len(pairs), 3) checkN(len(pairs), 3)
check(pairs, tui.Key('a'), "a") check(pairs, tui.Key('a'), "a")
check(pairs, tui.Key('b'), "b") check(pairs, tui.Key('b'), "b")
check(pairs, tui.Key(','), ",") check(pairs, tui.Key(','), ",")
pairs = parseKeyChords("a,,,b", "") pairs, _ = parseKeyChords("a,,,b", "")
checkN(len(pairs), 3) checkN(len(pairs), 3)
check(pairs, tui.Key('a'), "a") check(pairs, tui.Key('a'), "a")
check(pairs, tui.Key('b'), "b") check(pairs, tui.Key('b'), "b")
check(pairs, tui.Key(','), ",") check(pairs, tui.Key(','), ",")
pairs = parseKeyChords("a,,,b,c", "") pairs, _ = parseKeyChords("a,,,b,c", "")
checkN(len(pairs), 4) checkN(len(pairs), 4)
check(pairs, tui.Key('a'), "a") check(pairs, tui.Key('a'), "a")
check(pairs, tui.Key('b'), "b") check(pairs, tui.Key('b'), "b")
check(pairs, tui.Key('c'), "c") check(pairs, tui.Key('c'), "c")
check(pairs, tui.Key(','), ",") check(pairs, tui.Key(','), ",")
pairs = parseKeyChords(",,,", "") pairs, _ = parseKeyChords(",,,", "")
checkN(len(pairs), 1) checkN(len(pairs), 1)
check(pairs, tui.Key(','), ",") check(pairs, tui.Key(','), ",")
pairs = parseKeyChords(",ALT-,,", "") pairs, _ = parseKeyChords(",ALT-,,", "")
checkN(len(pairs), 1) checkN(len(pairs), 1)
check(pairs, tui.AltKey(','), "ALT-,") check(pairs, tui.AltKey(','), "ALT-,")
} }
@ -262,17 +262,13 @@ func TestBind(t *testing.T) {
} }
} }
check(tui.CtrlA.AsEvent(), "", actBeginningOfLine) check(tui.CtrlA.AsEvent(), "", actBeginningOfLine)
errorString := ""
errorFn := func(e string) {
errorString = e
}
parseKeymap(keymap, parseKeymap(keymap,
"ctrl-a:kill-line,ctrl-b:toggle-sort+up+down,c:page-up,alt-z:page-down,"+ "ctrl-a:kill-line,ctrl-b:toggle-sort+up+down,c:page-up,alt-z:page-down,"+
"f1:execute(ls {+})+abort+execute(echo \n{+})+select-all,f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+ "f1:execute(ls {+})+abort+execute(echo \n{+})+select-all,f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
"alt-a:execute-Multi@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};,"+ "alt-a:execute-Multi@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};,"+
"x:Execute(foo+bar),X:execute/bar+baz/"+ "x:Execute(foo+bar),X:execute/bar+baz/"+
",f1:+first,f1:+top"+ ",f1:+first,f1:+top"+
",,:abort,::accept,+:execute:++\nfoobar,Y:execute(baz)+up", errorFn) ",,:abort,::accept,+:execute:++\nfoobar,Y:execute(baz)+up")
check(tui.CtrlA.AsEvent(), "", actKillLine) check(tui.CtrlA.AsEvent(), "", actKillLine)
check(tui.CtrlB.AsEvent(), "", actToggleSort, actUp, actDown) check(tui.CtrlB.AsEvent(), "", actToggleSort, actUp, actDown)
check(tui.Key('c'), "", actPageUp) check(tui.Key('c'), "", actPageUp)
@ -290,20 +286,17 @@ func TestBind(t *testing.T) {
check(tui.Key('+'), "++\nfoobar,Y:execute(baz)+up", actExecute) check(tui.Key('+'), "++\nfoobar,Y:execute(baz)+up", actExecute)
for idx, char := range []rune{'~', '!', '@', '#', '$', '%', '^', '&', '*', '|', ';', '/'} { for idx, char := range []rune{'~', '!', '@', '#', '$', '%', '^', '&', '*', '|', ';', '/'} {
parseKeymap(keymap, fmt.Sprintf("%d:execute%cfoobar%c", idx%10, char, char), errorFn) parseKeymap(keymap, fmt.Sprintf("%d:execute%cfoobar%c", idx%10, char, char))
check(tui.Key([]rune(fmt.Sprintf("%d", idx%10))[0]), "foobar", actExecute) check(tui.Key([]rune(fmt.Sprintf("%d", idx%10))[0]), "foobar", actExecute)
} }
parseKeymap(keymap, "f1:abort", errorFn) parseKeymap(keymap, "f1:abort")
check(tui.F1.AsEvent(), "", actAbort) check(tui.F1.AsEvent(), "", actAbort)
if len(errorString) > 0 {
t.Errorf("error parsing keymap: %s", errorString)
}
} }
func TestColorSpec(t *testing.T) { func TestColorSpec(t *testing.T) {
theme := tui.Dark256 theme := tui.Dark256
dark := parseTheme(theme, "dark") dark, _ := parseTheme(theme, "dark")
if *dark != *theme { if *dark != *theme {
t.Errorf("colors should be equivalent") t.Errorf("colors should be equivalent")
} }
@ -311,7 +304,7 @@ func TestColorSpec(t *testing.T) {
t.Errorf("point should not be equivalent") t.Errorf("point should not be equivalent")
} }
light := parseTheme(theme, "dark,light") light, _ := parseTheme(theme, "dark,light")
if *light == *theme { if *light == *theme {
t.Errorf("should not be equivalent") t.Errorf("should not be equivalent")
} }
@ -322,7 +315,7 @@ func TestColorSpec(t *testing.T) {
t.Errorf("point should not be equivalent") t.Errorf("point should not be equivalent")
} }
customized := parseTheme(theme, "fg:231,bg:232") customized, _ := parseTheme(theme, "fg:231,bg:232")
if customized.Fg.Color != 231 || customized.Bg.Color != 232 { if customized.Fg.Color != 231 || customized.Bg.Color != 232 {
t.Errorf("color not customized") t.Errorf("color not customized")
} }
@ -335,7 +328,7 @@ func TestColorSpec(t *testing.T) {
t.Errorf("colors should now be equivalent: %v, %v", tui.Dark256, customized) t.Errorf("colors should now be equivalent: %v, %v", tui.Dark256, customized)
} }
customized = parseTheme(theme, "fg:231,dark,bg:232") customized, _ = parseTheme(theme, "fg:231,dark,bg:232")
if customized.Fg != tui.Dark256.Fg || customized.Bg == tui.Dark256.Bg { if customized.Fg != tui.Dark256.Fg || customized.Bg == tui.Dark256.Bg {
t.Errorf("color not customized") t.Errorf("color not customized")
} }
@ -475,7 +468,7 @@ func TestValidateSign(t *testing.T) {
} }
func TestParseSingleActionList(t *testing.T) { func TestParseSingleActionList(t *testing.T) {
actions := parseSingleActionList("Execute@foo+bar,baz@+up+up+reload:down+down", func(string) {}) actions, _ := parseSingleActionList("Execute@foo+bar,baz@+up+up+reload:down+down")
if len(actions) != 4 { if len(actions) != 4 {
t.Errorf("Invalid number of actions parsed:%d", len(actions)) t.Errorf("Invalid number of actions parsed:%d", len(actions))
} }
@ -491,11 +484,8 @@ func TestParseSingleActionList(t *testing.T) {
} }
func TestParseSingleActionListError(t *testing.T) { func TestParseSingleActionListError(t *testing.T) {
err := "" _, err := parseSingleActionList("change-query(foobar)baz")
parseSingleActionList("change-query(foobar)baz", func(e string) { if err == nil {
err = e
})
if len(err) == 0 {
t.Errorf("Failed to detect error") t.Errorf("Failed to detect error")
} }
} }

@ -73,28 +73,28 @@ func parseListenAddress(address string) (listenAddress, error) {
return listenAddress{parts[0], port}, nil return listenAddress{parts[0], port}, nil
} }
func startHttpServer(address listenAddress, actionChannel chan []*action, responseChannel chan string) (int, error) { func startHttpServer(address listenAddress, actionChannel chan []*action, responseChannel chan string) (net.Listener, int, error) {
host := address.host host := address.host
port := address.port port := address.port
apiKey := os.Getenv("FZF_API_KEY") apiKey := os.Getenv("FZF_API_KEY")
if !address.IsLocal() && len(apiKey) == 0 { if !address.IsLocal() && len(apiKey) == 0 {
return port, errors.New("FZF_API_KEY is required to allow remote access") return nil, port, errors.New("FZF_API_KEY is required to allow remote access")
} }
addrStr := fmt.Sprintf("%s:%d", host, port) addrStr := fmt.Sprintf("%s:%d", host, port)
listener, err := net.Listen("tcp", addrStr) listener, err := net.Listen("tcp", addrStr)
if err != nil { if err != nil {
return port, fmt.Errorf("failed to listen on %s", addrStr) return nil, port, fmt.Errorf("failed to listen on %s", addrStr)
} }
if port == 0 { if port == 0 {
addr := listener.Addr().String() addr := listener.Addr().String()
parts := strings.Split(addr, ":") parts := strings.Split(addr, ":")
if len(parts) < 2 { if len(parts) < 2 {
return port, fmt.Errorf("cannot extract port: %s", addr) return nil, port, fmt.Errorf("cannot extract port: %s", addr)
} }
var err error var err error
port, err = strconv.Atoi(parts[len(parts)-1]) port, err = strconv.Atoi(parts[len(parts)-1])
if err != nil { if err != nil {
return port, err return nil, port, err
} }
} }
@ -109,7 +109,7 @@ func startHttpServer(address listenAddress, actionChannel chan []*action, respon
conn, err := listener.Accept() conn, err := listener.Accept()
if err != nil { if err != nil {
if errors.Is(err, net.ErrClosed) { if errors.Is(err, net.ErrClosed) {
break return
} else { } else {
continue continue
} }
@ -117,10 +117,9 @@ func startHttpServer(address listenAddress, actionChannel chan []*action, respon
conn.Write([]byte(server.handleHttpRequest(conn))) conn.Write([]byte(server.handleHttpRequest(conn)))
conn.Close() conn.Close()
} }
listener.Close()
}() }()
return port, nil return listener, port, nil
} }
// Here we are writing a simplistic HTTP server without using net/http // Here we are writing a simplistic HTTP server without using net/http
@ -217,12 +216,9 @@ func (server *httpServer) handleHttpRequest(conn net.Conn) string {
} }
body = body[:contentLength] body = body[:contentLength]
errorMessage := "" actions, err := parseSingleActionList(strings.Trim(string(body), "\r\n"))
actions := parseSingleActionList(strings.Trim(string(body), "\r\n"), func(message string) { if err != nil {
errorMessage = message return bad(err.Error())
})
if len(errorMessage) > 0 {
return bad(errorMessage)
} }
if len(actions) == 0 { if len(actions) == 0 {
return bad("no action specified") return bad("no action specified")

@ -2,10 +2,12 @@ package fzf
import ( import (
"bufio" "bufio"
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"math" "math"
"net"
"os" "os"
"os/signal" "os/signal"
"regexp" "regexp"
@ -49,6 +51,7 @@ var whiteSuffix *regexp.Regexp
var offsetComponentRegex *regexp.Regexp var offsetComponentRegex *regexp.Regexp
var offsetTrimCharsRegex *regexp.Regexp var offsetTrimCharsRegex *regexp.Regexp
var activeTempFiles []string var activeTempFiles []string
var activeTempFilesMutex sync.Mutex
var passThroughRegex *regexp.Regexp var passThroughRegex *regexp.Regexp
var actionTypeRegex *regexp.Regexp var actionTypeRegex *regexp.Regexp
@ -63,6 +66,7 @@ func init() {
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{} 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
@ -241,6 +245,7 @@ type Terminal struct {
unicode bool unicode bool
listenAddr *listenAddress listenAddr *listenAddress
listenPort *int listenPort *int
listener net.Listener
listenUnsafe bool listenUnsafe bool
borderShape tui.BorderShape borderShape tui.BorderShape
cleanExit bool cleanExit bool
@ -259,7 +264,7 @@ type Terminal struct {
hasResizeActions bool hasResizeActions bool
triggerLoad bool triggerLoad bool
reading bool reading bool
running bool running *util.AtomicBool
failed *string failed *string
jumping jumpMode jumping jumpMode
jumpLabels string jumpLabels string
@ -278,12 +283,12 @@ type Terminal struct {
previewBox *util.EventBox previewBox *util.EventBox
eventBox *util.EventBox eventBox *util.EventBox
mutex sync.Mutex mutex sync.Mutex
initFunc func() initFunc func() error
prevLines []itemLine prevLines []itemLine
suppress bool suppress bool
sigstop bool sigstop bool
startChan chan fitpad startChan chan fitpad
killChan chan int killChan chan bool
serverInputChan chan []*action serverInputChan chan []*action
serverOutputChan chan string serverOutputChan chan string
eventChan chan tui.Event eventChan chan tui.Event
@ -340,6 +345,7 @@ const (
reqPreviewRefresh reqPreviewRefresh
reqPreviewDelayed reqPreviewDelayed
reqQuit reqQuit
reqFatal
) )
type action struct { type action struct {
@ -380,6 +386,7 @@ const (
actDeleteChar actDeleteChar
actDeleteCharEof actDeleteCharEof
actEndOfLine actEndOfLine
actFatal
actForwardChar actForwardChar
actForwardWord actForwardWord
actKillLine actKillLine
@ -537,6 +544,7 @@ func defaultKeymap() map[tui.Event][]*action {
keymap[e] = toActions(a) keymap[e] = toActions(a)
} }
add(tui.Fatal, actFatal)
add(tui.Invalid, actInvalid) add(tui.Invalid, actInvalid)
add(tui.CtrlA, actBeginningOfLine) add(tui.CtrlA, actBeginningOfLine)
add(tui.CtrlB, actBackwardChar) add(tui.CtrlB, actBackwardChar)
@ -642,7 +650,7 @@ func evaluateHeight(opts *Options, termHeight int) int {
} }
// NewTerminal returns new Terminal object // NewTerminal returns new Terminal object
func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor) *Terminal { func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor) (*Terminal, error) {
input := trimQuery(opts.Query) input := trimQuery(opts.Query)
var delay time.Duration var delay time.Duration
if opts.Tac { if opts.Tac {
@ -660,11 +668,12 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
} }
var renderer tui.Renderer var renderer tui.Renderer
fullscreen := !opts.Height.auto && (opts.Height.size == 0 || opts.Height.percent && opts.Height.size == 100) fullscreen := !opts.Height.auto && (opts.Height.size == 0 || opts.Height.percent && opts.Height.size == 100)
var err error
if fullscreen { if fullscreen {
if tui.HasFullscreenRenderer() { if tui.HasFullscreenRenderer() {
renderer = tui.NewFullscreenRenderer(opts.Theme, opts.Black, opts.Mouse) renderer = tui.NewFullscreenRenderer(opts.Theme, opts.Black, opts.Mouse)
} else { } else {
renderer = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit, renderer, err = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit,
true, func(h int) int { return h }) true, func(h int) int { return h })
} }
} else { } else {
@ -680,7 +689,10 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
effectiveMinHeight += borderLines(opts.BorderShape) effectiveMinHeight += borderLines(opts.BorderShape)
return util.Min(termHeight, util.Max(evaluateHeight(opts, termHeight), effectiveMinHeight)) return util.Min(termHeight, util.Max(evaluateHeight(opts, termHeight), effectiveMinHeight))
} }
renderer = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit, false, maxHeightFunc) renderer, err = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit, false, maxHeightFunc)
}
if err != nil {
return nil, err
} }
wordRubout := "[^\\pL\\pN][\\pL\\pN]" wordRubout := "[^\\pL\\pN][\\pL\\pN]"
wordNext := "[\\pL\\pN][^\\pL\\pN]|(.$)" wordNext := "[\\pL\\pN][^\\pL\\pN]|(.$)"
@ -693,6 +705,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
for key, action := range opts.Keymap { for key, action := range opts.Keymap {
keymapCopy[key] = action keymapCopy[key] = action
} }
t := Terminal{ t := Terminal{
initDelay: delay, initDelay: delay,
infoStyle: opts.InfoStyle, infoStyle: opts.InfoStyle,
@ -754,7 +767,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
hasLoadActions: false, hasLoadActions: false,
triggerLoad: false, triggerLoad: false,
reading: true, reading: true,
running: true, running: util.NewAtomicBool(true),
failed: nil, failed: nil,
jumping: jumpDisabled, jumping: jumpDisabled,
jumpLabels: opts.JumpLabels, jumpLabels: opts.JumpLabels,
@ -775,12 +788,12 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
slab: util.MakeSlab(slab16Size, slab32Size), slab: util.MakeSlab(slab16Size, slab32Size),
theme: opts.Theme, theme: opts.Theme,
startChan: make(chan fitpad, 1), startChan: make(chan fitpad, 1),
killChan: make(chan int), killChan: make(chan bool),
serverInputChan: make(chan []*action, 100), serverInputChan: make(chan []*action, 100),
serverOutputChan: make(chan string), serverOutputChan: make(chan string),
eventChan: make(chan tui.Event, 6), // (load + result + zero|one) | (focus) | (resize) | (GetChar) eventChan: make(chan tui.Event, 6), // (load + result + zero|one) | (focus) | (resize) | (GetChar)
tui: renderer, tui: renderer,
initFunc: func() { renderer.Init() }, initFunc: func() error { return renderer.Init() },
executing: util.NewAtomicBool(false), executing: util.NewAtomicBool(false),
lastAction: actStart, lastAction: actStart,
lastFocus: minItem.Index()} lastFocus: minItem.Index()}
@ -832,14 +845,15 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
_, t.hasLoadActions = t.keymap[tui.Load.AsEvent()] _, t.hasLoadActions = t.keymap[tui.Load.AsEvent()]
if t.listenAddr != nil { if t.listenAddr != nil {
port, err := startHttpServer(*t.listenAddr, t.serverInputChan, t.serverOutputChan) listener, port, err := startHttpServer(*t.listenAddr, t.serverInputChan, t.serverOutputChan)
if err != nil { if err != nil {
errorExit(err.Error()) return nil, err
} }
t.listener = listener
t.listenPort = &port t.listenPort = &port
} }
return &t return &t, nil
} }
func (t *Terminal) environ() []string { func (t *Terminal) environ() []string {
@ -2512,21 +2526,27 @@ func hasPreviewFlags(template string) (slot bool, plus bool, forceUpdate bool) {
func writeTemporaryFile(data []string, printSep string) string { func writeTemporaryFile(data []string, printSep string) string {
f, err := os.CreateTemp("", "fzf-preview-*") f, err := os.CreateTemp("", "fzf-preview-*")
if err != nil { if err != nil {
errorExit("Unable to create temporary file") // Unable to create temporary file
// FIXME: Should we terminate the program?
return ""
} }
defer f.Close() defer f.Close()
f.WriteString(strings.Join(data, printSep)) f.WriteString(strings.Join(data, printSep))
f.WriteString(printSep) f.WriteString(printSep)
activeTempFilesMutex.Lock()
activeTempFiles = append(activeTempFiles, f.Name()) activeTempFiles = append(activeTempFiles, f.Name())
activeTempFilesMutex.Unlock()
return f.Name() return f.Name()
} }
func cleanTemporaryFiles() { func cleanTemporaryFiles() {
activeTempFilesMutex.Lock()
for _, filename := range activeTempFiles { for _, filename := range activeTempFiles {
os.Remove(filename) os.Remove(filename)
} }
activeTempFiles = []string{} activeTempFiles = []string{}
activeTempFilesMutex.Unlock()
} }
type replacePlaceholderParams struct { type replacePlaceholderParams struct {
@ -2836,18 +2856,18 @@ func (t *Terminal) toggleItem(item *Item) bool {
return true return true
} }
func (t *Terminal) killPreview(code int) { func (t *Terminal) killPreview() {
select { select {
case t.killChan <- code: case t.killChan <- true:
default: default:
if code != exitCancel {
t.eventBox.Set(EvtQuit, code)
}
} }
} }
func (t *Terminal) cancelPreview() { func (t *Terminal) cancelPreview() {
t.killPreview(exitCancel) select {
case t.killChan <- false:
default:
}
} }
func (t *Terminal) pwindowSize() tui.TermSize { func (t *Terminal) pwindowSize() tui.TermSize {
@ -2871,7 +2891,7 @@ func (t *Terminal) currentIndex() int32 {
} }
// Loop is called to start Terminal I/O // Loop is called to start Terminal I/O
func (t *Terminal) Loop() { func (t *Terminal) Loop() error {
// prof := profile.Start(profile.ProfilePath("/tmp/")) // prof := profile.Start(profile.ProfilePath("/tmp/"))
fitpad := <-t.startChan fitpad := <-t.startChan
fit := fitpad.fit fit := fitpad.fit
@ -2895,14 +2915,23 @@ func (t *Terminal) Loop() {
return util.Min(termHeight, contentHeight+pad) return util.Min(termHeight, contentHeight+pad)
}) })
} }
// Context
ctx, cancel := context.WithCancel(context.Background())
{ // Late initialization { // Late initialization
intChan := make(chan os.Signal, 1) intChan := make(chan os.Signal, 1)
signal.Notify(intChan, os.Interrupt, syscall.SIGTERM) signal.Notify(intChan, os.Interrupt, syscall.SIGTERM)
go func() { go func() {
for s := range intChan { for {
// Don't quit by SIGINT while executing because it should be for the executing command and not for fzf itself select {
if !(s == os.Interrupt && t.executing.Get()) { case <-ctx.Done():
t.reqBox.Set(reqQuit, nil) return
case s := <-intChan:
// Don't quit by SIGINT while executing because it should be for the executing command and not for fzf itself
if !(s == os.Interrupt && t.executing.Get()) {
t.reqBox.Set(reqQuit, nil)
}
} }
} }
}() }()
@ -2911,8 +2940,12 @@ func (t *Terminal) Loop() {
notifyOnCont(contChan) notifyOnCont(contChan)
go func() { go func() {
for { for {
<-contChan select {
t.reqBox.Set(reqReinit, nil) case <-ctx.Done():
return
case <-contChan:
t.reqBox.Set(reqReinit, nil)
}
} }
}() }()
@ -2921,14 +2954,21 @@ func (t *Terminal) Loop() {
notifyOnResize(resizeChan) // Non-portable notifyOnResize(resizeChan) // Non-portable
go func() { go func() {
for { for {
<-resizeChan select {
t.reqBox.Set(reqResize, nil) case <-ctx.Done():
return
case <-resizeChan:
t.reqBox.Set(reqResize, nil)
}
} }
}() }()
} }
t.mutex.Lock() t.mutex.Lock()
t.initFunc() if err := t.initFunc(); err != nil {
t.mutex.Unlock()
return err
}
t.termSize = t.tui.Size() t.termSize = t.tui.Size()
t.resizeWindows(false) t.resizeWindows(false)
t.window.Erase() t.window.Erase()
@ -2945,7 +2985,7 @@ func (t *Terminal) Loop() {
// Keep the spinner spinning // Keep the spinner spinning
go func() { go func() {
for { for t.running.Get() {
t.mutex.Lock() t.mutex.Lock()
reading := t.reading reading := t.reading
t.mutex.Unlock() t.mutex.Unlock()
@ -3071,12 +3111,13 @@ func (t *Terminal) Loop() {
Loop: Loop:
for { for {
select { select {
case <-ctx.Done():
break Loop
case <-timer.C: case <-timer.C:
t.reqBox.Set(reqPreviewDelayed, version) t.reqBox.Set(reqPreviewDelayed, version)
case code := <-t.killChan: case immediately := <-t.killChan:
if code != exitCancel { if immediately {
util.KillCommand(cmd) util.KillCommand(cmd)
t.eventBox.Set(EvtQuit, code)
} else { } else {
// We can immediately kill a long-running preview program // We can immediately kill a long-running preview program
// once we started rendering its partial output // once we started rendering its partial output
@ -3131,11 +3172,14 @@ func (t *Terminal) Loop() {
var focusedIndex int32 = minItem.Index() var focusedIndex int32 = minItem.Index()
var version int64 = -1 var version int64 = -1
running := true running := true
code := exitError code := ExitError
exit := func(getCode func() int) { exit := func(getCode func() int) {
if t.listener != nil {
t.listener.Close()
}
t.tui.Close() t.tui.Close()
code = getCode() code = getCode()
if code <= exitNoMatch && t.history != nil { if code <= ExitNoMatch && t.history != nil {
t.history.append(string(t.input)) t.history.append(string(t.input))
} }
running = false running = false
@ -3203,9 +3247,9 @@ func (t *Terminal) Loop() {
case reqClose: case reqClose:
exit(func() int { exit(func() int {
if t.output() { if t.output() {
return exitOk return ExitOk
} }
return exitNoMatch return ExitNoMatch
}) })
return return
case reqPreviewDisplay: case reqPreviewDisplay:
@ -3233,11 +3277,14 @@ func (t *Terminal) Loop() {
case reqPrintQuery: case reqPrintQuery:
exit(func() int { exit(func() int {
t.printer(string(t.input)) t.printer(string(t.input))
return exitOk return ExitOk
}) })
return return
case reqQuit: case reqQuit:
exit(func() int { return exitInterrupt }) exit(func() int { return ExitInterrupt })
return
case reqFatal:
exit(func() int { return ExitError })
return return
} }
} }
@ -3245,8 +3292,11 @@ func (t *Terminal) Loop() {
t.mutex.Unlock() t.mutex.Unlock()
}) })
} }
// prof.Stop()
t.killPreview(code) t.eventBox.Set(EvtQuit, code)
t.running.Set(false)
t.killPreview()
cancel()
}() }()
looping := true looping := true
@ -3256,8 +3306,16 @@ func (t *Terminal) Loop() {
barrier := make(chan bool) barrier := make(chan bool)
go func() { go func() {
for { for {
<-barrier select {
t.eventChan <- t.tui.GetChar() case <-ctx.Done():
return
case <-barrier:
}
select {
case <-ctx.Done():
return
case t.eventChan <- t.tui.GetChar():
}
} }
}() }()
previewDraggingPos := -1 previewDraggingPos := -1
@ -3353,7 +3411,7 @@ func (t *Terminal) Loop() {
t.pressed = ret t.pressed = ret
t.reqBox.Set(reqClose, nil) t.reqBox.Set(reqClose, nil)
t.mutex.Unlock() t.mutex.Unlock()
return return nil
} }
} }
@ -3547,8 +3605,9 @@ func (t *Terminal) Loop() {
} }
case actTransform: case actTransform:
body := t.executeCommand(a.a, false, true, true, false) body := t.executeCommand(a.a, false, true, true, false)
actions := parseSingleActionList(strings.Trim(body, "\r\n"), func(message string) {}) if actions, err := parseSingleActionList(strings.Trim(body, "\r\n")); err == nil {
return doActions(actions) return doActions(actions)
}
case actTransformBorderLabel: case actTransformBorderLabel:
label := t.executeCommand(a.a, false, true, true, true) label := t.executeCommand(a.a, false, true, true, true)
t.borderLabelOpts.label = label t.borderLabelOpts.label = label
@ -3580,6 +3639,8 @@ func (t *Terminal) Loop() {
t.input = current.text.ToRunes() t.input = current.text.ToRunes()
t.cx = len(t.input) t.cx = len(t.input)
} }
case actFatal:
req(reqFatal)
case actAbort: case actAbort:
req(reqQuit) req(reqQuit)
case actDeleteChar: case actDeleteChar:
@ -4066,15 +4127,17 @@ func (t *Terminal) Loop() {
t.reading = true t.reading = true
} }
case actUnbind: case actUnbind:
keys := parseKeyChords(a.a, "PANIC") if keys, err := parseKeyChords(a.a, "PANIC"); err == nil {
for key := range keys { for key := range keys {
delete(t.keymap, key) delete(t.keymap, key)
}
} }
case actRebind: case actRebind:
keys := parseKeyChords(a.a, "PANIC") if keys, err := parseKeyChords(a.a, "PANIC"); err == nil {
for key := range keys { for key := range keys {
if originalAction, found := t.keymapOrg[key]; found { if originalAction, found := t.keymapOrg[key]; found {
t.keymap[key] = originalAction t.keymap[key] = originalAction
}
} }
} }
case actChangePreview: case actChangePreview:
@ -4221,6 +4284,7 @@ func (t *Terminal) Loop() {
t.reqBox.Set(event, nil) t.reqBox.Set(event, nil)
} }
} }
return nil
} }
func (t *Terminal) constrain() { func (t *Terminal) constrain() {

@ -71,14 +71,14 @@ func TestTransform(t *testing.T) {
{ {
tokens := Tokenize(input, Delimiter{}) tokens := Tokenize(input, Delimiter{})
{ {
ranges := splitNth("1,2,3") ranges, _ := splitNth("1,2,3")
tx := Transform(tokens, ranges) tx := Transform(tokens, ranges)
if joinTokens(tx) != "abc: def: ghi: " { if joinTokens(tx) != "abc: def: ghi: " {
t.Errorf("%s", tx) t.Errorf("%s", tx)
} }
} }
{ {
ranges := splitNth("1..2,3,2..,1") ranges, _ := splitNth("1..2,3,2..,1")
tx := Transform(tokens, ranges) tx := Transform(tokens, ranges)
if string(joinTokens(tx)) != "abc: def: ghi: def: ghi: jklabc: " || if string(joinTokens(tx)) != "abc: def: ghi: def: ghi: jklabc: " ||
len(tx) != 4 || len(tx) != 4 ||
@ -93,7 +93,7 @@ func TestTransform(t *testing.T) {
{ {
tokens := Tokenize(input, delimiterRegexp(":")) tokens := Tokenize(input, delimiterRegexp(":"))
{ {
ranges := splitNth("1..2,3,2..,1") ranges, _ := splitNth("1..2,3,2..,1")
tx := Transform(tokens, ranges) tx := Transform(tokens, ranges)
if joinTokens(tx) != " abc: def: ghi: def: ghi: jkl abc:" || if joinTokens(tx) != " abc: def: ghi: def: ghi: jkl abc:" ||
len(tx) != 4 || len(tx) != 4 ||
@ -108,5 +108,6 @@ func TestTransform(t *testing.T) {
} }
func TestTransformIndexOutOfBounds(t *testing.T) { func TestTransformIndexOutOfBounds(t *testing.T) {
Transform([]Token{}, splitNth("1")) s, _ := splitNth("1")
Transform([]Token{}, s)
} }

@ -29,7 +29,7 @@ const (
StrikeThrough = Attr(1 << 7) StrikeThrough = Attr(1 << 7)
) )
func (r *FullscreenRenderer) Init() {} func (r *FullscreenRenderer) Init() error { return nil }
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {} func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
func (r *FullscreenRenderer) Pause(bool) {} func (r *FullscreenRenderer) Pause(bool) {}
func (r *FullscreenRenderer) Resume(bool, bool) {} func (r *FullscreenRenderer) Resume(bool, bool) {}

@ -83,34 +83,35 @@ func _() {
_ = x[Alt-72] _ = x[Alt-72]
_ = x[CtrlAlt-73] _ = x[CtrlAlt-73]
_ = x[Invalid-74] _ = x[Invalid-74]
_ = x[Mouse-75] _ = x[Fatal-75]
_ = x[DoubleClick-76] _ = x[Mouse-76]
_ = x[LeftClick-77] _ = x[DoubleClick-77]
_ = x[RightClick-78] _ = x[LeftClick-78]
_ = x[SLeftClick-79] _ = x[RightClick-79]
_ = x[SRightClick-80] _ = x[SLeftClick-80]
_ = x[ScrollUp-81] _ = x[SRightClick-81]
_ = x[ScrollDown-82] _ = x[ScrollUp-82]
_ = x[SScrollUp-83] _ = x[ScrollDown-83]
_ = x[SScrollDown-84] _ = x[SScrollUp-84]
_ = x[PreviewScrollUp-85] _ = x[SScrollDown-85]
_ = x[PreviewScrollDown-86] _ = x[PreviewScrollUp-86]
_ = x[Resize-87] _ = x[PreviewScrollDown-87]
_ = x[Change-88] _ = x[Resize-88]
_ = x[BackwardEOF-89] _ = x[Change-89]
_ = x[Start-90] _ = x[BackwardEOF-90]
_ = x[Load-91] _ = x[Start-91]
_ = x[Focus-92] _ = x[Load-92]
_ = x[One-93] _ = x[Focus-93]
_ = x[Zero-94] _ = x[One-94]
_ = x[Result-95] _ = x[Zero-95]
_ = x[Jump-96] _ = x[Result-96]
_ = x[JumpCancel-97] _ = x[Jump-97]
_ = x[JumpCancel-98]
} }
const _EventType_name = "RuneCtrlACtrlBCtrlCCtrlDCtrlECtrlFCtrlGCtrlHTabCtrlJCtrlKCtrlLCtrlMCtrlNCtrlOCtrlPCtrlQCtrlRCtrlSCtrlTCtrlUCtrlVCtrlWCtrlXCtrlYCtrlZEscCtrlSpaceCtrlDeleteCtrlBackSlashCtrlRightBracketCtrlCaretCtrlSlashShiftTabBackspaceDeletePageUpPageDownUpDownLeftRightHomeEndInsertShiftUpShiftDownShiftLeftShiftRightShiftDeleteF1F2F3F4F5F6F7F8F9F10F11F12AltBackspaceAltUpAltDownAltLeftAltRightAltShiftUpAltShiftDownAltShiftLeftAltShiftRightAltCtrlAltInvalidMouseDoubleClickLeftClickRightClickSLeftClickSRightClickScrollUpScrollDownSScrollUpSScrollDownPreviewScrollUpPreviewScrollDownResizeChangeBackwardEOFStartLoadFocusOneZeroResultJumpJumpCancel" const _EventType_name = "RuneCtrlACtrlBCtrlCCtrlDCtrlECtrlFCtrlGCtrlHTabCtrlJCtrlKCtrlLCtrlMCtrlNCtrlOCtrlPCtrlQCtrlRCtrlSCtrlTCtrlUCtrlVCtrlWCtrlXCtrlYCtrlZEscCtrlSpaceCtrlDeleteCtrlBackSlashCtrlRightBracketCtrlCaretCtrlSlashShiftTabBackspaceDeletePageUpPageDownUpDownLeftRightHomeEndInsertShiftUpShiftDownShiftLeftShiftRightShiftDeleteF1F2F3F4F5F6F7F8F9F10F11F12AltBackspaceAltUpAltDownAltLeftAltRightAltShiftUpAltShiftDownAltShiftLeftAltShiftRightAltCtrlAltInvalidFatalMouseDoubleClickLeftClickRightClickSLeftClickSRightClickScrollUpScrollDownSScrollUpSScrollDownPreviewScrollUpPreviewScrollDownResizeChangeBackwardEOFStartLoadFocusOneZeroResultJumpJumpCancel"
var _EventType_index = [...]uint16{0, 4, 9, 14, 19, 24, 29, 34, 39, 44, 47, 52, 57, 62, 67, 72, 77, 82, 87, 92, 97, 102, 107, 112, 117, 122, 127, 132, 135, 144, 154, 167, 183, 192, 201, 209, 218, 224, 230, 238, 240, 244, 248, 253, 257, 260, 266, 273, 282, 291, 301, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 333, 336, 339, 351, 356, 363, 370, 378, 388, 400, 412, 425, 428, 435, 442, 447, 458, 467, 477, 487, 498, 506, 516, 525, 536, 551, 568, 574, 580, 591, 596, 600, 605, 608, 612, 618, 622, 632} var _EventType_index = [...]uint16{0, 4, 9, 14, 19, 24, 29, 34, 39, 44, 47, 52, 57, 62, 67, 72, 77, 82, 87, 92, 97, 102, 107, 112, 117, 122, 127, 132, 135, 144, 154, 167, 183, 192, 201, 209, 218, 224, 230, 238, 240, 244, 248, 253, 257, 260, 266, 273, 282, 291, 301, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 333, 336, 339, 351, 356, 363, 370, 378, 388, 400, 412, 425, 428, 435, 442, 447, 452, 463, 472, 482, 492, 503, 511, 521, 530, 541, 556, 573, 579, 585, 596, 601, 605, 610, 613, 617, 623, 627, 637}
func (i EventType) String() string { func (i EventType) String() string {
if i < 0 || i >= EventType(len(_EventType_index)-1) { if i < 0 || i >= EventType(len(_EventType_index)-1) {

@ -2,6 +2,7 @@ package tui
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"os" "os"
"regexp" "regexp"
@ -10,6 +11,7 @@ import (
"time" "time"
"unicode/utf8" "unicode/utf8"
"github.com/junegunn/fzf/src/util"
"github.com/rivo/uniseg" "github.com/rivo/uniseg"
"golang.org/x/term" "golang.org/x/term"
@ -26,6 +28,7 @@ const (
) )
const consoleDevice string = "/dev/tty" const consoleDevice string = "/dev/tty"
const fatalError string = "Failed to read " + consoleDevice
var offsetRegexp *regexp.Regexp = regexp.MustCompile("(.*)\x1b\\[([0-9]+);([0-9]+)R") var offsetRegexp *regexp.Regexp = regexp.MustCompile("(.*)\x1b\\[([0-9]+);([0-9]+)R")
var offsetRegexpBegin *regexp.Regexp = regexp.MustCompile("^\x1b\\[[0-9]+;[0-9]+R") var offsetRegexpBegin *regexp.Regexp = regexp.MustCompile("^\x1b\\[[0-9]+;[0-9]+R")
@ -78,6 +81,7 @@ func (r *LightRenderer) flush() {
// Light renderer // Light renderer
type LightRenderer struct { type LightRenderer struct {
closed *util.AtomicBool
theme *ColorTheme theme *ColorTheme
mouse bool mouse bool
forceBlack bool forceBlack bool
@ -123,19 +127,24 @@ type LightWindow struct {
bg Color bg Color
} }
func NewLightRenderer(theme *ColorTheme, forceBlack bool, mouse bool, tabstop int, clearOnExit bool, fullscreen bool, maxHeightFunc func(int) int) Renderer { func NewLightRenderer(theme *ColorTheme, forceBlack bool, mouse bool, tabstop int, clearOnExit bool, fullscreen bool, maxHeightFunc func(int) int) (Renderer, error) {
in, err := openTtyIn()
if err != nil {
return nil, err
}
r := LightRenderer{ r := LightRenderer{
closed: util.NewAtomicBool(false),
theme: theme, theme: theme,
forceBlack: forceBlack, forceBlack: forceBlack,
mouse: mouse, mouse: mouse,
clearOnExit: clearOnExit, clearOnExit: clearOnExit,
ttyin: openTtyIn(), ttyin: in,
yoffset: 0, yoffset: 0,
tabstop: tabstop, tabstop: tabstop,
fullscreen: fullscreen, fullscreen: fullscreen,
upOneLine: false, upOneLine: false,
maxHeightFunc: maxHeightFunc} maxHeightFunc: maxHeightFunc}
return &r return &r, nil
} }
func repeat(r rune, times int) string { func repeat(r rune, times int) string {
@ -153,11 +162,11 @@ func atoi(s string, defaultValue int) int {
return value return value
} }
func (r *LightRenderer) Init() { func (r *LightRenderer) Init() error {
r.escDelay = atoi(os.Getenv("ESCDELAY"), defaultEscDelay) r.escDelay = atoi(os.Getenv("ESCDELAY"), defaultEscDelay)
if err := r.initPlatform(); err != nil { if err := r.initPlatform(); err != nil {
errorExit(err.Error()) return err
} }
r.updateTerminalSize() r.updateTerminalSize()
initTheme(r.theme, r.defaultTheme(), r.forceBlack) initTheme(r.theme, r.defaultTheme(), r.forceBlack)
@ -195,6 +204,7 @@ func (r *LightRenderer) Init() {
if !r.fullscreen && r.mouse { if !r.fullscreen && r.mouse {
r.yoffset, _ = r.findOffset() r.yoffset, _ = r.findOffset()
} }
return nil
} }
func (r *LightRenderer) Resize(maxHeightFunc func(int) int) { func (r *LightRenderer) Resize(maxHeightFunc func(int) int) {
@ -233,15 +243,16 @@ func getEnv(name string, defaultValue int) int {
return atoi(env, defaultValue) return atoi(env, defaultValue)
} }
func (r *LightRenderer) getBytes() []byte { func (r *LightRenderer) getBytes() ([]byte, error) {
return r.getBytesInternal(r.buffer, false) bytes, err := r.getBytesInternal(r.buffer, false)
return bytes, err
} }
func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte { func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) ([]byte, error) {
c, ok := r.getch(nonblock) c, ok := r.getch(nonblock)
if !nonblock && !ok { if !nonblock && !ok {
r.Close() r.Close()
errorExit("Failed to read " + consoleDevice) return nil, errors.New("Failed to read " + consoleDevice)
} }
retries := 0 retries := 0
@ -272,19 +283,23 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
// so terminate fzf immediately. // so terminate fzf immediately.
if len(buffer) > maxInputBuffer { if len(buffer) > maxInputBuffer {
r.Close() r.Close()
panic(fmt.Sprintf("Input buffer overflow (%d): %v", len(buffer), buffer)) return nil, fmt.Errorf("Input buffer overflow (%d): %v", len(buffer), buffer)
} }
} }
return buffer return buffer, nil
} }
func (r *LightRenderer) GetChar() Event { func (r *LightRenderer) GetChar() Event {
var err error
if len(r.buffer) == 0 { if len(r.buffer) == 0 {
r.buffer = r.getBytes() r.buffer, err = r.getBytes()
if err != nil {
return Event{Fatal, 0, nil}
}
} }
if len(r.buffer) == 0 { if len(r.buffer) == 0 {
panic("Empty buffer") return Event{Fatal, 0, nil}
} }
sz := 1 sz := 1
@ -315,7 +330,10 @@ func (r *LightRenderer) GetChar() Event {
ev := r.escSequence(&sz) ev := r.escSequence(&sz)
// Second chance // Second chance
if ev.Type == Invalid { if ev.Type == Invalid {
r.buffer = r.getBytes() r.buffer, err = r.getBytes()
if err != nil {
return Event{Fatal, 0, nil}
}
ev = r.escSequence(&sz) ev = r.escSequence(&sz)
} }
return ev return ev
@ -738,6 +756,7 @@ func (r *LightRenderer) Close() {
r.flush() r.flush()
r.closePlatform() r.closePlatform()
r.restoreTerminal() r.restoreTerminal()
r.closed.Set(true)
} }
func (r *LightRenderer) Top() int { func (r *LightRenderer) Top() int {

@ -3,7 +3,7 @@
package tui package tui
import ( import (
"fmt" "errors"
"os" "os"
"os/exec" "os/exec"
"strings" "strings"
@ -48,19 +48,18 @@ func (r *LightRenderer) closePlatform() {
// NOOP // NOOP
} }
func openTtyIn() *os.File { func openTtyIn() (*os.File, error) {
in, err := os.OpenFile(consoleDevice, syscall.O_RDONLY, 0) in, err := os.OpenFile(consoleDevice, syscall.O_RDONLY, 0)
if err != nil { if err != nil {
tty := ttyname() tty := ttyname()
if len(tty) > 0 { if len(tty) > 0 {
if in, err := os.OpenFile(tty, syscall.O_RDONLY, 0); err == nil { if in, err := os.OpenFile(tty, syscall.O_RDONLY, 0); err == nil {
return in return in, nil
} }
} }
fmt.Fprintln(os.Stderr, "Failed to open "+consoleDevice) return nil, errors.New("Failed to open " + consoleDevice)
util.Exit(2)
} }
return in return in, nil
} }
func (r *LightRenderer) setupTerminal() { func (r *LightRenderer) setupTerminal() {
@ -86,9 +85,14 @@ func (r *LightRenderer) updateTerminalSize() {
func (r *LightRenderer) findOffset() (row int, col int) { func (r *LightRenderer) findOffset() (row int, col int) {
r.csi("6n") r.csi("6n")
r.flush() r.flush()
var err error
bytes := []byte{} bytes := []byte{}
for tries := 0; tries < offsetPollTries; tries++ { for tries := 0; tries < offsetPollTries; tries++ {
bytes = r.getBytesInternal(bytes, tries > 0) bytes, err = r.getBytesInternal(bytes, tries > 0)
if err != nil {
return -1, -1
}
offsets := offsetRegexp.FindSubmatch(bytes) offsets := offsetRegexp.FindSubmatch(bytes)
if len(offsets) > 3 { if len(offsets) > 3 {
// Add anything we skipped over to the input buffer // Add anything we skipped over to the input buffer

@ -72,7 +72,7 @@ func (r *LightRenderer) initPlatform() error {
go func() { go func() {
fd := int(r.inHandle) fd := int(r.inHandle)
b := make([]byte, 1) b := make([]byte, 1)
for { for !r.closed.Get() {
// HACK: if run from PSReadline, something resets ConsoleMode to remove ENABLE_VIRTUAL_TERMINAL_INPUT. // HACK: if run from PSReadline, something resets ConsoleMode to remove ENABLE_VIRTUAL_TERMINAL_INPUT.
_ = windows.SetConsoleMode(windows.Handle(r.inHandle), consoleFlagsInput) _ = windows.SetConsoleMode(windows.Handle(r.inHandle), consoleFlagsInput)
@ -91,9 +91,9 @@ func (r *LightRenderer) closePlatform() {
windows.SetConsoleMode(windows.Handle(r.inHandle), r.origStateInput) windows.SetConsoleMode(windows.Handle(r.inHandle), r.origStateInput)
} }
func openTtyIn() *os.File { func openTtyIn() (*os.File, error) {
// not used // not used
return nil return nil, nil
} }
func (r *LightRenderer) setupTerminal() error { func (r *LightRenderer) setupTerminal() error {

@ -7,7 +7,6 @@ import (
"time" "time"
"github.com/gdamore/tcell/v2" "github.com/gdamore/tcell/v2"
"github.com/gdamore/tcell/v2/encoding"
"github.com/junegunn/fzf/src/util" "github.com/junegunn/fzf/src/util"
"github.com/rivo/uniseg" "github.com/rivo/uniseg"
@ -146,13 +145,13 @@ var (
_initialResize bool = true _initialResize bool = true
) )
func (r *FullscreenRenderer) initScreen() { func (r *FullscreenRenderer) initScreen() error {
s, e := tcell.NewScreen() s, e := tcell.NewScreen()
if e != nil { if e != nil {
errorExit(e.Error()) return e
} }
if e = s.Init(); e != nil { if e = s.Init(); e != nil {
errorExit(e.Error()) return e
} }
if r.mouse { if r.mouse {
s.EnableMouse() s.EnableMouse()
@ -160,16 +159,21 @@ func (r *FullscreenRenderer) initScreen() {
s.DisableMouse() s.DisableMouse()
} }
_screen = s _screen = s
return nil
} }
func (r *FullscreenRenderer) Init() { func (r *FullscreenRenderer) Init() error {
if os.Getenv("TERM") == "cygwin" { if os.Getenv("TERM") == "cygwin" {
os.Setenv("TERM", "") os.Setenv("TERM", "")
} }
encoding.Register()
r.initScreen() if err := r.initScreen(); err != nil {
return err
}
initTheme(r.theme, r.defaultTheme(), r.forceBlack) initTheme(r.theme, r.defaultTheme(), r.forceBlack)
return nil
} }
func (r *FullscreenRenderer) Top() int { func (r *FullscreenRenderer) Top() int {

@ -1,8 +1,6 @@
package tui package tui
import ( import (
"fmt"
"os"
"strconv" "strconv"
"time" "time"
@ -104,6 +102,7 @@ const (
CtrlAlt CtrlAlt
Invalid Invalid
Fatal
Mouse Mouse
DoubleClick DoubleClick
@ -525,7 +524,7 @@ type TermSize struct {
} }
type Renderer interface { type Renderer interface {
Init() Init() error
Resize(maxHeightFunc func(int) int) Resize(maxHeightFunc func(int) int)
Pause(clear bool) Pause(clear bool)
Resume(clear bool, sigcont bool) Resume(clear bool, sigcont bool)
@ -685,11 +684,6 @@ func NoColorTheme() *ColorTheme {
} }
} }
func errorExit(message string) {
fmt.Fprintln(os.Stderr, message)
util.Exit(2)
}
func init() { func init() {
Default16 = &ColorTheme{ Default16 = &ColorTheme{
Colored: true, Colored: true,

@ -25,6 +25,7 @@ func RunAtExitFuncs() {
for i := len(fns) - 1; i >= 0; i-- { for i := len(fns) - 1; i >= 0; i-- {
fns[i]() fns[i]()
} }
atExitFuncs = nil
} }
// Exit executes any functions registered with AtExit() then exits the program // Exit executes any functions registered with AtExit() then exits the program

Loading…
Cancel
Save