Exemplo n.º 1
0
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
}
Exemplo n.º 2
0
// 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)
		}
	})
}
Exemplo n.º 3
0
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()
}