wip
diff --git a/flow/flow.go b/flow/flow.go
new file mode 100644
index 0000000..a93b100
--- /dev/null
+++ b/flow/flow.go
@@ -0,0 +1,51 @@
+// Command flow reflows text in paragraphs.
+//
+//   Usage: flow [-w width] [-pre prefix] [file...]
+package main
+
+import (
+	"flag"
+	"github.com/kr/pty"
+	"github.com/kr/text/flowwriter"
+	"io"
+	"log"
+	"os"
+)
+
+func main() {
+	var width int
+	var pre string
+	flag.IntVar(&width, "w", 0, "width")
+	flag.StringVar(&pre, "pre", "    ", "preformatted prefix")
+	flag.Parse()
+	if width < 1 {
+		_, width, _ = pty.Getsize(os.Stdout)
+	}
+	if width < 1 {
+		width = 80
+	}
+
+	w := flowwriter.NewWriter(os.Stdout, width)
+	w.SetNoWrap(pre)
+	if flag.NArg() > 0 {
+		for _, s := range flag.Args() {
+			if f, err := os.Open(s); err == nil {
+				copyin(w, f)
+				f.Close()
+			} else {
+				log.Println(err)
+			}
+		}
+	} else {
+		copyin(w, os.Stdin)
+	}
+}
+
+func copyin(w *flowwriter.Writer, r io.Reader) {
+	if _, err := io.Copy(w, r); err != nil {
+		log.Println(err)
+	}
+	if err := w.Flush(); err != nil {
+		log.Println(err)
+	}
+}
diff --git a/flowwriter/flow.go b/flowwriter/flow.go
new file mode 100755
index 0000000..c215d94
--- /dev/null
+++ b/flowwriter/flow.go
@@ -0,0 +1,117 @@
+package flowwriter
+
+import (
+	"bytes"
+	"github.com/kr/text/indentwriter"
+	"github.com/kr/text/wrapwriter"
+	"io"
+	"strings"
+	"unicode/utf8"
+)
+
+type Writer struct {
+	width  int
+	hang   string
+	indent string
+	chars   string // hang + indent
+	repl rune   // last rune in indent
+	noWrap string
+
+	sec  [][]byte
+	buf  []byte
+	pre0 []byte
+	pre  []byte
+
+	w io.Writer
+}
+
+func NewWriter(w io.Writer, width int) *Writer {
+	nw := new(Writer)
+	nw.w = w
+	nw.width = width
+	nw.hang = "-*"
+	nw.indent = "> "
+	nw.initChars()
+	return nw
+}
+
+func (w *Writer) SetHang(chars string) {
+	w.hang = chars
+	w.initChars()
+}
+
+func (w *Writer) SetIndent(chars string) {
+	w.indent = chars
+	w.initChars()
+}
+
+func (w *Writer) initChars() {
+	w.chars = w.hang + w.indent
+	w.repl, _ = utf8.DecodeLastRuneInString(w.indent)
+}
+
+func (w *Writer) SetNoWrap(prefix string) {
+	w.noWrap = prefix
+}
+
+func (w *Writer) Write(p []byte) (n int, err error) {
+	for i, c := range p {
+		w.buf = append(w.buf, c)
+		if c == '\n' {
+			body := bytes.TrimLeft(w.buf, w.chars)
+			pre := w.buf[:len(w.buf)-len(body)]
+			if !bytes.Equal(pre, w.pre) {
+				if err := w.flow(); err != nil {
+					return i - len(w.buf), err
+				}
+				w.pre0 = pre
+				w.pre = w.tr(pre)
+			}
+			w.sec = append(w.sec, body)
+			w.buf = nil
+		}
+	}
+	return len(p), nil
+}
+
+func (w *Writer) Flush() error {
+	// TODO(kr): handle incomplete last line
+	return w.flow()
+}
+
+func (w *Writer) tr(s []byte) []byte {
+	f := func(r rune) rune {
+		if strings.ContainsRune(w.hang, r) {
+			return w.repl
+		}
+		return r
+	}
+	return bytes.Map(f, s)
+}
+
+func (w *Writer) flow() (err error) {
+	sec := w.sec
+	w.sec = nil
+	n := w.width - len(w.pre0)
+	dst := indentwriter.NewWriter(w.w, [][]byte{w.pre0, w.pre})
+	if w.noWrap == "" || string(w.pre) != w.noWrap {
+		dst = wrapwriter.NewWriter(dst, n)
+	}
+	for _, s := range sec {
+		if _, err = dst.Write(s); err != nil {
+			return err
+		}
+	}
+	if f, ok := dst.(flusher); ok {
+		f.Flush()
+	}
+	return nil
+}
+
+func Wrapper(w io.Writer) io.Writer {
+	return w
+}
+
+type flusher interface {
+	Flush() error
+}
diff --git a/flowwriter/flow_test.go b/flowwriter/flow_test.go
new file mode 100644
index 0000000..ab1d4bf
--- /dev/null
+++ b/flowwriter/flow_test.go
@@ -0,0 +1,77 @@
+package flowwriter
+
+import (
+	"bytes"
+	"testing"
+)
+
+type Test struct {
+	n int
+	d string
+	w string
+}
+
+var ts = []Test{
+	{9, `
+a b c d e f
+  - a b c d
+    e f
+a b c d e f
+`,
+		`
+a b c d e
+f
+  - a b c
+    d e f
+a b c d e
+f
+`},
+
+	{9, `
+a b c d e f
+> - a b c d
+>   e f
+a b c d e f
+`,
+		`
+a b c d e
+f
+> - a b c
+>   d e f
+a b c d e
+f
+`},
+
+	{9, `
+a b c d e f
+> - a b c d
+a b c d e f
+`,
+		`
+a b c d e
+f
+> - a b c
+>   d
+a b c d e
+f
+`},
+}
+
+func TestIndent(t *testing.T) {
+	for _, ts := range ts {
+		b := new(bytes.Buffer)
+		w := NewWriter(b, ts.n, "-", "> ", "")
+		if _, err := w.Write([]byte(ts.d)); err != nil {
+			t.Error(err)
+		}
+		if err := w.Flush(); err != nil {
+			t.Error(err)
+		}
+		if g := b.String(); g != ts.w {
+			t.Logf("%q != %q", g, ts.w)
+			t.Log("\n" + g)
+			t.Log("\n" + ts.w)
+			t.Fail()
+		}
+	}
+}
diff --git a/fulltest.go.waiting b/fulltest.go.waiting
new file mode 100644
index 0000000..1eb14dd
--- /dev/null
+++ b/fulltest.go.waiting
@@ -0,0 +1,61 @@
+package text
+
+import (
+	"bytes"
+	"testing"
+)
+
+type Test struct {
+	n int
+	d string
+	w string
+}
+
+ts := []Test{
+	{9, `
+a b c d e f
+  - a b c d
+    e f
+a b c d e f
+`,
+	`
+a b c d e
+f
+  - a b c
+    d e f
+a b c d e
+f
+`},
+
+	{9, `
+a b c d e f
+> - a b c d
+>   e f
+a b c d e f
+`,
+	`
+a b c d e
+f
+> - a b c
+>   d e f
+a b c d e
+f
+`},
+
+	{9, `
+a b c d e f
+> - a b c d
+a b c d e f
+`,
+	`
+a b c d e
+f
+> - a b c
+>   d
+a b c d e
+f
+`},
+}
+
+func TestIndent(t *testing.T) {
+}
diff --git a/tab/main.go b/tab/main.go
new file mode 100644
index 0000000..f502e1c
--- /dev/null
+++ b/tab/main.go
@@ -0,0 +1,47 @@
+// Command tab translates tabs to spaces.
+//
+//   Usage: tab [file...]
+//
+// Uses the elastic tabs algorithm. See tabwriter and original
+// source.
+package main
+
+import (
+	"io"
+	"log"
+	"os"
+	"text/tabwriter"
+)
+
+var (
+	minw      = 2
+	tabw      = 4
+	npad      = 2
+	padc byte = ' '
+	flag uint = 0
+)
+
+func main() {
+	args := os.Args[1:]
+	w := tabwriter.NewWriter(os.Stdout, minw, tabw, npad, padc, flag)
+	if len(args) > 0 {
+		for _, s := range args {
+			if f, err := os.Open(s); err == nil {
+				copyin(w, f)
+			} else {
+				log.Println(err)
+			}
+		}
+	} else {
+		copyin(w, os.Stdin)
+	}
+}
+
+func copyin(w *tabwriter.Writer, r io.Reader) {
+	if _, err := io.Copy(w, r); err != nil {
+		log.Println(err)
+	}
+	if err := w.Flush(); err != nil {
+		log.Println(err)
+	}
+}
diff --git a/wrapwriter/wrap.go b/wrapwriter/wrap.go
new file mode 100755
index 0000000..47e0cab
--- /dev/null
+++ b/wrapwriter/wrap.go
@@ -0,0 +1,125 @@
+package wrapwriter
+
+import (
+	"bytes"
+	"io"
+	"math"
+)
+
+var (
+	nl = []byte{'\n'}
+	sp = []byte{' '}
+)
+
+type Writer struct {
+	width int
+	buf   []byte
+	bol   bool
+	w     io.Writer
+}
+
+func NewWriter(w io.Writer, width int) *Writer {
+	return &Writer{width: width, w: w, bol: true}
+}
+
+func (w *Writer) Write(p []byte) (n int, err error) {
+	for _, c := range p {
+		w.buf = append(w.buf, c)
+		if c == '\n' && w.bol {
+			if err = w.wrap(); err != nil {
+				return 0, err
+			}
+			if _, err = w.w.Write(nl); err != nil {
+				return 0, err
+			}
+		}
+		w.bol = c == '\n'
+	}
+	return len(p), nil
+}
+
+func (w *Writer) Flush() error {
+	return w.wrap()
+}
+
+func (w *Writer) wrap() (err error) {
+	b := bytes.TrimSpace(w.buf)
+	w.buf = nil
+	if len(b) < 1 {
+		return nil
+	}
+	words := bytes.Split(bytes.Replace(b, nl, sp, -1), sp)
+	for _, line := range wrapWords(words, w.width) {
+		for i, word := range line {
+			if _, err = w.w.Write(word); err != nil {
+				return err
+			}
+			if i == len(line)-1 {
+				_, err = w.w.Write(nl)
+			} else {
+				_, err = w.w.Write(sp)
+			}
+			if err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
+
+// wrapWords is the low-level line-breaking algorithm, useful if you need more
+// control over the details of the text wrapping process. For most uses, either
+// Wrap or WrapBytes will be sufficient and more convenient. 
+//
+// wrapWords splits a list of words into lines with minimal "raggedness",
+// treating each byte as one unit, accounting for 1 unit between adjacent
+// words on each line, and attempting to limit lines to lim units. Raggedness
+// is the total error over all lines, where error is the square of the
+// difference of the length of the line and lim. Too-long lines (which only
+// happen when a single word is longer than lim units) have lim**2 penalty
+// units added to the error.
+// TODO(kr): count runes instead of bytes for width
+func wrapWords(words [][]byte, lim int) [][][]byte {
+	pen := lim * lim
+	n := len(words)
+	length := make([][]int, n)
+	for i := 0; i < n; i++ {
+		length[i] = make([]int, n)
+		length[i][i] = len(words[i])
+		for j := i + 1; j < n; j++ {
+			length[i][j] = length[i][j-1] + 1 + len(words[j])
+		}
+	}
+
+	nbrk := make([]int, n)
+	cost := make([]int, n)
+	for i := range cost {
+		cost[i] = math.MaxInt32
+	}
+	for i := n - 1; i >= 0; i-- {
+		if length[i][n-1] <= lim {
+			cost[i] = 0
+			nbrk[i] = n
+		} else {
+			for j := i + 1; j < n; j++ {
+				d := lim - length[i][j-1]
+				c := d*d + cost[j]
+				if length[i][j-1] > lim {
+					c += pen // too-long lines get a worse penalty
+				}
+				if c < cost[i] {
+					cost[i] = c
+					nbrk[i] = j
+				}
+			}
+		}
+	}
+
+	var lines [][][]byte
+	i := 0
+	for i < n {
+		lines = append(lines, words[i:nbrk[i]])
+		i = nbrk[i]
+	}
+	return lines
+}
diff --git a/wrapwriter/wrap_test.go b/wrapwriter/wrap_test.go
new file mode 100644
index 0000000..cea2d0b
--- /dev/null
+++ b/wrapwriter/wrap_test.go
@@ -0,0 +1,41 @@
+package wrapwriter
+
+import (
+	"bytes"
+	"testing"
+)
+
+const text = "The quick brown fox jumps over the lazy dog."
+
+type Test struct {
+	n   int
+	in  string
+	exp string
+}
+
+var ts = []Test{
+	{24, text, "The quick brown fox\njumps over the lazy dog.\n"},
+	{5, text, "The\nquick\nbrown\nfox\njumps\nover\nthe\nlazy\ndog.\n"},
+	{500, text, "The quick brown fox jumps over the lazy dog.\n"},
+	{9, "\na b c d e f\n", "\na b c d e\nf\n"},
+	{9, "\na b c d e f\n\na b c d e f\n", "\na b c d e\nf\n\na b c d e\nf\n"},
+}
+
+func TestWrap(t *testing.T) {
+	for _, ts := range ts {
+		b := new(bytes.Buffer)
+		w := NewWriter(b, ts.n)
+		if _, err := w.Write([]byte(ts.in)); err != nil {
+			t.Error(err)
+		}
+		if err := w.Flush(); err != nil {
+			t.Error(err)
+		}
+		got := b.String()
+		if got != ts.exp {
+			t.Errorf("%q != %q", got, ts.exp)
+			t.Log(got)
+			t.Log(ts.exp)
+		}
+	}
+}