mirror of
https://github.com/quitesimpleorg/hs9001.git
synced 2024-11-21 22:27:50 +01:00
197 lines
6.7 KiB
Go
197 lines
6.7 KiB
Go
/*
|
|
Package liner implements a simple command line editor, inspired by linenoise
|
|
(https://github.com/antirez/linenoise/). This package supports WIN32 in
|
|
addition to the xterm codes supported by everything else.
|
|
*/
|
|
package liner
|
|
|
|
import (
|
|
"bufio"
|
|
"container/ring"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
)
|
|
|
|
type commonState struct {
|
|
terminalSupported bool
|
|
outputRedirected bool
|
|
inputRedirected bool
|
|
historyProvider HistoryProvider
|
|
completer WordCompleter
|
|
columns int
|
|
killRing *ring.Ring
|
|
ctrlCAborts bool
|
|
r *bufio.Reader
|
|
tabStyle TabStyle
|
|
multiLineMode bool
|
|
cursorRows int
|
|
maxRows int
|
|
shouldRestart ShouldRestart
|
|
noBeep bool
|
|
needRefresh bool
|
|
}
|
|
|
|
type HistoryProvider interface {
|
|
ReadHistory(r io.Reader) (num int, err error)
|
|
WriteHistory(w io.Writer) (num int, err error)
|
|
AppendHistory(item string)
|
|
ClearHistory()
|
|
GetHistoryByPrefix(prefix string, mode int) (ph []string)
|
|
GetHistoryByPattern(pattern string, mode int) (ph []string, pos []int)
|
|
RLock()
|
|
RUnlock()
|
|
}
|
|
|
|
// TabStyle is used to select how tab completions are displayed.
|
|
type TabStyle int
|
|
|
|
// Two tab styles are currently available:
|
|
//
|
|
// TabCircular cycles through each completion item and displays it directly on
|
|
// the prompt
|
|
//
|
|
// TabPrints prints the list of completion items to the screen after a second
|
|
// tab key is pressed. This behaves similar to GNU readline and BASH (which
|
|
// uses readline)
|
|
const (
|
|
TabCircular TabStyle = iota
|
|
TabPrints
|
|
)
|
|
|
|
// ErrPromptAborted is returned from Prompt or PasswordPrompt when the user presses Ctrl-C
|
|
// if SetCtrlCAborts(true) has been called on the State
|
|
var ErrPromptAborted = errors.New("prompt aborted")
|
|
|
|
// ErrNotTerminalOutput is returned from Prompt or PasswordPrompt if the
|
|
// platform is normally supported, but stdout has been redirected
|
|
var ErrNotTerminalOutput = errors.New("standard output is not a terminal")
|
|
|
|
// ErrInvalidPrompt is returned from Prompt or PasswordPrompt if the
|
|
// prompt contains any unprintable runes (including substrings that could
|
|
// be colour codes on some platforms).
|
|
var ErrInvalidPrompt = errors.New("invalid prompt")
|
|
|
|
// ErrInternal is returned when liner experiences an error that it cannot
|
|
// handle. For example, if the number of colums becomes zero during an
|
|
// active call to Prompt
|
|
var ErrInternal = errors.New("liner: internal error")
|
|
|
|
// KillRingMax is the max number of elements to save on the killring.
|
|
const KillRingMax = 60
|
|
|
|
// HistoryLimit is the maximum number of entries saved in the scrollback history.
|
|
const HistoryLimit = 1000
|
|
|
|
func (s *State) ReadHistory(r io.Reader) (num int, err error) {
|
|
return s.historyProvider.ReadHistory(r)
|
|
}
|
|
func (s *State) WriteHistory(w io.Writer) (num int, err error) {
|
|
return s.historyProvider.WriteHistory(w)
|
|
}
|
|
func (s *State) AppendHistory(item string) {
|
|
s.historyProvider.AppendHistory(item)
|
|
}
|
|
func (s *State) ClearHistory() {
|
|
s.historyProvider.ClearHistory()
|
|
}
|
|
func (s *State) getHistoryByPrefix(prefix string, mode int) (ph []string) {
|
|
return s.historyProvider.GetHistoryByPrefix(prefix, mode)
|
|
}
|
|
func (s *State) getHistoryByPattern(pattern string, mode int) (ph []string, pos []int) {
|
|
return s.historyProvider.GetHistoryByPattern(pattern, mode)
|
|
}
|
|
|
|
// SetHistoryProvider allows you to set a custom provider
|
|
// for reading, writing and searching history.
|
|
// By implementing the HistoryProvider interface
|
|
// and injecting your custom implementation here.
|
|
func (s *State) SetHistoryProvider(f HistoryProvider) {
|
|
s.historyProvider = f
|
|
}
|
|
|
|
// Completer takes the currently edited line content at the left of the cursor
|
|
// and returns a list of completion candidates.
|
|
// If the line is "Hello, wo!!!" and the cursor is before the first '!', "Hello, wo" is passed
|
|
// to the completer which may return {"Hello, world", "Hello, Word"} to have "Hello, world!!!".
|
|
type Completer func(line string) []string
|
|
|
|
// WordCompleter takes the currently edited line with the cursor position and
|
|
// returns the completion candidates for the partial word to be completed.
|
|
// If the line is "Hello, wo!!!" and the cursor is before the first '!', ("Hello, wo!!!", 9) is passed
|
|
// to the completer which may returns ("Hello, ", {"world", "Word"}, "!!!") to have "Hello, world!!!".
|
|
type WordCompleter func(line string, pos int) (head string, completions []string, tail string)
|
|
|
|
// SetCompleter sets the completion function that Liner will call to
|
|
// fetch completion candidates when the user presses tab.
|
|
func (s *State) SetCompleter(f Completer) {
|
|
if f == nil {
|
|
s.completer = nil
|
|
return
|
|
}
|
|
s.completer = func(line string, pos int) (string, []string, string) {
|
|
return "", f(string([]rune(line)[:pos])), string([]rune(line)[pos:])
|
|
}
|
|
}
|
|
|
|
// SetWordCompleter sets the completion function that Liner will call to
|
|
// fetch completion candidates when the user presses tab.
|
|
func (s *State) SetWordCompleter(f WordCompleter) {
|
|
s.completer = f
|
|
}
|
|
|
|
// SetTabCompletionStyle sets the behvavior when the Tab key is pressed
|
|
// for auto-completion. TabCircular is the default behavior and cycles
|
|
// through the list of candidates at the prompt. TabPrints will print
|
|
// the available completion candidates to the screen similar to BASH
|
|
// and GNU Readline
|
|
func (s *State) SetTabCompletionStyle(tabStyle TabStyle) {
|
|
s.tabStyle = tabStyle
|
|
}
|
|
|
|
// ModeApplier is the interface that wraps a representation of the terminal
|
|
// mode. ApplyMode sets the terminal to this mode.
|
|
type ModeApplier interface {
|
|
ApplyMode() error
|
|
}
|
|
|
|
// SetCtrlCAborts sets whether Prompt on a supported terminal will return an
|
|
// ErrPromptAborted when Ctrl-C is pressed. The default is false (will not
|
|
// return when Ctrl-C is pressed). Unsupported terminals typically raise SIGINT
|
|
// (and Prompt does not return) regardless of the value passed to SetCtrlCAborts.
|
|
func (s *State) SetCtrlCAborts(aborts bool) {
|
|
s.ctrlCAborts = aborts
|
|
}
|
|
|
|
// SetMultiLineMode sets whether line is auto-wrapped. The default is false (single line).
|
|
func (s *State) SetMultiLineMode(mlmode bool) {
|
|
s.multiLineMode = mlmode
|
|
}
|
|
|
|
// ShouldRestart is passed the error generated by readNext and returns true if
|
|
// the the read should be restarted or false if the error should be returned.
|
|
type ShouldRestart func(err error) bool
|
|
|
|
// SetShouldRestart sets the restart function that Liner will call to determine
|
|
// whether to retry the call to, or return the error returned by, readNext.
|
|
func (s *State) SetShouldRestart(f ShouldRestart) {
|
|
s.shouldRestart = f
|
|
}
|
|
|
|
// SetBeep sets whether liner should beep the terminal at various times (output
|
|
// ASCII BEL, 0x07). Default is true (will beep).
|
|
func (s *State) SetBeep(beep bool) {
|
|
s.noBeep = !beep
|
|
}
|
|
|
|
func (s *State) promptUnsupported(p string) (string, error) {
|
|
if !s.inputRedirected || !s.terminalSupported {
|
|
fmt.Print(p)
|
|
}
|
|
linebuf, _, err := s.r.ReadLine()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(linebuf), nil
|
|
}
|