func (b *Buffer) Towd(start int, dir int, dontForceAdvance bool) int {
	first := (dir < 0)
	notfirst := !first
	var i int
	for i = start; (i >= 0) && (i < b.Size()); i += dir {
		c := b.At(i)
		if !(unicode.IsLetter(c) || unicode.IsDigit(c) || (c == '_')) {
			if !first && !dontForceAdvance {
				i++
			}
			break
		}
		first = notfirst
	}
	if i < 0 {
		i = 0
	}
	return i
}
Moves to the beginning or end of an alphanumerically delimited word
package buf
import (
	"bufio"
	"crypto/sha1"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"sync"
	"time"
	"unicode"
	"unicode/utf8"
	"github.com/aarzilli/yacco/config"
	"github.com/aarzilli/yacco/hl"
	"github.com/aarzilli/yacco/util"
)
const SLOP = 128
var nonwdRe = regexp.MustCompile(`\W+`)
type Buffer struct {
	Dir           string
	Name          string
	Editable      bool
	EditableStart int
	Modified      bool
	modTime        time.Time  
	onDiskChecksum *[sha1.Size]byte
	Props map[string]string
	buf        []rune
	sels       []*util.Sel
	gap, gapsz int
	ul undoList
	lock sync.RWMutex
	Hl    hl.Highlighter
	hlbuf []uint8
	RevCount int
	Words       []string
	WordsUpdate time.Time
	updcount    int
	EditMark, EditMarkNext bool
	Markat int
	DumpCmd, DumpDir string
}
type BufferSize struct {
	GapUsed, GapGap uintptr
	Words           uintptr
	Undo            uintptr
}
func NewBuffer(dir, name string, create bool, indentchar string, hl hl.Highlighter) (b *Buffer, err error) {
	b = &Buffer{
		Dir:           dir,
		Name:          name,
		Editable:      true,
		EditableStart: -1,
		Modified:      false,
		RevCount: 0,
		Hl: hl,
		buf:   make([]rune, SLOP),
		gap:   0,
		gapsz: SLOP,
		Markat: -1,
		ul: undoList{0, []undoInfo{}, true}}
	dirfile, err := os.Open(dir)
	if err != nil {
		return nil, err
	}
	defer dirfile.Close()
	dirinfo, err := dirfile.Stat()
	if err != nil {
		return nil, err
	}
	if !dirinfo.IsDir() {
		return nil, fmt.Errorf("Not a directory: %s", dir)
	}
	if name[0] != '+' {
		flag := ReloadFlag(0)
		if create {
			flag |= ReloadCreate
		}
		err := b.Reload(flag)
		if err != nil {
			return nil, err
		}
	}
	b.Props = map[string]string{}
	b.Props["indentchar"] = indentchar
	b.Props["font"] = "main"
	b.Props["indent"] = "on"
	b.Props["tab"] = "8"
	b.EditMarkNext = true
	b.EditMark = true
	b.sels = []*util.Sel{}
	return b, nil
}
func (b *Buffer) AddSel(sel *util.Sel) {
	for i := range b.sels {
		if b.sels[i] == nil {
			b.sels[i] = sel
			return
		}
	}
	b.sels = append(b.sels, sel)
}
func (b *Buffer) RmSel(sel *util.Sel) {
	for i := range b.sels {
		if b.sels[i] == sel {
			b.sels[i] = nil
			break
		}
	}
}
type ReloadFlag uint8
const (
	ReloadCreate ReloadFlag = 1 << iota
	ReloadPreserveCurlineWhitespace
)
func (b *Buffer) Reload(flags ReloadFlag) error {
	create := flags&ReloadCreate != 0
	path := filepath.Join(b.Dir, b.Name)
	infile, err := os.Open(path)
	if err == nil {
		defer infile.Close()
		fi, err := infile.Stat()
		if err != nil {
			return fmt.Errorf("Couldn't stat file (after opening it?) %s", path)
		}
		if fi.IsDir() {
			return b.reloadDir(infile)
		}
		if fi.Size() > 500*1024*1024 {
			return fmt.Errorf("Refusing to open files larger than 500MB")
		}
		bytes, err := ioutil.ReadAll(infile)
		if err != nil {
			return err
		}
		if isBinary(bytes) {
			return fmt.Errorf("Can not open binary file")
		}
		restoreCurline := ""
		if len(b.sels) > 0 && b.sels[0].S == b.sels[0].E {
			s1 := b.Tonl(b.sels[0].S-1, -1)
			if s1 < b.sels[0].S {
				curline := string(b.SelectionRunes(util.Sel{s1, b.sels[0].S}))
				allspace := true
				for _, ch := range curline {
					if ch != ' ' && ch != '\t' {
						allspace = false
						break
					}
				}
				if allspace && b.At(b.sels[0].S) == '\n' {
					restoreCurline = curline
				}
			}
		}
		b.ReplaceFull([]rune(string(bytes)))
		if restoreCurline != "" {
			s1 := b.Tonl(b.sels[0].S-1, -1)
			if s1 == b.sels[0].S {
				b.Replace([]rune(restoreCurline), b.sels[0], false, nil, 0)
			}
		}
		b.modTime = fi.ModTime()
		s1 := sha1.Sum(bytes)
		b.onDiskChecksum = &s1
		b.Modified = false
		if len(b.ul.lst) == 1 {
			b.ul.Reset()
		} else {
			b.ul.lst[b.ul.cur-1].solid = false
		}
		b.ul.SetSaved()
		if len(b.buf)-b.gapsz < 1*1024*1024 {
			str := string(b.SelectionRunes(util.Sel{0, b.Size()}))
			b.Words = util.Dedup(nonwdRe.Split(str, -1))
			b.WordsUpdate = time.Now()
		}
	} else {
		if create {
			b.Modified = true
			b.ul.nilIsSaved = false
			b.modTime = time.Now()
		} else {
			return fmt.Errorf("File doesn't exist: %s", path)
		}
	}
	return nil
}
func isBinary(bytes []byte) bool {
	testb := bytes
	if len(testb) > 1024 {
		testb = testb[:1024]
	}
	good := 0
	bad := 0
	for len(testb) > 0 {
		r, size := utf8.DecodeRune(testb)
		testb = testb[size:]
		if r == utf8.RuneError || r == 0 {
			bad++
		} else {
			good++
		}
	}
	if bad == 0 {
		return false
	}
	return bad > good
}
func (b *Buffer) reloadDir(fh *os.File) error {
	if b.Name[len(b.Name)-1] != '/' {
		b.Name = b.Name + "/"
	}
	fis, err := fh.Readdir(-1)
	if err != nil {
		return err
	}
	r := make([]string, 0, len(fis))
	for _, fi := range fis {
		n := fi.Name()
		if config.HideHidden && (len(n) <= 0 || n[0] == '.') {
			continue
		}
		switch {
		case fi.IsDir():
			n += "/"
		case fi.Mode()&os.ModeSymlink != 0:
			n += "@"
		case fi.Mode()&0111 != 0:
			n += "*"
		}
		r = append(r, n)
	}
	b.Replace([]rune(strings.Join(r, "\t")), &util.Sel{0, b.Size()}, true, nil, 0)
	b.Modified = false
	return nil
}
func (b *Buffer) ReplaceFull(text []rune) {
	saveSels := b.saveSels()
	b.Replace(text, &util.Sel{0, b.Size()}, true, nil, 0)
	b.restoreSels(saveSels)
}
func (b *Buffer) Replace(text []rune, sel *util.Sel, solid bool, eventChan chan string, origin util.EventOrigin) {
	if !b.Editable {
		return
	}
	b.FixSel(sel)
	if sel.S < 0 {
		sel.S = sel.E
	}
	if sel.S < b.EditableStart {
		sel.S = b.EditableStart
		sel.E = b.EditableStart
		return
	}
	b.wrlock()
	b.Modified = true
	osel := *sel
	b.pushUndo(*sel, text, solid)
	b.replaceIntl(text, sel)
	b.updateSels(sel, len(text))
	b.unlock()
	sel.S = sel.S + len(text)
	sel.E = sel.S
	b.generateEvent(text, osel, eventChan, origin)
	if origin == util.EO_FILES {
		b.updcount += len(text)
		if b.updcount > 2*1024 {
			b.updcount = 0
			b.UpdateWords()
		}
	}
}
func (b *Buffer) generateEvent(text []rune, sel util.Sel, eventChan chan string, origin util.EventOrigin) {
	if eventChan == nil {
		return
	}
	if sel.S != sel.E {
		util.FmteventBase(eventChan, origin, b.Name == "+Tag", util.ET_BODYDEL, sel.S, sel.E, "", func() {})
	}
	if (sel.S == sel.E) || (len(text) != 0) {
		util.FmteventBase(eventChan, origin, b.Name == "+Tag", util.ET_BODYINS, sel.S, sel.S, string(text), func() {})
	}
}
func (b *Buffer) Undo(sel *util.Sel, redo bool) {
	if !b.Editable {
		return
	}
	first := true
	for {
		var ui *undoInfo
		if redo {
			ui = b.ul.PeekUndo()
			if (ui != nil) && ui.solid && !first {
				return
			}
			ui = b.ul.Redo()
		} else {
			ui = b.ul.Undo()
		}
		if ui == nil {
			return
		}
		first = false
		b.wrlock()
		var us undoSel
		var text []rune
		if redo {
			us = ui.before
			text = []rune(ui.after.text)
		} else {
			us = ui.after
			text = []rune(ui.before.text)
		}
		ws := util.Sel{us.S, us.E}
		b.replaceIntl(text, &ws)
		b.updateSels(&ws, len(text))
		b.unlock()
		sel.S = ws.S
		sel.E = ws.S + len(text)
		mui := ui
		if !redo {
			mui = b.ul.PeekUndo()
		}
		if mui == nil {
			b.Modified = !b.ul.nilIsSaved
		} else {
			b.Modified = !mui.saved
		}
		if !redo {
			if ui.solid {
				return
			}
		}
	}
}
func (b *Buffer) LastEdit() time.Time {
	ui := b.ul.PeekUndo()
	if ui == nil {
		return time.Unix(0, 0)
	} else {
		return ui.ts
	}
}
func (b *Buffer) LastEditIsType(rev int) int {
	if rev != b.RevCount-1 {
		return -1
	}
	ui := b.ul.PeekUndo()
	if ui == nil {
		return -1
	}
	d := b.RevCount - ui.rev
	if (ui.before.S != ui.before.E) || (ui.before.S != ui.after.S) || (ui.after.S != ui.after.E-d) || (strings.Index(ui.after.text, "\n") >= 0) {
		return -1
	}
	return ui.after.E - 1
}
func (b *Buffer) Rdlock() {
	b.lock.RLock()
}
func (b *Buffer) Rdunlock() {
	b.lock.RUnlock()
}
func (b *Buffer) wrlock() {
	b.lock.RLock()
	b.lock.RUnlock()
	b.lock.Lock()
}
func (b *Buffer) unlock() {
	b.lock.Unlock()
}
func (b *Buffer) replaceIntl(text []rune, sel *util.Sel) {
	regionSize := sel.E - sel.S
	if sel.S != sel.E {
		b.updateSels(sel, -regionSize)
		b.MoveGap(sel.S)
		b.gapsz += regionSize  
	} else {
		b.MoveGap(sel.S)
	}
	b.IncGap(len(text))
	for i, r := range text {
		b.buf[b.gap+i] = r
	}
	b.gap += len(text)
	b.gapsz -= len(text)
	b.Hl.Alter(sel.S - 1)
	b.RevCount++
}
func (b *Buffer) pushUndo(sel util.Sel, text []rune, solid bool) {
	var ui undoInfo
	ui.rev = b.RevCount
	ui.before.S = sel.S
	ui.before.E = sel.E
	ui.before.text = string(b.SelectionRunes(sel))
	ui.after.S = sel.S
	ui.after.E = sel.S + len(text)
	ui.after.text = string(text)
	ui.solid = solid
	b.ul.Add(ui)
}
func (b *Buffer) updateSels(sel *util.Sel, delta int) {
	var end int
	if delta < 0 {
		end = sel.S - delta
	} else {
		end = -1
	}
	s := b.Size()
	sels := b.sels
	for i := range sels {
		if sels[i] == nil {
			continue
		}
		if sels[i] == sel {
			continue
		}
		if (sels[i].S >= sel.S) && (sels[i].S <= end) {
			sels[i].S = sel.S
		} else if sels[i].S > sel.S {
			sels[i].S += delta
		}
		if (sels[i].E >= sel.S) && (sels[i].E <= end) {
			sels[i].E = sel.S
		} else if sels[i].E > sel.S {
			sels[i].E += delta
		}
		if sels[i].E > s {
			sels[i].E = s
		}
		if sels[i].S > s {
			sels[i].S = s
		}
	}
}
func (b *Buffer) IncGap(delta int) {
	if b.gapsz > delta+1 {
		return
	}
	ngapsz := (delta/SLOP + 1) * SLOP
	nbuf := make([]rune, len(b.buf)-b.gapsz+ngapsz)
	copy(nbuf, b.buf[:b.gap])
	copy(nbuf[b.gap+ngapsz:], b.buf[b.gap+b.gapsz:])
	b.buf = nbuf
	b.gapsz = ngapsz
}
func (b *Buffer) MoveGap(p int) {
	pp := b.phisical(p)
	if pp > len(b.buf) {
		panic(fmt.Errorf("MoveGap point out of range: %d", pp))
	}
	if pp < b.gap {
		if b.gap-pp > 0 {
			copy(b.buf[pp+b.gapsz:], b.buf[pp:b.gap])
		}
		b.gap = pp
	} else if pp > b.gap {
		if pp-b.gap-b.gapsz > 0 {
			copy(b.buf[b.gap:], b.buf[b.gap+b.gapsz:pp])
		}
		b.gap = pp - b.gapsz
	}
}
func (b *Buffer) phisical(p int) int {
	if p < b.gap {
		return p
	} else {
		return p + b.gapsz
	}
}
func (b *Buffer) At(p int) rune {
	pp := b.phisical(p)
	if (pp < 0) || (pp >= len(b.buf)) {
		return 0
	}
	return b.buf[pp]
}
func (b *Buffer) Selection(sel util.Sel) ([]rune, []rune) {
	b.FixSel(&sel)
	ps := b.phisical(sel.S)
	pe := b.phisical(sel.E)
	if ps < 0 {
		ps = 0
	}
	if pe > len(b.buf) {
		pe = len(b.buf)
	}
	if (ps < b.gap) && (pe >= b.gap) {
		return b.buf[ps:b.gap], b.buf[b.gap+b.gapsz : pe]
	} else {
		if pe <= ps {
			return []rune{}, []rune{}
		} else {
			return b.buf[ps:pe], []rune{}
		}
	}
}
func (b *Buffer) SelectionRunes(sel util.Sel) []rune {
	ba, bb := b.Selection(sel)
	r := make([]rune, 0, len(ba)+len(bb))
	r = append(r, ba...)
	r = append(r, bb...)
	return r
}
func (b *Buffer) ByteOffset(p int) int {
	n := 0
	ba, bb := b.Selection(util.Sel{0, p})
	for _, bcur := range [][]rune{ba, bb} {
		for _, ch := range bcur {
			switch {
			case ch <= 0x7f:
				n++
			case ch <= 0x7FF:
				n += 2
			case ch <= 0xFFFF:
				n += 3
			case ch <= 0x10FFFF:
				n += 4
			}
		}
	}
	return n
}
func (b *Buffer) Size() int {
	return len(b.buf) - b.gapsz
}
func (b *Buffer) Tonl(start int, dir int) int {
	sz := b.Size()
	ba, bb := b.Selection(util.Sel{0, sz})
	i := start
	if i < 0 {
		return 0
	}
	if i >= sz {
		i = sz - 1
	}
	for ; (i >= 0) && (i < sz); i += dir {
		var c rune
		if i < len(ba) {
			c = ba[i]
		} else {
			c = bb[i-len(ba)]
		}
		if c == '\n' {
			return i + 1
		}
	}
	if dir < 0 {
		return 0
	} else {
		return sz
	}
}