adventofcode/2019/day25/cli_processor.go
2020-11-06 15:04:08 -06:00

265 lines
5.7 KiB
Go

package main
import (
"errors"
"strings"
"github.com/nsf/termbox-go"
)
type CliProc struct {
History []string
HistoryIdx int
Suggestions []string // Suggestions are 'autocomplete' commands
Proposals []string // Proposals are proposed 'next' commands
Buffer string
Cursor int
Complete bool
}
func NewCLI() *CliProc {
c := CliProc{}
return &c
}
func (c *CliProc) handleEvent(event termbox.Event) error {
if c.Complete {
return nil
}
if event.Ch == 0 {
switch event.Key {
case termbox.KeyEnter:
// Execute command
c.Complete = true
case termbox.KeySpace:
c.putInBuffer(" ")
case termbox.KeyBackspace, termbox.KeyBackspace2:
if c.Cursor > 0 {
c.Buffer = c.Buffer[:c.Cursor-1] + c.Buffer[c.Cursor:]
c.Cursor = c.Cursor - 1
}
case termbox.KeyDelete:
if c.Cursor < len(c.Buffer) {
c.Buffer = c.Buffer[:c.Cursor] + c.Buffer[c.Cursor+1:]
}
case termbox.KeyArrowLeft:
if c.Cursor > 0 {
c.Cursor = c.Cursor - 1
}
case termbox.KeyArrowRight:
if c.Cursor < len(c.Buffer) {
c.Cursor = c.Cursor + 1
} else {
// Complete suggestion
}
case termbox.KeyHome:
c.Cursor = 0
case termbox.KeyEnd:
c.Cursor = len(c.Buffer)
case termbox.KeyArrowUp:
val, err := c.HistoryBack()
if err != nil {
return err
}
c.Buffer = val
c.Cursor = len(c.Buffer)
case termbox.KeyArrowDown:
val, err := c.HistoryForward()
if err != nil {
return err
}
c.Buffer = val
c.Cursor = len(c.Buffer)
case termbox.KeyTab:
// Complete suggestion
args := strings.Split(c.Buffer, " ")
acText := strings.TrimPrefix(c.GetAutocompleteText(), args[len(args)-1])
if len(acText) > 0 {
c.Buffer = c.Buffer + acText
c.Cursor = len(c.Buffer)
}
case termbox.KeyCtrlU:
// Delete everything from the cursor forward
c.Buffer = c.Buffer[c.Cursor:]
c.Cursor = 0
}
} else {
c.putInBuffer(string(event.Ch))
/*
if c.Cursor == len(c.Buffer) {
c.Buffer = c.Buffer + string(event.Ch)
} else {
c.Buffer = c.Buffer[:c.Cursor] + string(event.Ch) + c.Buffer[c.Cursor:]
}
c.Cursor = c.Cursor + 1
*/
}
return nil
}
func (c *CliProc) putInBuffer(v string) {
c.Buffer = c.Buffer[:c.Cursor] + v + c.Buffer[c.Cursor:]
c.Cursor = c.Cursor + 1
}
func (c *CliProc) ClearBuffer() {
c.Buffer = ""
c.Cursor = 0
c.Complete = false
}
func (c *CliProc) GetBuffer() string {
return c.Buffer
}
func (c *CliProc) HistoryBack() (string, error) {
if len(c.History) < c.HistoryIdx+1 {
return "", errors.New("Already at oldest command")
}
c.HistoryIdx = c.HistoryIdx + 1
return c.History[len(c.History)-c.HistoryIdx], nil
}
func (c *CliProc) HistoryForward() (string, error) {
if c.HistoryIdx == 0 {
return "", errors.New("Already at most recent command")
}
c.HistoryIdx = c.HistoryIdx - 1
if c.HistoryIdx == 0 {
return "", nil
}
return c.History[len(c.History)-c.HistoryIdx], nil
}
func (c *CliProc) Suggest(cmd string) string {
allSuggestions := c.GetAllSuggestions(cmd)
if len(allSuggestions) == 0 {
return ""
}
return allSuggestions[0]
}
func (c *CliProc) GetDisplaySuggestions() []string {
var ret []string
v := c.GetAllSuggestions(c.Buffer)
if len(c.Buffer) > 0 {
for k := range v {
if strings.HasPrefix(v[k], c.Buffer) {
ret = append(ret, v[k])
}
}
} else {
// Normally, filter out 'drop's
for k := range v {
if !strings.HasPrefix(v[k], "drop ") {
ret = append(ret, v[k])
}
}
}
return ret
}
func (c *CliProc) GetAllSuggestions(cmd string) []string {
var ret []string
args := strings.Fields(cmd)
if len(args) == 0 {
// Return all commands
for _, v := range c.Suggestions {
ret = append(ret, v)
}
return ret
} else if len(args) == 1 && !strings.HasSuffix(cmd, " ") {
for _, v := range c.Suggestions {
if strings.HasPrefix(v, args[0]) {
ret = append(ret, v)
}
}
}
return ret
}
func (c *CliProc) Draw(x, y int, bg, fg termbox.Attribute) {
DrawString("[ "+strings.Join(c.GetDisplaySuggestions(), ", ")+" ]", x, y, bg, fg)
y = y + 1
prompt := '>'
termbox.SetCell(x, y, prompt, bg, fg)
x = x + 2
for k := range c.Buffer {
useFg, useBg := bg, fg
if k == c.Cursor {
useFg, useBg = fg, bg
}
termbox.SetCell(x+k, y, rune(c.Buffer[k]), useFg, useBg)
}
args := strings.Split(c.Buffer, " ")
acText := strings.TrimPrefix(c.GetAutocompleteText(), args[len(args)-1])
if len(acText) > 0 {
for k := range acText {
useFg, useBg := bg|termbox.AttrBold, fg
if k == 0 && c.Cursor == len(c.Buffer) {
useFg, useBg = fg|termbox.AttrBold, bg
}
termbox.SetCell(x+len(c.Buffer)+k, y, rune(acText[k]), useFg, useBg)
}
} else {
if c.Cursor == len(c.Buffer) {
termbox.SetCell(x+len(c.Buffer), y, ' ', fg, bg)
}
}
}
func (c *CliProc) GetAutocompleteText() string {
suggestions := c.GetAllSuggestions(c.Buffer)
if len(suggestions) == 1 {
args := strings.Split(c.Buffer, " ")
return strings.TrimPrefix(suggestions[0], args[len(args)-1])
} else if len(suggestions) > 1 {
// Find how many characters we _can_ autocomplete
var ret string
for k := range suggestions[0] {
allHave := true
for wrk := range suggestions {
if len(suggestions[wrk]) <= k || suggestions[0][k] != suggestions[wrk][k] {
allHave = false
break
}
}
if allHave {
ret = ret + string(suggestions[0][k])
}
}
return ret
}
return ""
}
func (c *CliProc) ClearSuggestions() {
c.Suggestions = nil
}
func (c *CliProc) AddSuggestion(s string) {
c.AddSuggestions([]string{s})
}
func (c *CliProc) AddSuggestions(s []string) {
c.Suggestions = append(c.Suggestions, s...)
}
func (c *CliProc) RemoveSuggestion(s string) {
var i int
for i = range c.Suggestions {
if c.Suggestions[i] == s {
break
}
}
c.Suggestions = append(c.Suggestions[:i], c.Suggestions[i+1:]...)
}