github.com/mithrandie/csvq@v1.18.1/lib/terminal/terminal_readline.go (about) 1 //go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || windows 2 3 package terminal 4 5 import ( 6 "context" 7 "fmt" 8 "os" 9 "path/filepath" 10 11 "github.com/mithrandie/csvq/lib/option" 12 "github.com/mithrandie/csvq/lib/query" 13 14 "github.com/mitchellh/go-homedir" 15 "github.com/mithrandie/readline-csvq" 16 ) 17 18 type ReadLineTerminal struct { 19 terminal *readline.Instance 20 fd int 21 prompt *Prompt 22 env *option.Environment 23 completer *Completer 24 tx *query.Transaction 25 } 26 27 func NewTerminal(ctx context.Context, scope *query.ReferenceScope) (query.VirtualTerminal, error) { 28 fd := int(scope.Tx.Session.ScreenFd()) 29 30 limit := *scope.Tx.Environment.InteractiveShell.HistoryLimit 31 historyFile, err := HistoryFilePath(scope.Tx.Environment.InteractiveShell.HistoryFile) 32 if err != nil { 33 scope.Tx.LogWarn(fmt.Sprintf("cannot detect filepath: %q", scope.Tx.Environment.InteractiveShell.HistoryFile), false) 34 limit = -1 35 } 36 37 prompt := NewPrompt(scope) 38 completer := NewCompleter(scope) 39 40 t, err := readline.NewEx(&readline.Config{ 41 HistoryFile: historyFile, 42 DisableAutoSaveHistory: true, 43 HistoryLimit: limit, 44 HistorySearchFold: true, 45 Listener: new(ReadlineListener), 46 Stdin: readline.NewCancelableStdin(scope.Tx.Session.Stdin()), 47 Stdout: scope.Tx.Session.Stdout(), 48 Stderr: scope.Tx.Session.Stderr(), 49 }) 50 if err != nil { 51 return nil, err 52 } 53 54 terminal := ReadLineTerminal{ 55 terminal: t, 56 fd: fd, 57 prompt: prompt, 58 env: scope.Tx.Environment, 59 completer: completer, 60 tx: scope.Tx, 61 } 62 63 terminal.setCompleter() 64 terminal.setKillWholeLine() 65 terminal.setViMode() 66 if err = prompt.LoadConfig(); err != nil { 67 return nil, err 68 } 69 70 terminal.SetPrompt(ctx) 71 return terminal, nil 72 } 73 74 func (t ReadLineTerminal) Teardown() error { 75 return t.terminal.Close() 76 } 77 78 func (t ReadLineTerminal) ReadLine() (string, error) { 79 return t.terminal.Readline() 80 } 81 82 func (t ReadLineTerminal) Write(s string) error { 83 _, err := t.terminal.Write([]byte(s)) 84 return err 85 } 86 87 func (t ReadLineTerminal) WriteError(s string) error { 88 _, err := t.terminal.Stderr().Write([]byte(s)) 89 return err 90 } 91 92 func (t ReadLineTerminal) SetPrompt(ctx context.Context) { 93 str, err := t.prompt.RenderPrompt(ctx) 94 if err != nil { 95 t.tx.LogError(err.Error()) 96 } 97 t.terminal.SetPrompt(str) 98 } 99 100 func (t ReadLineTerminal) SetContinuousPrompt(ctx context.Context) { 101 str, err := t.prompt.RenderContinuousPrompt(ctx) 102 if err != nil { 103 t.tx.LogError(err.Error()) 104 } 105 t.terminal.SetPrompt(str) 106 } 107 108 func (t ReadLineTerminal) SaveHistory(s string) error { 109 return t.terminal.SaveHistory(s) 110 } 111 112 func (t ReadLineTerminal) GetSize() (int, int, error) { 113 return readline.GetSize(t.fd) 114 } 115 116 func (t ReadLineTerminal) ReloadConfig() error { 117 t.setCompleter() 118 t.setKillWholeLine() 119 t.setViMode() 120 return t.prompt.LoadConfig() 121 } 122 123 func (t ReadLineTerminal) UpdateCompleter() { 124 if t.completer != nil { 125 t.completer.Update() 126 } 127 } 128 129 func (t ReadLineTerminal) setCompleter() { 130 if *t.env.InteractiveShell.Completion { 131 t.terminal.Config.AutoComplete = t.completer 132 } else { 133 t.terminal.Config.AutoComplete = nil 134 } 135 } 136 137 func (t ReadLineTerminal) setKillWholeLine() { 138 if *t.env.InteractiveShell.KillWholeLine { 139 t.terminal.EnableKillWholeLine() 140 } else { 141 t.terminal.DisableKillWholeLine() 142 } 143 } 144 145 func (t ReadLineTerminal) setViMode() { 146 t.terminal.SetVimMode(*t.env.InteractiveShell.ViMode) 147 } 148 149 func HistoryFilePath(filename string) (string, error) { 150 if filename[0] == '~' { 151 if fpath, err := homedir.Expand(filename); err == nil { 152 return fpath, nil 153 } 154 } 155 156 fpath := os.ExpandEnv(filename) 157 158 if filepath.IsAbs(fpath) { 159 return fpath, nil 160 } 161 162 home, err := homedir.Dir() 163 if err != nil { 164 return filename, err 165 } 166 return filepath.Join(home, fpath), nil 167 }