From 77eee7bf6d25ac9323fccdf2c2c6e1dcb6acebe2 Mon Sep 17 00:00:00 2001 From: Laurent Demailly Date: Thu, 8 Aug 2024 09:48:48 -0700 Subject: [PATCH] Use/demo history from PR for https://github.com/golang/go/issues/68780 --- example/main.go | 4 ++- go.mod | 1 + go.sum | 4 +-- terminal.go | 72 ++++++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 74 insertions(+), 7 deletions(-) diff --git a/example/main.go b/example/main.go index 827fb19..654713e 100644 --- a/example/main.go +++ b/example/main.go @@ -2,6 +2,7 @@ package main import ( "errors" + "flag" "fmt" "io" "os" @@ -56,7 +57,7 @@ func autoCompleteCallback(line string, pos int, key rune) (newLine string, newPo func Main() int { // Pending https://github.com/golang/go/issues/68780 - // flagHistory := flag.String("history", "", "History `file` to use") + flagHistory := flag.String("history", "/tmp/terminal_history", "History `file` to use") cli.Main() t, err := terminal.Open() if err != nil { @@ -65,6 +66,7 @@ func Main() int { defer t.Close() t.SetPrompt("Terminal demo> ") t.LoggerSetup() + t.SetHistoryFile(*flagHistory) fmt.Fprintf(t.Out, "Terminal is open\nis valid %t\nuse exit or ^D or ^C to exit\n", t.IsTerminal()) fmt.Fprintf(t.Out, "Use 'prompt ' to change the prompt\n") fmt.Fprintf(t.Out, "Try 'after duration text...' to see text showing in the middle of edits after said duration\n") diff --git a/go.mod b/go.mod index 49251d6..b6f19fd 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( ) // replace fortio.org/cli => ../cli +replace golang.org/x/term => github.com/ldemailly/term v0.0.0-20240808164309-1ca6c26d14bf require ( fortio.org/struct2env v0.4.1 // indirect diff --git a/go.sum b/go.sum index 057d009..664b212 100644 --- a/go.sum +++ b/go.sum @@ -8,9 +8,9 @@ fortio.org/version v1.0.4 h1:FWUMpJ+hVTNc4RhvvOJzb0xesrlRmG/a+D6bjbQ4+5U= fortio.org/version v1.0.4/go.mod h1:2JQp9Ax+tm6QKiGuzR5nJY63kFeANcgrZ0osoQFDVm0= github.com/kortschak/goroutine v1.1.2 h1:lhllcCuERxMIK5cYr8yohZZScL1na+JM5JYPRclWjck= github.com/kortschak/goroutine v1.1.2/go.mod h1:zKpXs1FWN/6mXasDQzfl7g0LrGFIOiA6cLs9eXKyaMY= +github.com/ldemailly/term v0.0.0-20240808164309-1ca6c26d14bf h1:tSC3QHTyGX/bno3fLgNBikbqJ5+/qBh8bFJVnCXsBrw= +github.com/ldemailly/term v0.0.0-20240808164309-1ca6c26d14bf/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/crypto/x509roots/fallback v0.0.0-20240626151235-a6a393ffd658 h1:i7K6wQLN/0oxF7FT3tKkfMCstxoT4VGG36YIB9ZKLzI= golang.org/x/crypto/x509roots/fallback v0.0.0-20240626151235-a6a393ffd658/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8= golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= diff --git a/terminal.go b/terminal.go index e9ed945..647f64d 100644 --- a/terminal.go +++ b/terminal.go @@ -2,6 +2,7 @@ package terminal // import "fortio.org/terminal" import ( + "bufio" "io" "os" @@ -10,10 +11,11 @@ import ( ) type Terminal struct { - fd int - oldState *term.State - term *term.Terminal - Out io.Writer + fd int + oldState *term.State + term *term.Terminal + Out io.Writer + historyFile string } // CRWriter is a writer that adds \r before each \n. @@ -100,6 +102,62 @@ func (t *Terminal) LoggerSetup() { log.SetColorMode() } +func (t *Terminal) SetHistoryFile(f string) { + t.historyFile = f + entries := readOrCreateHistory(f) + for _, e := range entries { + t.term.AddToHistory(e) + } + log.Infof("Loaded %d history entries from %s", len(entries), f) +} + +func readOrCreateHistory(f string) []string { + if f == "" { + log.Infof("No history file specified") + return nil + } + // open file or create it + h, err := os.OpenFile(f, os.O_RDWR|os.O_CREATE, 0o600) + if err != nil { + log.Errf("Error opening history file %s: %v", f, err) + return nil + } + defer h.Close() + // read lines separated by \n + var lines []string + scanner := bufio.NewScanner(h) + for scanner.Scan() { + lines = append(lines, scanner.Text()) + } + if err := scanner.Err(); err != nil { + log.Errf("Error reading history file %s: %v", f, err) + return nil + } + return lines +} + +func saveHistory(f string, h []string) { + if f == "" { + log.Infof("No history file specified") + return + } + // open file or create it + hf, err := os.OpenFile(f, os.O_RDWR|os.O_CREATE, 0o600) + if err != nil { + log.Errf("Error opening history file %s: %v", f, err) + return + } + defer hf.Close() + // write lines separated by \n + for _, l := range h { + _, err := hf.WriteString(l + "\n") + if err != nil { + log.Errf("Error writing history file %s: %v", f, err) + return + } + } +} + func (t *Terminal) Close() error { if t.oldState == nil { return nil @@ -107,6 +165,12 @@ func (t *Terminal) Close() error { err := term.Restore(t.fd, t.oldState) t.oldState = nil t.Out = os.Stderr + // saving history if any + if t.historyFile != "" { + h := t.term.History() + log.Infof("Saving history (%d commands) to %s", len(h), t.historyFile) + saveHistory(t.historyFile, h) + } return err }