func (w *Win) PrintTabbed(text string) {
	tab, font, _ := w.Font()
	lines := strings.SplitAfter(text, "\n")
	var allRows [][]string
	for _, line := range lines {
		if line == "" {
			continue
		}
		line = strings.TrimSuffix(line, "\n")
		allRows = append(allRows, strings.Split(line, "\t"))
	}
	var buf bytes.Buffer
	for len(allRows) > 0 {
		if row := allRows[0]; len(row) <= 1 {
			if len(row) > 0 {
				buf.WriteString(row[0])
			}
			buf.WriteString("\n")
			allRows = allRows[1:]
			continue
		}
		i := 0
		for i < len(allRows) && len(allRows[i]) > 1 {
			i++
		}
		rows := allRows[:i]
		allRows = allRows[i:]
		var wid []int
		if font != nil {
			for _, row := range rows {
				for len(wid) < len(row) {
					wid = append(wid, 0)
				}
				for i, col := range row {
					n := font.StringWidth(col)
					if wid[i] < n {
						wid[i] = n
					}
				}
			}
		}
		for _, row := range rows {
			for i, col := range row {
				buf.WriteString(col)
				if i == len(row)-1 {
					break
				}
				if font == nil || tab == 0 {
					buf.WriteString("\t")
					continue
				}
				pos := font.StringWidth(col)
				for pos <= wid[i] {
					buf.WriteString("\t")
					pos += tab - pos%tab
				}
			}
			buf.WriteString("\n")
		}
	}
	w.Write("body", buf.Bytes())
}
PrintTabbed prints tab-separated columnated text to body,
replacing single tabs with runs of tabs as needed to align columns.
package acme  
import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"
	"9fans.net/go/draw"
	"9fans.net/go/plan9"
	"9fans.net/go/plan9/client"
)
type Win struct {
	id         int
	ctl        *client.Fid
	tag        *client.Fid
	body       *client.Fid
	addr       *client.Fid
	event      *client.Fid
	data       *client.Fid
	xdata      *client.Fid
	errors     *client.Fid
	ebuf       *bufio.Reader
	c          chan *Event
	next, prev *Win
	buf        []byte
	e2, e3, e4 Event
	name       string
	errorPrefix string
}
var windowsMu sync.Mutex
var windows, last *Win
var fsys *client.Fsys
var fsysErr error
var fsysOnce sync.Once
func mountAcme() {
	fsys, fsysErr = client.MountService("acme")
}
func New() (*Win, error) {
	fsysOnce.Do(mountAcme)
	if fsysErr != nil {
		return nil, fsysErr
	}
	fid, err := fsys.Open("new/ctl", plan9.ORDWR)
	if err != nil {
		return nil, err
	}
	buf := make([]byte, 100)
	n, err := fid.Read(buf)
	if err != nil {
		fid.Close()
		return nil, err
	}
	a := strings.Fields(string(buf[0:n]))
	if len(a) == 0 {
		fid.Close()
		return nil, errors.New("short read from acme/new/ctl")
	}
	id, err := strconv.Atoi(a[0])
	if err != nil {
		fid.Close()
		return nil, errors.New("invalid window id in acme/new/ctl: " + a[0])
	}
	return Open(id, fid)
}
type WinInfo struct {
	ID   int
	Name string
}
type LogReader struct {
	f   *client.Fid
	buf [8192]byte
}
func (r *LogReader) Close() error {
	return r.f.Close()
}
type LogEvent struct {
	ID   int
	Op   string
	Name string
}
func (r *LogReader) Read() (LogEvent, error) {
	n, err := r.f.Read(r.buf[:])
	if err != nil {
		return LogEvent{}, err
	}
	f := strings.SplitN(string(r.buf[:n]), " ", 3)
	if len(f) != 3 {
		return LogEvent{}, fmt.Errorf("malformed log event")
	}
	id, _ := strconv.Atoi(f[0])
	op := f[1]
	name := f[2]
	name = strings.TrimSpace(name)
	return LogEvent{id, op, name}, nil
}
func Log() (*LogReader, error) {
	fsysOnce.Do(mountAcme)
	if fsysErr != nil {
		return nil, fsysErr
	}
	f, err := fsys.Open("log", plan9.OREAD)
	if err != nil {
		return nil, err
	}
	return &LogReader{f: f}, nil
}
func Windows() ([]WinInfo, error) {
	fsysOnce.Do(mountAcme)
	if fsysErr != nil {
		return nil, fsysErr
	}
	index, err := fsys.Open("index", plan9.OREAD)
	if err != nil {
		return nil, err
	}
	defer index.Close()
	data, err := ioutil.ReadAll(index)
	if err != nil {
		return nil, err
	}
	var info []WinInfo
	for _, line := range strings.Split(string(data), "\n") {
		f := strings.Fields(line)
		if len(f) < 6 {
			continue
		}
		n, _ := strconv.Atoi(f[0])
		info = append(info, WinInfo{n, f[5]})
	}
	return info, nil
}
func Show(name string) *Win {
	windowsMu.Lock()
	defer windowsMu.Unlock()
	for w := windows; w != nil; w = w.next {
		if w.name == name {
			if err := w.Ctl("show"); err != nil {
				w.dropLocked()
				return nil
			}
			return w
		}
	}
	return nil
}
func Open(id int, ctl *client.Fid) (*Win, error) {
	fsysOnce.Do(mountAcme)
	if fsysErr != nil {
		return nil, fsysErr
	}
	if ctl == nil {
		var err error
		ctl, err = fsys.Open(fmt.Sprintf("%d/ctl", id), plan9.ORDWR)
		if err != nil {
			return nil, err
		}
	}
	w := new(Win)
	w.id = id
	w.ctl = ctl
	w.next = nil
	w.prev = last
	if last != nil {
		last.next = w
	} else {
		windows = w
	}
	last = w
	return w, nil
}
func (w *Win) Addr(format string, args ...interface{}) error {
	return w.Fprintf("addr", format, args...)
}
func (w *Win) CloseFiles() {
	w.ctl.Close()
	w.ctl = nil
	w.body.Close()
	w.body = nil
	w.addr.Close()
	w.addr = nil
	w.tag.Close()
	w.tag = nil
	w.event.Close()
	w.event = nil
	w.ebuf = nil
	w.data.Close()
	w.data = nil
	w.xdata.Close()
	w.xdata = nil
	w.errors.Close()
	w.errors = nil
}
func (w *Win) Ctl(format string, args ...interface{}) error {
	return w.Fprintf("ctl", format+"\n", args...)
}
func (w *Win) Del(sure bool) error {
	cmd := "del"
	if sure {
		cmd = "delete"
	}
	return w.Ctl(cmd)
}
func DeleteAll() {
	for w := windows; w != nil; w = w.next {
		w.Ctl("delete")
	}
}
func (w *Win) OpenEvent() error {
	_, err := w.fid("event")
	return err
}
func (w *Win) fid(name string) (*client.Fid, error) {
	var f **client.Fid
	var mode uint8 = plan9.ORDWR
	switch name {
	case "addr":
		f = &w.addr
	case "body":
		f = &w.body
	case "ctl":
		f = &w.ctl
	case "data":
		f = &w.data
	case "event":
		f = &w.event
	case "tag":
		f = &w.tag
	case "xdata":
		f = &w.xdata
	case "errors":
		f = &w.errors
		mode = plan9.OWRITE
	default:
		return nil, errors.New("unknown acme file: " + name)
	}
	if *f == nil {
		var err error
		*f, err = fsys.Open(fmt.Sprintf("%d/%s", w.id, name), mode)
		if err != nil {
			return nil, err
		}
	}
	return *f, nil
}
func (w *Win) ReadAll(file string) ([]byte, error) {
	f, err := w.fid(file)
	f.Seek(0, 0)
	if err != nil {
		return nil, err
	}
	return ioutil.ReadAll(f)
}
func (w *Win) ID() int {
	return w.id
}
func (w *Win) Name(format string, args ...interface{}) error {
	name := fmt.Sprintf(format, args...)
	if err := w.Ctl("name %s", name); err != nil {
		return err
	}
	w.name = name
	return nil
}
func (w *Win) Fprintf(file, format string, args ...interface{}) error {
	f, err := w.fid(file)
	if err != nil {
		return err
	}
	var buf bytes.Buffer
	fmt.Fprintf(&buf, format, args...)
	_, err = f.Write(buf.Bytes())
	return err
}
func (w *Win) Read(file string, b []byte) (n int, err error) {
	f, err := w.fid(file)
	if err != nil {
		return 0, err
	}
	return f.Read(b)
}
func (w *Win) ReadAddr() (q0, q1 int, err error) {
	f, err := w.fid("addr")
	if err != nil {
		return 0, 0, err
	}
	buf := make([]byte, 40)
	n, err := f.ReadAt(buf, 0)
	if err != nil {
		return 0, 0, err
	}
	a := strings.Fields(string(buf[0:n]))
	if len(a) < 2 {
		return 0, 0, errors.New("short read from acme addr")
	}
	q0, err0 := strconv.Atoi(a[0])
	q1, err1 := strconv.Atoi(a[1])
	if err0 != nil || err1 != nil {
		return 0, 0, errors.New("invalid read from acme addr")
	}
	return q0, q1, nil
}
func (w *Win) Seek(file string, offset int64, whence int) (int64, error) {
	f, err := w.fid(file)
	if err != nil {
		return 0, err
	}
	return f.Seek(offset, whence)
}
func (w *Win) Write(file string, b []byte) (n int, err error) {
	f, err := w.fid(file)
	if err != nil {
		return 0, err
	}
	return f.Write(b)
}
const eventSize = 256
type Event struct {
	C1, C2 rune
	Q0, Q1 int
	OrigQ0, OrigQ1 int
	Flag int
	Nb int
	Nr int
	Text []byte
	Arg []byte
	Loc []byte
}
func (w *Win) ReadEvent() (e *Event, err error) {
	defer func() {
		if v := recover(); v != nil {
			e = nil
			err = errors.New("malformed acme event: " + v.(string))
		}
	}()
	if _, err = w.fid("event"); err != nil {
		return nil, err
	}
	e = new(Event)
	w.gete(e)
	e.OrigQ0 = e.Q0
	e.OrigQ1 = e.Q1
	if e.Flag&2 != 0 {
		e2 := new(Event)
		w.gete(e2)
		if e.Q0 == e.Q1 {
			e2.OrigQ0 = e.Q0
			e2.OrigQ1 = e.Q1
			e2.Flag = e.Flag
			e = e2
		}
	}
	if e.Flag&8 != 0 {
		e3 := new(Event)
		e4 := new(Event)
		w.gete(e3)
		w.gete(e4)
		e.Arg = e3.Text
		e.Loc = e4.Text
	}
	return e, nil
}
func (w *Win) gete(e *Event) {
	if w.ebuf == nil {
		w.ebuf = bufio.NewReader(w.event)
	}
	e.C1 = w.getec()
	e.C2 = w.getec()
	e.Q0 = w.geten()
	e.Q1 = w.geten()
	e.Flag = w.geten()
	e.Nr = w.geten()
	if e.Nr > eventSize {
		panic("event string too long")
	}
	r := make([]rune, e.Nr)
	for i := 0; i < e.Nr; i++ {
		r[i] = w.getec()
	}
	e.Text = []byte(string(r))
	if w.getec() != '\n' {
		panic("phase error")
	}
}
func (w *Win) getec() rune {
	c, _, err := w.ebuf.ReadRune()
	if err != nil {
		panic(err.Error())
	}
	return c
}
func (w *Win) geten() int {
	var (
		c rune
		n int
	)
	for {
		c = w.getec()
		if c < '0' || c > '9' {
			break
		}
		n = n*10 + int(c) - '0'
	}
	if c != ' ' {
		panic("event number syntax")
	}
	return n
}
func (w *Win) WriteEvent(e *Event) error {
	var buf bytes.Buffer
	fmt.Fprintf(&buf, "%c%c%d %d \n", e.C1, e.C2, e.Q0, e.Q1)
	_, err := w.Write("event", buf.Bytes())
	return err
}
func (w *Win) EventChan() <-chan *Event {
	if w.c == nil {
		w.c = make(chan *Event, 0)
		go w.eventReader()
	}
	return w.c
}
func (w *Win) eventReader() {
	for {
		e, err := w.ReadEvent()
		if err != nil {
			break
		}
		w.c <- e
	}
	w.drop()
	close(w.c)
}
func (w *Win) drop() {
	windowsMu.Lock()
	defer windowsMu.Unlock()
	w.dropLocked()
}
func (w *Win) dropLocked() {
	if w.prev == nil && w.next == nil && windows != w {
		return
	}
	if w.prev != nil {
		w.prev.next = w.next
	} else {
		windows = w.next
	}
	if w.next != nil {
		w.next.prev = w.prev
	} else {
		last = w.prev
	}
	w.prev = nil
	w.next = nil
}
var fontCache struct {
	sync.Mutex
	m map[string]*draw.Font
}
func (w *Win) Font() (tab int, font *draw.Font, err error) {
	ctl := make([]byte, 1000)
	w.Seek("ctl", 0, 0)
	n, err := w.Read("ctl", ctl)
	if err != nil {
		return 0, nil, err
	}
	f := strings.Fields(string(ctl[:n]))
	if len(f) < 8 {
		return 0, nil, fmt.Errorf("malformed ctl file")
	}
	tab, _ = strconv.Atoi(f[7])
	if tab == 0 {
		return 0, nil, fmt.Errorf("malformed ctl file")
	}
	name := f[6]
	fontCache.Lock()
	font = fontCache.m[name]
	fontCache.Unlock()
	if font != nil {
		return tab, font, nil
	}
	var disp *draw.Display = nil
	font, err = disp.OpenFont(name)
	if err != nil {
		return tab, nil, err
	}
	fontCache.Lock()
	if fontCache.m == nil {
		fontCache.m = make(map[string]*draw.Font)
	}
	if fontCache.m[name] != nil {
		font = fontCache.m[name]
	} else {
		fontCache.m[name] = font
	}
	fontCache.Unlock()
	return tab, font, nil
}
func (w *Win) Blink() (stop func()) {
	c := make(chan struct{})
	go func() {
		t := time.NewTicker(1000 * time.Millisecond)
		defer t.Stop()
		dirty := false
		for {
			select {
			case <-t.C:
				dirty = !dirty
				if dirty {
					w.Ctl("dirty")
				} else {
					w.Ctl("clean")
				}
			case <-c:
				if dirty {
					w.Ctl("clean")
				}
				c <- struct{}{}
				return
			}
		}
	}()
	return func() {
		c <- struct{}{}
		<-c
	}
}
func (w *Win) Sort(less func(x, y string) bool) error {
	q0, q1, err := w.ReadAddr()
	if err != nil {
		return err
	}
	data, err := w.ReadAll("xdata")
	if err != nil {
		return err
	}
	suffix := ""
	lines := strings.Split(string(data), "\n")
	if lines[len(lines)-1] == "" {
		suffix = "\n"
		lines = lines[:len(lines)-1]
	}
	sort.SliceStable(lines, func(i, j int) bool { return less(lines[i], lines[j]) })
	w.Addr("#%d,#%d", q0, q1)
	w.Write("data", []byte(strings.Join(lines, "\n")+suffix))
	return nil
}