265 lines
5.7 KiB
Go
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:]...)
|
||
|
}
|