diff --git a/src/constants.go b/src/constants.go index b2225f1..9a4fc29 100644 --- a/src/constants.go +++ b/src/constants.go @@ -47,3 +47,9 @@ const ( EvtHeader EvtClose ) + +const ( + exitOk = 0 + exitNoMatch = 1 + exitError = 2 +) diff --git a/src/core.go b/src/core.go index 96bfdd4..04b6eab 100644 --- a/src/core.go +++ b/src/core.go @@ -56,7 +56,7 @@ func Run(opts *Options) { if opts.Version { fmt.Println(version) - os.Exit(0) + os.Exit(exitOk) } // Event channel @@ -156,12 +156,14 @@ func Run(opts *Options) { pattern := patternBuilder([]rune(*opts.Filter)) + found := false if streamingFilter { reader := Reader{ func(runes []byte) bool { item := chunkList.trans(runes, 0) if item != nil && pattern.MatchItem(item) { fmt.Println(string(item.text)) + found = true } return false }, eventBox, opts.ReadZero} @@ -176,9 +178,13 @@ func Run(opts *Options) { pattern: pattern}) for i := 0; i < merger.Length(); i++ { fmt.Println(merger.Get(i).AsString(opts.Ansi)) + found = true } } - os.Exit(0) + if found { + os.Exit(exitOk) + } + os.Exit(exitNoMatch) } // Synchronous search @@ -253,7 +259,10 @@ func Run(opts *Options) { for i := 0; i < count; i++ { fmt.Println(val.Get(i).AsString(opts.Ansi)) } - os.Exit(0) + if count > 0 { + os.Exit(exitOk) + } + os.Exit(exitNoMatch) } deferred = false terminal.startChan <- true diff --git a/src/curses/curses.go b/src/curses/curses.go index 3de8e98..59cea3b 100644 --- a/src/curses/curses.go +++ b/src/curses/curses.go @@ -261,7 +261,7 @@ func Init(theme *ColorTheme, black bool, mouse bool) { _screen = C.newterm(nil, C.stderr, C.stdin) if _screen == nil { fmt.Println("Invalid $TERM: " + os.Getenv("TERM")) - os.Exit(1) + os.Exit(2) } C.set_term(_screen) if mouse { @@ -275,7 +275,7 @@ func Init(theme *ColorTheme, black bool, mouse bool) { go func() { <-intChan Close() - os.Exit(1) + os.Exit(2) }() if theme != nil { diff --git a/src/options.go b/src/options.go index 7090066..47d8bb1 100644 --- a/src/options.go +++ b/src/options.go @@ -180,14 +180,14 @@ func defaultOptions() *Options { Version: false} } -func help(ok int) { +func help(code int) { os.Stderr.WriteString(usage) - os.Exit(ok) + os.Exit(code) } func errorExit(msg string) { os.Stderr.WriteString(msg + "\n") - os.Exit(1) + os.Exit(exitError) } func optString(arg string, prefixes ...string) (bool, string) { @@ -682,7 +682,7 @@ func parseOptions(opts *Options, allArgs []string) { arg := allArgs[i] switch arg { case "-h", "--help": - help(0) + help(exitOk) case "-x", "--extended": opts.Mode = ModeExtended case "-e", "--extended-exact": diff --git a/src/terminal.go b/src/terminal.go index 053ed78..c3fb966 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -280,17 +280,19 @@ func (t *Terminal) UpdateList(merger *Merger) { t.reqBox.Set(reqList, nil) } -func (t *Terminal) output() { +func (t *Terminal) output() bool { if t.printQuery { fmt.Println(string(t.input)) } if len(t.expect) > 0 { fmt.Println(t.pressed) } - if len(t.selected) == 0 { + found := len(t.selected) > 0 + if !found { cnt := t.merger.Length() if cnt > 0 && cnt > t.cy { fmt.Println(t.merger.Get(t.cy).AsString(t.ansi)) + found = true } } else { sels := make([]selectedItem, 0, len(t.selected)) @@ -302,6 +304,7 @@ func (t *Terminal) output() { fmt.Println(*sel.text) } } + return found } func runeWidth(r rune, prefixWidth int) int { @@ -743,7 +746,7 @@ func (t *Terminal) Loop() { } exit := func(code int) { - if code == 0 && t.history != nil { + if code <= exitNoMatch && t.history != nil { t.history.append(string(t.input)) } os.Exit(code) @@ -776,11 +779,13 @@ func (t *Terminal) Loop() { t.printAll() case reqClose: C.Close() - t.output() - exit(0) + if t.output() { + exit(exitOk) + } + exit(exitNoMatch) case reqQuit: C.Close() - exit(1) + exit(exitError) } } t.placeCursor() diff --git a/test/test_go.rb b/test/test_go.rb index d1f45dc..5b35264 100644 --- a/test/test_go.rb +++ b/test/test_go.rb @@ -780,11 +780,6 @@ class TestGoFZF < TestBase tmux.send_keys :Enter end - def test_invalid_term - tmux.send_keys "TERM=xxx fzf", :Enter - tmux.until { |lines| lines.any? { |l| l.include? 'Invalid $TERM: xxx' } } - end - def test_with_nth writelines tempname, ['hello world ', 'byebye'] assert_equal 'hello world ', `cat #{tempname} | #{FZF} -f"^he hehe" -x -n 2.. --with-nth 2,1,1`.chomp @@ -801,6 +796,47 @@ class TestGoFZF < TestBase assert_equal src, `cat #{tempname} | #{FZF} -fhehe -x -n 2.. --with-nth 2,1,1 --no-ansi`.chomp end + def test_exit_0_exit_code + `echo foo | #{FZF} -q bar -0` + assert_equal 1, $?.exitstatus + end + + def test_invalid_term + lines = `TERM=xxx #{FZF}` + assert_equal 2, $?.exitstatus + assert lines.include?('Invalid $TERM: xxx') + end + + def test_invalid_option + lines = `#{FZF} --foobar 2>&1` + assert_equal 2, $?.exitstatus + assert lines.include?('unknown option: --foobar'), lines + end + + def test_filter_exitstatus + # filter / streaming filter + ["", "--no-sort"].each do |opts| + assert `echo foo | #{FZF} -f foo #{opts}`.include?('foo') + assert_equal 0, $?.exitstatus + + assert `echo foo | #{FZF} -f bar #{opts}`.empty? + assert_equal 1, $?.exitstatus + end + end + + def test_exitstatus_empty + { '99' => '0', '999' => '1' }.each do |query, status| + tmux.send_keys "seq 100 | #{FZF} -q #{query}", :Enter + tmux.until { |lines| lines[-2] =~ %r{ [10]/100} } + tmux.send_keys :Enter + + tmux.send_keys 'echo --\$?--' + tmux.until { |lines| lines.last.include? "echo --$?--" } + tmux.send_keys :Enter + tmux.until { |lines| lines.last.include? "--#{status}--" } + end + end + private def writelines path, lines File.unlink path while File.exists? path