diff --git a/go.mod b/go.mod index 13f5067..85cc153 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module git.yetaga.in/alazyreader/prosper go 1.15 + +require github.com/peterh/liner v1.2.1 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..83a7dd4 --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4= +github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/peterh/liner v1.2.1 h1:O4BlKaq/LWu6VRWmol4ByWfzx6MfXc5Op5HETyIy5yg= +github.com/peterh/liner v1.2.1/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= diff --git a/main.go b/main.go index f81afd0..11e35ee 100644 --- a/main.go +++ b/main.go @@ -1,12 +1,14 @@ package main import ( - "bufio" "errors" "flag" "fmt" "os" + "path/filepath" "strings" + + "github.com/peterh/liner" ) type libraryFiles []string @@ -20,10 +22,41 @@ func (l *libraryFiles) Set(value string) error { return nil } +func historyFn() string { + dataHome := os.Getenv("XDG_DATA_HOME") + home := os.Getenv("HOME") + if dataHome == "" { + dataHome = filepath.Join(home, ".local/share/") + } + prosperDir := filepath.Join(dataHome, "prosper") + os.MkdirAll(prosperDir, 0755) + return filepath.Join(prosperDir, ".history") +} + +func completer(d Dictionary) liner.Completer { + return func(line string) []string { + candidates := []string{} + words := strings.Split(line, " ") + for w := range d { + if strings.HasPrefix(w, words[len(words)-1]) { + // must reconstruct the _full_ line to return for the completer + pref := strings.Join(words[:len(words)-1], " ") + if len(pref) != 0 { + candidates = append(candidates, pref+" "+w) + } else { + candidates = append(candidates, w) + } + } + } + return candidates + } +} + func main() { // flag setup var libs libraryFiles flag.Var(&libs, "l", "propser library file (may be repeated)") + debug := flag.Bool("debug", false, "output debugging information") flag.Parse() // create main context @@ -64,27 +97,60 @@ func main() { } } - reader := bufio.NewReader(os.Stdin) + // set up liner + line := liner.NewLiner() + line.SetCtrlCAborts(true) + line.SetCompleter(completer(dict)) + + historyFile := historyFn() + + if f, err := os.Open(historyFile); err == nil { + _, err := line.ReadHistory(f) + if err != nil && *debug { + fmt.Printf("error reading line history: %v", err) + } + f.Close() + } + + defer func() { + if f, err := os.Create(historyFile); err != nil { + if *debug { + fmt.Printf("Error writing history file: %v", err) + } + } else { + line.WriteHistory(f) + f.Close() + } + line.Close() + }() + + // Welcome banner fmt.Print("prosper\n") // read loop for { - if c.Flags["Immediate"] { - fmt.Print("> ") - } else { - fmt.Print(" ") + p := "> " + if !c.Flags["Immediate"] { + p = " " } - line, err := reader.ReadString('\n') - if err != nil { + // read line + l, err := line.Prompt(p) + if err == liner.ErrPromptAborted { + fmt.Println("ctrl-C caught (try BYE)") + continue + } else if err != nil { fmt.Println(err.Error()) os.Exit(1) } - line = strings.TrimSpace(line) - err = c.Eval(line + " ") // append a space to make sure we always close out our parse loop + + // parse line + l = strings.TrimSpace(l) + line.AppendHistory(l) + err = c.Eval(l + " ") // append a space to make sure we always close out our parse loop if errors.Is(err, ErrExit) { fmt.Printf("bye\n") - os.Exit(0) + break } else if err != nil { fmt.Printf("error in evaluation: %v\n", err) } else if c.Flags["Immediate"] {