func (b *Buffer) appendLine(val string) {
	b.modified = true
	b.table.Lines = append(b.table.Lines, piecetable.NewLine(val, b.table))
	b.curs.x = 0
}
appendLine adds a string to the end of
the buffer.
package buff
import (
	"fmt"
	"github.com/felixangell/phi/internal/cfg"
	"github.com/felixangell/phi/internal/command_handler"
	"github.com/felixangell/phi/internal/gui"
	"github.com/felixangell/phi/internal/lex"
	"github.com/felixangell/phi/pkg/piecetable"
	"github.com/felixangell/strife"
	"github.com/lithammer/fuzzysearch/fuzzy"
	"github.com/veandco/go-sdl2/sdl"
	"io/ioutil"
	"log"
	"math"
	"os"
	"path"
	"regexp"
	"runtime"
	"strings"
	"time"
	"unicode"
)
const (
	DefaultScrollAmount = 10
)
var pad = 6
var halfPad = pad / 2
type camera struct {
	x  int
	y  int
	dx int
	dy int
}
type AutoCompleteBox struct {
	vocabularyRegister map[string]bool
	vocabulary         []string
	suggestions        []string
	lastRunes          []rune
}
func (a *AutoCompleteBox) hasSuggestions() bool {
	return len(a.suggestions) > 0
}
func (a *AutoCompleteBox) process(r rune) {
	if r == ' ' || !unicode.IsLetter(r) {
		if r == ' ' {
			word := string(a.lastRunes)
			if _, ok := a.vocabularyRegister[word]; !ok {
				a.vocabulary = append(a.vocabulary, word)
				a.vocabularyRegister[word] = true
			}
		}
		a.lastRunes = []rune{}
		return
	}
	a.lastRunes = append(a.lastRunes, r)
	if len(a.lastRunes) <= 3 {
		return
	}
	word := string(a.lastRunes)
	results := fuzzy.RankFind(word, a.vocabulary)
	a.suggestions = []string{}
	for _, res := range results {
		a.suggestions = append(a.suggestions, res.Target)
	}
}
func (a *AutoCompleteBox) renderAt(x, y int, ctx *strife.Renderer) {
	height := lastCharH + pad
	itemCount := len(a.suggestions)
	ctx.SetColor(strife.HexRGB(0x000000))
	ctx.Rect(x, y, 120, height*itemCount, strife.Fill)
	ctx.SetColor(strife.HexRGB(0xffffff))
	for idx, sugg := range a.suggestions {
		ctx.Text(sugg, x, y+(idx*height))
	}
}
func newAutoCompleteBox() *AutoCompleteBox {
	return &AutoCompleteBox{
		map[string]bool{},
		[]string{},
		[]string{},
		[]rune{},
	}
}
type BufferConfig struct {
	background        uint32
	foreground        uint32
	cursor            uint32
	cursorInvert      uint32
	highlightLine     uint32
	lineNumBackground uint32
	lineNumForeground uint32
	font              *strife.Font
}
type Buffer struct {
	gui.BaseComponent
	index        int
	parent       *BufferView
	curs         *Cursor
	cfg          *cfg.PhiEditorConfig
	buffOpts     BufferConfig
	cam          *camera
	table        *piecetable.PieceTable
	filePath     string
	languageInfo *cfg.LanguageSyntaxConfig
	ex, ey       int
	modified     bool
	autoComplete *AutoCompleteBox
}
func NewBuffer(conf *cfg.PhiEditorConfig, buffOpts BufferConfig, parent *BufferView, index int) *Buffer {
	config := conf
	if config == nil {
		config = cfg.NewDefaultConfig()
	}
	curs := sdl.CreateSystemCursor(sdl.SYSTEM_CURSOR_IBEAM)
	sdl.SetCursor(curs)
	buff := &Buffer{
		index:        index,
		parent:       parent,
		curs:         nil,
		cfg:          config,
		table:        piecetable.MakePieceTable(""),
		buffOpts:     buffOpts,
		filePath:     "",
		cam:          &camera{0, 0, 0, 0},
		autoComplete: newAutoCompleteBox(),
	}
	buff.curs = newCursor(buff)
	return buff
}
type selection struct {
	parent *Buffer
	sx, sy int
	ex, ey int
}
func (s *selection) renderAt(ctx *strife.Renderer, _ int, _ int) {
	ctx.SetColor(strife.Blue)
	yd := (s.ey - s.sy) + 1
	b := s.parent
	for y := 0; y < yd; y++ {
		lineLen := s.parent.table.Lines[s.sy+y].Len()
		if lineLen == 0 {
			lineLen = 1
		}
		height := (lastCharH + pad) - b.ey
		width := lineLen * lastCharW
		if y == yd-1 {
			width = s.ex * lastCharW
		}
		xPos := s.sx * lastCharW
		yPos := (s.sy + y) * (lastCharH + pad)
		ctx.SetColor(strife.Blue)
		ctx.Rect(b.ex+xPos, b.ey+yPos, width, height, strife.Fill)
	}
}
func (b *Buffer) reload() {
	if _, err := os.Stat(b.filePath); os.IsNotExist(err) {
		log.Println("File does not exist when reloading?! " + b.filePath)
		return
	}
	if b.modified {
		panic("this should show a file modified thing but this has been removed for now!")
	}
	contents, err := ioutil.ReadFile(b.filePath)
	if err != nil {
		panic(err)
	}
	b.table = piecetable.MakePieceTable(string(contents))
	b.modified = false
}
func (b *Buffer) OpenFile(filePath string) {
	b.filePath = filePath
	log.Println("Opening file ", filePath)
	ext := path.Ext(filePath)
	var err error
	b.languageInfo, err = b.cfg.GetSyntaxConfig(ext)
	if err != nil {
		log.Println(err.Error())
	}
	if _, err := os.Stat(filePath); os.IsNotExist(err) {
		f, err := os.Create(filePath)
		if err != nil {
			panic(err)
		} else {
			if err := f.Close(); err != nil {
				panic(err)
			}
		}
	}
	contents, err := ioutil.ReadFile(filePath)
	if err != nil {
		panic(err)
	}
	b.parent.registerFile(filePath, b)
	b.table = piecetable.MakePieceTable(string(contents))
	b.modified = false
}
func (b *Buffer) setLine(idx int, val string) {
	b.modified = true
	b.table.Lines[idx] = piecetable.NewLine(val, b.table)
	if b.curs.y == idx {
		b.moveToEndOfLine()
	}
}
func (b *Buffer) appendLineAt(val *piecetable.Line, idx int) {
	b.modified = true
	b.table.Lines = append(b.table.Lines, val)
	copy(b.table.Lines[idx+1:], b.table.Lines[idx:])
	b.table.Lines[idx] = val
}
func (b *Buffer) appendStringAt(val string, idx int) {
	b.appendLineAt(piecetable.NewLine(val, b.table), idx)
}