From a78051dcbe28571ed9d93101acf93ab359e777f4 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Fri, 3 Mar 2017 13:53:41 +0900 Subject: [PATCH 01/11] Properly suspend termbox before executing external command This makes sure that termbox does not inadvertantly steal our commands while executing external commands, and also makes sure we explicitly reset the screen. Also, make sure we use a temporary selection. --- action.go | 13 ++++++++++- cmd/peco/peco.go | 8 +++++-- interface.go | 6 ++++- peco.go | 4 ++-- peco_test.go | 4 +++- screen.go | 57 ++++++++++++++++++++++++++++++++++++++++++++++-- selection.go | 7 ++++++ 7 files changed, 90 insertions(+), 9 deletions(-) diff --git a/action.go b/action.go index c57fdfb..0aeefd8 100644 --- a/action.go +++ b/action.go @@ -2,6 +2,7 @@ package peco import ( "fmt" + "io" "io/ioutil" "os" "os/exec" @@ -730,9 +731,15 @@ func makeCombinedAction(actions ...Action) ActionFunc { }) } +type nopCloseWriter struct { + io.Writer +} + +func (nopCloseWriter) Close() error { return nil } func makeCommandAction(state *Peco, cc *CommandConfig) ActionFunc { return func(ctx context.Context, state *Peco, _ termbox.Event) { - sel := state.Selection() + sel := NewSelection() + state.Selection().Copy(sel) if sel.Len() == 0 { if l, err := state.CurrentLineBuffer().LineAt(state.Location().LineNumber()); err == nil { sel.Add(l) @@ -776,10 +783,14 @@ func makeCommandAction(state *Peco, cc *CommandConfig) ActionFunc { cmd.Stdin = state.Stdin cmd.Stdout = state.Stdout cmd.Stderr = state.Stderr + + state.screen.Suspend() + err = cmd.Run() if f != nil { os.Remove(f.Name()) } + state.screen.Resume() state.ExecQuery() } if err != nil { diff --git a/cmd/peco/peco.go b/cmd/peco/peco.go index d84c381..9538cee 100644 --- a/cmd/peco/peco.go +++ b/cmd/peco/peco.go @@ -5,9 +5,11 @@ import ( "os" "runtime" + "context" + + pdebug "github.com/lestrrat/go-pdebug" "github.com/peco/peco" "github.com/peco/peco/internal/util" - "context" ) func main() { @@ -21,6 +23,9 @@ func main() { } func _main() int { + if pdebug.Enabled { + pdebug.DefaultCtx.Writer = os.Stderr + } if envvar := os.Getenv("GOMAXPROCS"); envvar == "" { runtime.GOMAXPROCS(runtime.NumCPU()) } @@ -42,7 +47,6 @@ func _main() int { return 1 } - } return 0 diff --git a/interface.go b/interface.go index 907c9ea..1816bed 100644 --- a/interface.go +++ b/interface.go @@ -156,16 +156,20 @@ type Screen interface { Init() error Close() error Flush() error - PollEvent() chan termbox.Event + PollEvent(context.Context) chan termbox.Event Print(PrintArgs) int + Resume() SetCell(int, int, rune, termbox.Attribute, termbox.Attribute) Size() (int, int) SendEvent(termbox.Event) + Suspend() } // Termbox just hands out the processing to the termbox library type Termbox struct { mutex sync.Mutex + resumeCh chan(struct{}) + suspendCh chan(struct{}) } // View handles the drawing/updating the screen diff --git a/peco.go b/peco.go index a832867..0da9d92 100644 --- a/peco.go +++ b/peco.go @@ -113,7 +113,7 @@ func New() *Peco { idgen: newIDGen(), queryExecDelay: 50 * time.Millisecond, readyCh: make(chan struct{}), - screen: &Termbox{}, + screen: NewTermbox(), selection: NewSelection(), } } @@ -326,7 +326,7 @@ func (p *Peco) Run(ctx context.Context) (err error) { loopers := []interface { Loop(ctx context.Context, cancel func()) error }{ - NewInput(p, p.Keymap(), p.screen.PollEvent()), + NewInput(p, p.Keymap(), p.screen.PollEvent(ctx)), NewView(p), NewFilter(p), sig.New(sig.SigReceivedHandlerFunc(func(sig os.Signal) { diff --git a/peco_test.go b/peco_test.go index 31a7d53..ec0667e 100644 --- a/peco_test.go +++ b/peco_test.go @@ -124,12 +124,14 @@ func (d dummyScreen) Flush() error { d.record("Flush", interceptorArgs{}) return nil } -func (d dummyScreen) PollEvent() chan termbox.Event { +func (d dummyScreen) PollEvent(ctx context.Context) chan termbox.Event { return d.pollCh } func (d dummyScreen) Size() (int, int) { return d.width, d.height } +func (d dummyScreen) Resume() {} +func (d dummyScreen) Suspend() {} func TestIDGen(t *testing.T) { idgen := newIDGen() diff --git a/screen.go b/screen.go index 796971c..0e87240 100644 --- a/screen.go +++ b/screen.go @@ -1,8 +1,10 @@ package peco import ( + "context" "unicode/utf8" + pdebug "github.com/lestrrat/go-pdebug" "github.com/mattn/go-runewidth" "github.com/nsf/termbox-go" "github.com/pkg/errors" @@ -16,6 +18,13 @@ func (t *Termbox) Init() error { return t.PostInit() } +func NewTermbox() *Termbox { + return &Termbox{ + suspendCh: make(chan struct{}), + resumeCh: make(chan struct{}), + } +} + func (t *Termbox) Close() error { termbox.Close() return nil @@ -38,7 +47,7 @@ func (t *Termbox) Flush() error { // PollEvent returns a channel that you can listen to for // termbox's events. The actual polling is done in a // separate gouroutine -func (t *Termbox) PollEvent() chan termbox.Event { +func (t *Termbox) PollEvent(ctx context.Context) chan termbox.Event { // XXX termbox.PollEvent() can get stuck on unexpected signal // handling cases. We still would like to wait until the user // (termbox) has some event for us to process, but we don't @@ -49,17 +58,61 @@ func (t *Termbox) PollEvent() chan termbox.Event { // safely be implemented in terms of select {} which is // safe from being stuck. evCh := make(chan termbox.Event) + + go func() { + // keep listening to suspend requests here + for { + select { + case <-ctx.Done(): + return + case <-t.suspendCh: + if pdebug.Enabled { + pdebug.Printf("poll event suspended!") + } + termbox.Interrupt() + t.Close() + } + } + }() + go func() { defer func() { recover() }() defer func() { close(evCh) }() + for { - evCh <- termbox.PollEvent() + ev := termbox.PollEvent() + if ev.Type != termbox.EventInterrupt { + evCh <- ev + continue + } + + select { + case <-ctx.Done(): + return + case <-t.resumeCh: + t.Init() + } } }() return evCh } +func (t *Termbox) Suspend() { + pdebug.Printf("termbox.Suspend") + select { + case t.suspendCh <- struct{}{}: + default: + } +} + +func (t *Termbox) Resume() { + select { + case t.resumeCh <- struct{}{}: + default: + } +} + // SetCell writes to the terminal func (t *Termbox) SetCell(x, y int, ch rune, fg, bg termbox.Attribute) { t.mutex.Lock() diff --git a/selection.go b/selection.go index 6f6f1ea..e607213 100644 --- a/selection.go +++ b/selection.go @@ -20,6 +20,13 @@ func (s *Selection) Add(l line.Line) { s.tree.ReplaceOrInsert(l) } +func (s *Selection) Copy(dst *Selection) { + s.Ascend(func(it btree.Item) bool { + dst.Add(it.(line.Line)) + return true + }) +} + // Remove removes the specified line from the selection func (s *Selection) Remove(l line.Line) { s.mutex.Lock() From 482fa7fc6df812f3ef9184b5cc31952c3280337e Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Fri, 3 Mar 2017 14:21:38 +0900 Subject: [PATCH 02/11] Change default scan buffer size to 256kb ...and make it configurable --- README.md | 12 ++++++++++++ interface.go | 8 +++++--- peco.go | 5 +++++ source.go | 18 +++++++++++++++++- 4 files changed, 39 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 94673d1..c6401ba 100644 --- a/README.md +++ b/README.md @@ -251,6 +251,18 @@ Default value for StickySelection is false. OnCancel is equivalent to `--on-cancel` command line option. +### MaxScanBufferSize + +```json +{ + "MaxScanBufferSize": 256 +} +``` + +Controls the buffer sized used by `bufio.Scanner`, which is responsible for +reading the input lines. If you believe that your input has very long lines +that prohibit peco from reading them, try increasing this number + ## Keymaps Example: diff --git a/interface.go b/interface.go index 1816bed..98c9d2c 100644 --- a/interface.go +++ b/interface.go @@ -78,6 +78,7 @@ type Peco struct { keymap Keymap layoutType string location Location + maxScanBufferSize int mutex sync.Mutex onCancel string prompt string @@ -167,9 +168,9 @@ type Screen interface { // Termbox just hands out the processing to the termbox library type Termbox struct { - mutex sync.Mutex - resumeCh chan(struct{}) - suspendCh chan(struct{}) + mutex sync.Mutex + resumeCh chan (struct{}) + suspendCh chan (struct{}) } // View handles the drawing/updating the screen @@ -299,6 +300,7 @@ type Config struct { Command []CommandConfig QueryExecutionDelay int StickySelection bool + MaxScanBufferSize int // If this is true, then the prefix for single key jump mode // is displayed by default. diff --git a/peco.go b/peco.go index 0da9d92..9155cc2 100644 --- a/peco.go +++ b/peco.go @@ -503,6 +503,11 @@ func (p *Peco) ApplyConfig(opts CLIOptions) error { } } + p.maxScanBufferSize = 256 + if v := p.config.MaxScanBufferSize; v > 0 { + p.maxScanBufferSize = v + } + p.enableSep = opts.OptEnableNullSep if i := opts.OptInitialIndex; i >= 0 { diff --git a/source.go b/source.go index c3516ad..9b269fa 100644 --- a/source.go +++ b/source.go @@ -77,7 +77,12 @@ func (s *Source) Setup(ctx context.Context, state *Peco) { // Note: this will be a no-op if notify.Do has been called before defer notify.Do(notifycb) + if pdebug.Enabled { + pdebug.Printf("Source: using buffer size of %dkb", state.maxScanBufferSize) + } + scanbuf := make([]byte, state.maxScanBufferSize*1024) scanner := bufio.NewScanner(s.in) + scanner.Buffer(scanbuf, 0) defer func() { if util.IsTty(s.in) { return @@ -95,7 +100,18 @@ func (s *Source) Setup(ctx context.Context, state *Peco) { } defer close(lines) - for scanner.Scan() { + for loop := true; loop; { + if !scanner.Scan() { + switch err := scanner.Err(); err { + case nil: // if error was io.EOF, returns nil + loop = false + default: + if pdebug.Enabled { + pdebug.Printf("err: %s", err) + } + } + continue + } lines <- scanner.Text() scanned++ } From e670560421dbb866d5cb62303c6b3fd303b450f5 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Fri, 3 Mar 2017 15:03:11 +0900 Subject: [PATCH 03/11] Change ExecuteCommand in a non-backcompatible way Data is channeled through Stdin of the command, so we don't do any silly interpolation --- action.go | 80 ++++++++++++++++++++----------------------------------- 1 file changed, 29 insertions(+), 51 deletions(-) diff --git a/action.go b/action.go index 0aeefd8..b6a57cc 100644 --- a/action.go +++ b/action.go @@ -1,10 +1,9 @@ package peco import ( + "bytes" "fmt" "io" - "io/ioutil" - "os" "os/exec" "unicode" @@ -746,58 +745,37 @@ func makeCommandAction(state *Peco, cc *CommandConfig) ActionFunc { } } + var stdin bytes.Buffer sel.Ascend(func(it btree.Item) bool { line := it.(line.Line) - - var f *os.File - var err error - - args := append([]string{}, cc.Args...) - for i, v := range args { - switch v { - case "$FILE": - if f == nil { - f, err = ioutil.TempFile("", "peco") - if err != nil { - return false - } - f.WriteString(line.Buffer()) - f.Close() - } - args[i] = f.Name() - case "$LINE": - args[i] = line.Buffer() - } - } - state.Hub().SendStatusMsg("Executing " + cc.Name) - cmd := exec.Command(args[0], args[1:]...) - if cc.Spawn { - err = cmd.Start() - go func() { - cmd.Wait() - if f != nil { - os.Remove(f.Name()) - } - }() - } else { - cmd.Stdin = state.Stdin - cmd.Stdout = state.Stdout - cmd.Stderr = state.Stderr - - state.screen.Suspend() - - err = cmd.Run() - if f != nil { - os.Remove(f.Name()) - } - state.screen.Resume() - state.ExecQuery() - } - if err != nil { - return false - } - + stdin.WriteString(line.Buffer()) + stdin.WriteRune('\n') return true }) + + var err error + state.Hub().SendStatusMsg("Executing " + cc.Name) + cmd := exec.Command(cc.Args[0], cc.Args[1:]...) + cmd.Stdin = &stdin + if cc.Spawn { + err = cmd.Start() + go cmd.Wait() + } else { + cmd.Stdout = state.Stdout + cmd.Stderr = state.Stderr + + state.screen.Suspend() + + err = cmd.Run() + state.screen.Resume() + state.ExecQuery() + } + + if err != nil { + if pdebug.Enabled { + pdebug.Printf("Error executing command %v", cc.Args) + pdebug.Printf("error: %s", err) + } + } } } From eb3dcf5306a615fea151a4750f177669d67330a8 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Fri, 3 Mar 2017 15:04:46 +0900 Subject: [PATCH 04/11] fix README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c6401ba..6ee8a5a 100644 --- a/README.md +++ b/README.md @@ -259,9 +259,9 @@ OnCancel is equivalent to `--on-cancel` command line option. } ``` -Controls the buffer sized used by `bufio.Scanner`, which is responsible for -reading the input lines. If you believe that your input has very long lines -that prohibit peco from reading them, try increasing this number +Controls the buffer sized (in kilobytes) used by `bufio.Scanner`, which is +responsible for reading the input lines. If you believe that your input has +very long lines that prohibit peco from reading them, try increasing this number. ## Keymaps From 8ca4f61967a0521fb001f7508cab3b8c8029485b Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Fri, 3 Mar 2017 15:40:09 +0900 Subject: [PATCH 05/11] Remove ExecuteCommand, hello --exec --exec is a new, and far better tool to execute external commands from peco results. It is invoked when `Finish` action is called, and pipes selected line(s) to the specified command. If there were saved lines, every saved line plus the currently selected line is piped to the command. Otherwise, only the currently selected line is piped. When the command exits, execution goes back to peco, where you can keep doing incremental searches, and again execute external commands. When you are done, you are expected to exit out of peco using the `Cancel` action --- action.go | 92 ++++++++++++++++++++++------------------------------ interface.go | 14 ++------ peco.go | 19 +++-------- 3 files changed, 45 insertions(+), 80 deletions(-) diff --git a/action.go b/action.go index b6a57cc..36a316c 100644 --- a/action.go +++ b/action.go @@ -3,8 +3,6 @@ package peco import ( "bytes" "fmt" - "io" - "os/exec" "unicode" "context" @@ -13,6 +11,7 @@ import ( "github.com/lestrrat/go-pdebug" "github.com/nsf/termbox-go" "github.com/peco/peco/internal/keyseq" + "github.com/peco/peco/internal/util" "github.com/peco/peco/line" "github.com/pkg/errors" ) @@ -299,7 +298,44 @@ func doFinish(ctx context.Context, state *Peco, _ termbox.Event) { defer g.End() } - state.Exit(errCollectResults{}) + ccarg := state.execOnFinish + if len(ccarg) == 0 { + state.Exit(errCollectResults{}) + return + } + + sel := NewSelection() + state.Selection().Copy(sel) + if sel.Len() == 0 { + if l, err := state.CurrentLineBuffer().LineAt(state.Location().LineNumber()); err == nil { + sel.Add(l) + } + } + + var stdin bytes.Buffer + sel.Ascend(func(it btree.Item) bool { + line := it.(line.Line) + stdin.WriteString(line.Buffer()) + stdin.WriteRune('\n') + return true + }) + + var err error + state.Hub().SendStatusMsg("Executing " + ccarg) + cmd := util.Shell(ccarg) + cmd.Stdin = &stdin + cmd.Stdout = state.Stdout + cmd.Stderr = state.Stderr + + state.screen.Suspend() + + err = cmd.Run() + state.screen.Resume() + state.ExecQuery() + if err != nil { + // bail out, or otherwise the user cannot know what happened + state.Exit(errors.Wrap(err, `failed to execute command`)) + } } func doCancel(ctx context.Context, state *Peco, e termbox.Event) { @@ -729,53 +765,3 @@ func makeCombinedAction(actions ...Action) ActionFunc { }, toplevel) }) } - -type nopCloseWriter struct { - io.Writer -} - -func (nopCloseWriter) Close() error { return nil } -func makeCommandAction(state *Peco, cc *CommandConfig) ActionFunc { - return func(ctx context.Context, state *Peco, _ termbox.Event) { - sel := NewSelection() - state.Selection().Copy(sel) - if sel.Len() == 0 { - if l, err := state.CurrentLineBuffer().LineAt(state.Location().LineNumber()); err == nil { - sel.Add(l) - } - } - - var stdin bytes.Buffer - sel.Ascend(func(it btree.Item) bool { - line := it.(line.Line) - stdin.WriteString(line.Buffer()) - stdin.WriteRune('\n') - return true - }) - - var err error - state.Hub().SendStatusMsg("Executing " + cc.Name) - cmd := exec.Command(cc.Args[0], cc.Args[1:]...) - cmd.Stdin = &stdin - if cc.Spawn { - err = cmd.Start() - go cmd.Wait() - } else { - cmd.Stdout = state.Stdout - cmd.Stderr = state.Stderr - - state.screen.Suspend() - - err = cmd.Run() - state.screen.Resume() - state.ExecQuery() - } - - if err != nil { - if pdebug.Enabled { - pdebug.Printf("Error executing command %v", cc.Args) - pdebug.Printf("error: %s", err) - } - } - } -} diff --git a/interface.go b/interface.go index 98c9d2c..9234a68 100644 --- a/interface.go +++ b/interface.go @@ -70,6 +70,7 @@ type Peco struct { config Config currentLineBuffer Buffer enableSep bool // Enable parsing on separators + execOnFinish string filters filter.Set idgen *idgen initialFilter string @@ -297,7 +298,6 @@ type Config struct { OnCancel string `json:"OnCancel"` CustomMatcher map[string][]string CustomFilter map[string]CustomFilterConfig - Command []CommandConfig QueryExecutionDelay int StickySelection bool MaxScanBufferSize int @@ -314,17 +314,6 @@ type SingleKeyJumpConfig struct { ShowPrefix bool `json:"ShowPrefix"` } -type CommandConfig struct { - // Name is the name of the command to execute - Name string - - // TODO: need to check if how we use this is correct - Args []string - - // Spawn mean the command should be executed asynchronous. - Spawn bool -} - // CustomFilterConfig is used to specify configuration parameters // to CustomFilters type CustomFilterConfig struct { @@ -427,6 +416,7 @@ type CLIOptions struct { OptSelect1 bool `long:"select-1" description:"select first item and immediately exit if the input contains only 1 item"` OptOnCancel string `long:"on-cancel" description:"specify action on user cancel. 'success' or 'error'.\ndefault is 'success'. This may change in future versions"` OptSelectionPrefix string `long:"selection-prefix" description:"use a prefix instead of changing line color to indicate currently selected lines.\ndefault is to use colors. This option is experimental"` + OptExec string `long:"exec" description:"execute command instead of finishing/terminating peco.\nPlease note that this command will receive selected line(s) from stdin,\nand will be executed via '/bin/sh -c' or 'cmd /c'"` } type CLI struct { diff --git a/peco.go b/peco.go index 9155cc2..1ef6b4d 100644 --- a/peco.go +++ b/peco.go @@ -482,17 +482,6 @@ func readConfig(cfg *Config, filename string) error { return nil } -func (p *Peco) populateCommandList() error { - for _, v := range p.config.Command { - if len(v.Args) == 0 { - continue - } - makeCommandAction(p, &v).Register("ExecuteCommand." + v.Name) - } - - return nil -} - func (p *Peco) ApplyConfig(opts CLIOptions) error { // If layoutType is not set and is set in the config, set it if p.layoutType == "" { @@ -508,6 +497,10 @@ func (p *Peco) ApplyConfig(opts CLIOptions) error { p.maxScanBufferSize = v } + if v := opts.OptExec; len(v) > 0 { + p.execOnFinish = v + } + p.enableSep = opts.OptEnableNullSep if i := opts.OptInitialIndex; i >= 0 { @@ -545,10 +538,6 @@ func (p *Peco) ApplyConfig(opts CLIOptions) error { p.initialFilter = opts.OptInitialMatcher } - if err := p.populateCommandList(); err != nil { - return errors.Wrap(err, "failed to populate command list") - } - if err := p.populateFilters(); err != nil { return errors.Wrap(err, "failed to populate filters") } From 20fe945b21d0d015849a71e5fd8f1dfb217d2f5a Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Fri, 3 Mar 2017 15:48:28 +0900 Subject: [PATCH 06/11] add shell --- internal/util/shell_unix.go | 18 ++++++++++++++++++ internal/util/shell_windows.go | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 internal/util/shell_unix.go create mode 100644 internal/util/shell_windows.go diff --git a/internal/util/shell_unix.go b/internal/util/shell_unix.go new file mode 100644 index 0000000..6233eff --- /dev/null +++ b/internal/util/shell_unix.go @@ -0,0 +1,18 @@ +// +build !windows + +package util + +import "os/exec" + +func Shell(cmd ...string) *exec.Cmd { + const shellpath = `/bin/sh` + const shellopt = `-c` + + args := make([]string, len(cmd) + 1) + args[0] = shellopt + for i := 0; i < len(cmd); i++ { + args[i+1] = cmd[i] + } + + return exec.Command(shellpath, args...) +} diff --git a/internal/util/shell_windows.go b/internal/util/shell_windows.go new file mode 100644 index 0000000..8bc58cd --- /dev/null +++ b/internal/util/shell_windows.go @@ -0,0 +1,18 @@ +// +build windows + +package util + +import "os/exec" + +func Shell(cmd ...string) *exec.Cmd { + const shellpath = `cmd` + const shellopt = `/c` + + args := make([]string, len(cmd) + 1) + args[0] = shellopt + for i := 0; i < len(cmd); i++ { + args[i+1] = cmd[i] + } + + return exec.Command(shellpath, args...) +} From 75d0d54da696c461d80799deada007b788da2b1a Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Fri, 3 Mar 2017 15:52:42 +0900 Subject: [PATCH 07/11] Fix test --- peco.go | 2 ++ source.go | 15 ++------------- source_test.go | 4 +++- 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/peco.go b/peco.go index 1ef6b4d..6486dd1 100644 --- a/peco.go +++ b/peco.go @@ -1,6 +1,7 @@ package peco import ( + "bufio" "bytes" "io" "os" @@ -115,6 +116,7 @@ func New() *Peco { readyCh: make(chan struct{}), screen: NewTermbox(), selection: NewSelection(), + maxScanBufferSize: bufio.MaxScanTokenSize, } } diff --git a/source.go b/source.go index 9b269fa..33b2cb7 100644 --- a/source.go +++ b/source.go @@ -82,7 +82,7 @@ func (s *Source) Setup(ctx context.Context, state *Peco) { } scanbuf := make([]byte, state.maxScanBufferSize*1024) scanner := bufio.NewScanner(s.in) - scanner.Buffer(scanbuf, 0) + scanner.Buffer(scanbuf, state.maxScanBufferSize*1024) defer func() { if util.IsTty(s.in) { return @@ -100,18 +100,7 @@ func (s *Source) Setup(ctx context.Context, state *Peco) { } defer close(lines) - for loop := true; loop; { - if !scanner.Scan() { - switch err := scanner.Err(); err { - case nil: // if error was io.EOF, returns nil - loop = false - default: - if pdebug.Enabled { - pdebug.Printf("err: %s", err) - } - } - continue - } + for scanner.Scan() { lines <- scanner.Text() scanned++ } diff --git a/source_test.go b/source_test.go index 0e45b79..839a59a 100644 --- a/source_test.go +++ b/source_test.go @@ -44,7 +44,9 @@ func TestSource(t *testing.T) { r := addReadDelay(strings.NewReader(strings.Join(lines, "\n")), 2*time.Second) s := NewSource(r, ig, 0, false) - go s.Setup(ctx, &Peco{hub: nullHub{}}) + p := New() + p.hub = nullHub{} + go s.Setup(ctx, p) timeout := time.After(5 * time.Second) waitout := time.After(1 * time.Second) From 2aafac08c13772ccefc1881fc8480958c81bae4c Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Fri, 3 Mar 2017 16:14:00 +0900 Subject: [PATCH 08/11] remove debug statement --- screen.go | 1 - 1 file changed, 1 deletion(-) diff --git a/screen.go b/screen.go index 0e87240..8d237ad 100644 --- a/screen.go +++ b/screen.go @@ -99,7 +99,6 @@ func (t *Termbox) PollEvent(ctx context.Context) chan termbox.Event { } func (t *Termbox) Suspend() { - pdebug.Printf("termbox.Suspend") select { case t.suspendCh <- struct{}{}: default: From 2abd71514d3b92ec9bf69022c80a5c8a82643bb1 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Fri, 3 Mar 2017 16:18:33 +0900 Subject: [PATCH 09/11] Update README --- README.md | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 6ee8a5a..efa9373 100644 --- a/README.md +++ b/README.md @@ -183,6 +183,18 @@ If there are multiple lines in the input, the usual selection view is displayed. Specifies the exit status to use when the user cancels the query execution. For historical and back-compatibility reasons, the default is `success`, meaning if the user cancels the query, the exit status is 0. When you choose `error`, peco will exit with a non-zero value. +### --selection-prefix `string` + +When specified, peco uses the specified prefix instead of changing line color to indicate currently selected line(s). default is to use colors. This option is experimental + +### --exec `string` + +When specified, peco executes the specified external command (via shell), with peco's currently selected line(s) as its input from STDIN. + +Upon exiting from the external command, the control goes back to peco where you can keep browsing your search buffer, and to possibly execute your external command repeatedly afterwards. + +To exit out of peco when running in this mode, you must execute the Cancel command, usually the escape key. + # Configuration File peco by default consults a few locations for the config files. @@ -550,23 +562,6 @@ See --layout. } ``` -## ExecuteCommand - -``` -{ - "Keymap": { - "C-e": "peco.ExecuteCommand.Notepad" - }, - "Command": [ - { - "Name": "Notepad", - "Args": ["notepad", "$FILE"], - "Spawn": true - } - ] -} -``` - ## SelectionPrefix `SelectionPrefix` is equivalent to using `--selection-prefix` in the command line. @@ -690,6 +685,7 @@ Much code stolen from https://github.com/mattn/gof * [--select-1](#--select-1) * [--on-cancel `success|error`](#--on-cancel-successerror) * [--selection-prefix `string`](#--selection-prefix-string) + * [--exec `string`](#--exec-string) * [Configuration File](#configuration-file) * [Global](#global) * [Prompt](#prompt) @@ -712,7 +708,6 @@ Much code stolen from https://github.com/mattn/gof * [Examples](#examples) * [Layout](#layout) * [SingleKeyJump](#singlekeyjump) - * [ExecuteCommand](#executecommand) * [SelectionPrefix](#selectionprefix) * [FAQ](#faq) * [Does peco work on (msys2|cygwin)?](#does-peco-work-on-msys2cygwin) From ab74eda9e5d656d5d850e909788443b4cf20fe52 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Fri, 3 Mar 2017 16:20:32 +0900 Subject: [PATCH 10/11] Update Changes --- Changes | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Changes b/Changes index 52449a3..ba3d07c 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,18 @@ Changes ======= +v0.5.0 - unreleased + Backwards Incompatible Changes: + * ExecuteCommand has been removed. + Features: + * A new command line option `--exec` has been added. This allows you to + execute external commands via shell, and should be used as replacement + to `ExecuteCommand` + * A new configuration option `MaxScanBufferSize` has been added. Whereas + bufio.Scanner (which peco internally relies on) only accepts lines that + are < 64kb when reading the input, specifying this option in the config + allows you to change this limit. + v0.4.9 - 01 Mar 2017 Bugs/Fixes * SavedSelection under `--selection-prefix` was not properly working From b3e1903818b7f444d3f78bfc2fd412be349a1128 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Fri, 3 Mar 2017 16:22:09 +0900 Subject: [PATCH 11/11] Update changes --- Changes | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Changes b/Changes index ba3d07c..170025a 100644 --- a/Changes +++ b/Changes @@ -12,6 +12,9 @@ v0.5.0 - unreleased bufio.Scanner (which peco internally relies on) only accepts lines that are < 64kb when reading the input, specifying this option in the config allows you to change this limit. + Bugs/Fixes + * When executing external commands, the screen and the capturing of user + input would interfere when getting back to peco. v0.4.9 - 01 Mar 2017 Bugs/Fixes