peco/pecoではじめるコード・リーディング

## はじめに

[peco](https://github.com/peco/peco)のソースコード読みが少し進んだので、そのことについて少し書きたいと思います。

どんなふうに進んだのかといいますと、pecoパッケージにBufferReaderという構造体があるのですが、この構造体を使わずに標準入力を内部状態に保存してみました。そして、pecoと同じような動作になるようにがんばってみました。

少し期待しない結果になっていますが、一応、同じような動作になっています。

## 実装

昨日の[jessevdk/go-flagsを使ってみる](http://qiita.com/t-mochizuki/items/4ffc478fedae7b776805)に引き続き、サンプルとして少し実装しました。

ソースコードのほとんどの部分は[peco.go](https://github.com/peco/peco/blob/master/cmd/peco/peco.go)のソースコードです。

変更しているところはいくつかあります。

まず、オプションの機能がありません。main関数をみていただければ納得していただけると思います。

次に、ファイル操作がありません。ターゲットは標準入力だけです。しかも、元のエラー処理をほとんど壊してしまっている可能性が高いです。

そして、私がコードを追加したところは、コメントで「ここから」「ここまで」となっているところと、peco/ctx.goです。peco/ctx.goにAppendLine関数を追加しました。

``` sample.go
package main

import (
	"bufio"
	"fmt"
	"os"
	"runtime"

	"github.com/jessevdk/go-flags"
	"github.com/nsf/termbox-go"
	"github.com/peco/peco"
)

type cmdOptions struct {
	OptHelp           bool   `short:"h" long:"help"`
	OptTTY            string `long:"tty"`
	OptQuery          string `long:"query"`
	OptRcfile         string `long:"rcfile"`
	OptNoIgnoreCase   bool   `long:"no-ignore-case"`
	OptVersion        bool   `long:"version"`
	OptBufferSize     int    `long:"buffer-size" short:"b"`
	OptEnableNullSep  bool   `long:"null"`
	OptInitialIndex   int    `long:"initial-index"`
	OptInitialMatcher string `long:"initial-matcher"`
	OptPrompt         string `long:"prompt"`
}

// BufferSize returns the specified buffer size. Fulfills peco.CtxOptions
func (o cmdOptions) BufferSize() int {
	return o.OptBufferSize
}

// EnableNullSep returns tru if --null was specified. Fulfills peco.CtxOptions
func (o cmdOptions) EnableNullSep() bool {
	return o.OptEnableNullSep
}

func (o cmdOptions) InitialIndex() int {
	if o.OptInitialIndex >= 0 {
		return o.OptInitialIndex + 1
	}
	return 1
}

func main() {
	opts := &cmdOptions{}
	p := flags.NewParser(opts, flags.PrintErrors)
	p.Parse()

	ctx := peco.NewCtx(opts)
	defer func() {
		if err := recover(); err != nil {
			fmt.Fprintf(os.Stderr, "Error:\n%s", err)
		}

		if result := ctx.Result(); result != nil {
			for _, match := range result {
				line := match.Output()
				if line[len(line)-1] != '\n' {
					line = line + "\n"
				}
				fmt.Fprint(os.Stdout, line)
			}
		}
	}()

	// ここから
	in := os.Stdin
	scanner := bufio.NewScanner(in)
	for scanner.Scan() {
		// fmt.Println(scanner.Text())
		// ctx.lines = append(ctx.lines, NewNoMatch(scanner.Text(), ctx.enableSep))
		ctx.AppendLine(scanner.Text())
	}
	// ここまで

	var err error
	err = peco.TtyReady()
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		return
	}
	defer peco.TtyTerm()

	err = termbox.Init()
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		return
	}
	defer termbox.Close()

	// Windows handle Esc/Alt self
	if runtime.GOOS == "windows" {
		termbox.SetInputMode(termbox.InputEsc | termbox.InputAlt)
	}

	view := ctx.NewView()
	filter := ctx.NewFilter()
	input := ctx.NewInput()
	sig := ctx.NewSignalHandler()

	loopers := []interface {
		Loop()
	}{
		view,
		filter,
		input,
		sig,
	}
	for _, looper := range loopers {
		ctx.AddWaitGroup(1)
		go looper.Loop()
	}

	ctx.WaitDone()
}
```
``` peco/ctx.go
func (c *Ctx) AppendLine(line string) {
	c.lines = append(c.lines, NewNoMatch(line, c.enableSep))
}
```

だめだろうと思いながらpeco/ctx.goに関数を追加しました。関数を追加して、次のようにコマンドラインを実行しました。

``` sh
$ go get github.com/peco/peco
$ go build sample.go
$ ls | ./sample
```

真っ暗な画面になりました。やっぱりだめだったかと思いながら、キーボードをタイプすると、pecoと同じような画面に！！

## まとめ

[カーネルハッカー・小崎資広の「コードを読む技術」](http://cybozushiki.cybozu.co.jp/articles/m000316.html)を読んで、目からウロコでした。

![kernel.png](https://qiita-image-store.s3.amazonaws.com/0/29884/2d861c59-f2ff-09a0-0a27-286b3e70a106.png)

この記事を読んでコンプレックスみたいなものが少し解消されました。

記事の内容と少し違うかもしれませんが、今回みたいな感じでコード・リーディングを続けたいと思います。