func (p *Peco) SetupSource() (s *Source, err error) { if pdebug.Enabled { g := pdebug.Marker("Peco.SetupSource").BindError(&err) defer g.End() } var in io.Reader switch { case len(p.args) > 1: f, err := os.Open(p.args[1]) if err != nil { return nil, errors.Wrap(err, "failed to open file for input") } in = f case !util.IsTty(p.Stdin): in = p.Stdin default: return nil, errors.New("you must supply something to work with via filename or stdin") } src := NewSource(in, p.idgen, p.enableSep) // Block until we receive something from `in` if pdebug.Enabled { pdebug.Printf("Blocking until we read something in source...") } go src.Setup(p) <-src.Ready() return src, nil }
// Setup reads from the input os.File. func (s *Source) Setup(state *Peco) { s.setupOnce.Do(func() { done := make(chan struct{}) refresh := make(chan struct{}, 1) defer close(done) defer close(refresh) draw := func(state *Peco) { // Not a great thing to do, allowing nil to be passed // as state, but for testing I couldn't come up with anything // better for the moment if state != nil { state.Hub().SendDraw(false) } } go func() { ticker := time.NewTicker(100 * time.Millisecond) defer ticker.Stop() for { select { case <-done: draw(state) return case <-ticker.C: draw(state) } } }() // This sync.Once var is used to receive the notification // that there was at least 1 line read from the source var notify sync.Once notifycb := func() { // close the ready channel so others can be notified // that there's at least 1 line in the buffer close(s.ready) } scanner := bufio.NewScanner(s.in) defer func() { if util.IsTty(s.in) { return } if closer, ok := s.in.(io.Closer); ok { closer.Close() } }() readCount := 0 for scanner.Scan() { txt := scanner.Text() readCount++ s.Append(NewRawLine(s.idgen.next(), txt, s.enableSep)) notify.Do(notifycb) } // XXX Just in case scanner.Scan() did not return a single line... // Note: this will be a no-op if notify.Do has been called before notify.Do(notifycb) // And also, close the done channel so we can tell the consumers // we have finished reading everything close(s.setupDone) if pdebug.Enabled { pdebug.Printf("Read all %d lines from source", readCount) } }) }
func (cli *CLI) Run() error { opts, args, err := cli.parseOptions() if err != nil { return err } if opts.OptHelp { showHelp() return nil } if opts.OptVersion { fmt.Fprintf(os.Stderr, "peco: %s\n", version) return nil } var in *os.File // receive in from either a file or Stdin switch { case len(args) > 0: in, err = os.Open(args[0]) if err != nil { return err } case !util.IsTty(os.Stdin.Fd()): in = os.Stdin default: return fmt.Errorf("error: You must supply something to work with via filename or stdin") } ctx := NewCtx(opts) defer func() { ch := ctx.ResultCh() if ch == nil { return } for match := range ch { line := match.Output() if line[len(line)-1] != '\n' { line = line + "\n" } fmt.Fprint(os.Stdout, line) } }() if opts.OptRcfile == "" { file, err := LocateRcfile() if err == nil { opts.OptRcfile = file } } // Default matcher is IgnoreCase ctx.SetCurrentFilterByName(IgnoreCaseMatch) if opts.OptRcfile != "" { err = ctx.ReadConfig(opts.OptRcfile) if err != nil { return err } } if len(opts.OptPrompt) > 0 { ctx.SetPrompt(opts.OptPrompt) } initialFilter := "" if len(opts.OptInitialFilter) <= 0 && len(opts.OptInitialMatcher) > 0 { initialFilter = opts.OptInitialMatcher } else if len(opts.OptInitialFilter) > 0 { initialFilter = opts.OptInitialFilter } if initialFilter != "" { if err := ctx.SetCurrentFilterByName(initialFilter); err != nil { return fmt.Errorf("unknown matcher: '%s'\n", initialFilter) } } // Try waiting for something available in the source stream // before doing any terminal initialization (also done by termbox) reader := ctx.NewBufferReader(in) ctx.AddWaitGroup(1) go reader.Loop() // This channel blocks until we receive something from `in` <-reader.InputReadyCh() if err := util.TtyReady(); err != nil { return err } defer util.TtyTerm() if err := screen.Init(); err != nil { return err } defer screen.Close() ctx.startInput() view := ctx.NewView() filter := ctx.NewFilter() sig := NewSignalHandler( ctx.LoopCh(), // onEnd ctx.ReleaseWaitGroup, // onSigReceived // XXX For future reference: DO NOT, and I mean DO NOT call // termbox.Close() here. Calling termbox.Close() twice in our // context actually BLOCKS. Can you believe it? IT BLOCKS. // // So if we called termbox.Close() here, and then in main() // defer termbox.Close() blocks. Not cool. func() { ctx.ExitWith(ErrSignalReceived) }, ) loopers := []interface { Loop() }{ view, filter, sig, } for _, looper := range loopers { ctx.AddWaitGroup(1) go looper.Loop() } if len(opts.OptQuery) > 0 { ctx.SetQuery([]rune(opts.OptQuery)) ctx.ExecQuery() } else { ctx.SendDraw(false) } ctx.WaitDone() return ctx.Error() }