2022-03-13 17:48:49 +00:00
|
|
|
package readline
|
|
|
|
|
|
|
|
import (
|
|
|
|
"os"
|
|
|
|
"regexp"
|
|
|
|
"sync"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Instance is used to encapsulate the parameter group and run time of any given
|
|
|
|
// readline instance so that you can reuse the readline API for multiple entry
|
|
|
|
// captures without having to repeatedly unload configuration.
|
|
|
|
type Instance struct {
|
|
|
|
|
|
|
|
//
|
|
|
|
// Input Modes -------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
// InputMode - The shell can be used in Vim editing mode, or Emacs (classic).
|
|
|
|
InputMode InputMode
|
|
|
|
|
|
|
|
// Vim parameters/functions
|
|
|
|
// ShowVimMode - If set to true, a string '[i]' or '[N]' indicating the
|
|
|
|
// current Vim mode will be appended to the prompt variable, therefore added to
|
|
|
|
// the user's custom prompt is set. Applies for both single and multiline prompts
|
|
|
|
ShowVimMode bool
|
|
|
|
VimModeColorize bool // If set to true, varies colors of the VimModePrompt
|
|
|
|
|
|
|
|
//
|
|
|
|
// Prompt -------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
Multiline bool // If set to true, the shell will have a two-line prompt.
|
|
|
|
MultilinePrompt string // If multiline is true, this is the content of the 2nd line.
|
|
|
|
|
2022-04-13 14:13:46 +00:00
|
|
|
mainPrompt string // If multiline true, the full prompt string / If false, the 1st line of the prompt
|
|
|
|
rightPrompt string
|
|
|
|
rightPromptLen int
|
|
|
|
realPrompt []rune // The prompt that is actually on the same line as the beginning of the input line.
|
|
|
|
defaultPrompt []rune
|
|
|
|
promptLen int
|
|
|
|
stillOnRefresh bool // True if some logs have printed asynchronously since last loop. Check refresh prompt funcs
|
2022-03-13 17:48:49 +00:00
|
|
|
|
|
|
|
//
|
|
|
|
// Input Line ---------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
// PasswordMask is what character to hide password entry behind.
|
|
|
|
// Once enabled, set to 0 (zero) to disable the mask again.
|
|
|
|
PasswordMask rune
|
|
|
|
|
|
|
|
// readline operating parameters
|
|
|
|
line []rune // This is the input line, with entered text: full line = mlnPrompt + line
|
|
|
|
pos int
|
|
|
|
posX int // Cursor position X
|
|
|
|
fullX int // X coordinate of the full input line, including the prompt if needed.
|
|
|
|
posY int // Cursor position Y (if multiple lines span)
|
|
|
|
fullY int // Y offset to the end of input line.
|
|
|
|
|
|
|
|
// Buffer received from host programms
|
|
|
|
multiline []byte
|
|
|
|
multisplit []string
|
|
|
|
skipStdinRead bool
|
|
|
|
|
|
|
|
// SyntaxHighlight is a helper function to provide syntax highlighting.
|
|
|
|
// Once enabled, set to nil to disable again.
|
|
|
|
SyntaxHighlighter func([]rune) string
|
|
|
|
|
|
|
|
//
|
|
|
|
// Completion ---------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
// TabCompleter is a simple function that offers completion suggestions.
|
|
|
|
// It takes the readline line ([]rune) and cursor pos.
|
|
|
|
// Returns a prefix string, and several completion groups with their items and description
|
|
|
|
// Asynchronously add/refresh completions
|
|
|
|
TabCompleter func([]rune, int, DelayedTabContext) (string, []*CompletionGroup)
|
|
|
|
delayedTabContext DelayedTabContext
|
|
|
|
|
|
|
|
// SyntaxCompletion is used to autocomplete code syntax (like braces and
|
|
|
|
// quotation marks). If you want to complete words or phrases then you might
|
|
|
|
// be better off using the TabCompletion function.
|
|
|
|
// SyntaxCompletion takes the line ([]rune) and cursor position, and returns
|
|
|
|
// the new line and cursor position.
|
|
|
|
SyntaxCompleter func([]rune, int) ([]rune, int)
|
|
|
|
|
|
|
|
// Asynchronously highlight/process the input line
|
|
|
|
DelayedSyntaxWorker func([]rune) []rune
|
|
|
|
delayedSyntaxCount int64
|
|
|
|
|
|
|
|
// MaxTabCompletionRows is the maximum number of rows to display in the tab
|
|
|
|
// completion grid.
|
|
|
|
MaxTabCompleterRows int // = 4
|
|
|
|
|
|
|
|
// tab completion operating parameters
|
|
|
|
tcGroups []*CompletionGroup // All of our suggestions tree is in here
|
|
|
|
tcPrefix string // The current tab completion prefix aggainst which to build candidates
|
|
|
|
|
|
|
|
modeTabCompletion bool
|
|
|
|
compConfirmWait bool // When too many completions, we ask the user to confirm with another Tab keypress.
|
|
|
|
tabCompletionSelect bool // We may have completions printed, but no selected candidate yet
|
|
|
|
tabCompletionReverse bool // Groups sometimes use this indicator to know how they should handle their index
|
|
|
|
tcUsedY int // Comprehensive offset of the currently built completions
|
2022-05-31 19:16:32 +00:00
|
|
|
completionOpen bool
|
2022-03-13 17:48:49 +00:00
|
|
|
|
|
|
|
// Candidate / virtual completion string / etc
|
|
|
|
currentComp []rune // The currently selected item, not yet a real part of the input line.
|
|
|
|
lineComp []rune // Same as rl.line, but with the currentComp inserted.
|
|
|
|
lineRemain []rune // When we complete in the middle of a line, we cut and keep the remain.
|
|
|
|
compAddSpace bool // If true, any candidate inserted into the real line is done with an added space.
|
|
|
|
|
|
|
|
//
|
|
|
|
// Completion Search (Normal & History) -----------------------------------------------------
|
|
|
|
|
|
|
|
modeTabFind bool // This does not change, because we will search in all options, no matter the group
|
|
|
|
tfLine []rune // The current search pattern entered
|
|
|
|
modeAutoFind bool // for when invoked via ^R or ^F outside of [tab]
|
|
|
|
searchMode FindMode // Used for varying hints, and underlying functions called
|
|
|
|
regexSearch *regexp.Regexp // Holds the current search regex match
|
2023-03-26 00:14:26 +00:00
|
|
|
search string
|
2022-03-13 17:48:49 +00:00
|
|
|
mainHist bool // Which history stdin do we want
|
2022-03-26 21:34:09 +00:00
|
|
|
histInfo []rune // We store a piece of hist info, for dual history sources
|
2023-03-26 00:14:26 +00:00
|
|
|
Searcher func(string, []string) []string
|
2022-03-13 17:48:49 +00:00
|
|
|
|
|
|
|
//
|
|
|
|
// History -----------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
// mainHistory - current mapped to CtrlR by default, with rl.SetHistoryCtrlR()
|
|
|
|
mainHistory History
|
|
|
|
mainHistName string
|
|
|
|
// altHistory is an alternative history input, in case a console user would
|
|
|
|
// like to have two different history flows. Mapped to CtrlE by default, with rl.SetHistoryCtrlE()
|
|
|
|
altHistory History
|
|
|
|
altHistName string
|
|
|
|
|
|
|
|
// HistoryAutoWrite defines whether items automatically get written to
|
|
|
|
// history.
|
|
|
|
// Enabled by default. Set to false to disable.
|
|
|
|
HistoryAutoWrite bool // = true
|
|
|
|
|
|
|
|
// history operating params
|
|
|
|
lineBuf string
|
|
|
|
histPos int
|
2022-12-10 01:45:52 +00:00
|
|
|
histOffset int
|
2022-03-13 17:48:49 +00:00
|
|
|
histNavIdx int // Used for quick history navigation.
|
|
|
|
|
|
|
|
//
|
2022-03-26 21:34:09 +00:00
|
|
|
// Info -------------------------------------------------------------------------------------
|
2022-03-13 17:48:49 +00:00
|
|
|
|
2022-03-26 21:34:09 +00:00
|
|
|
// InfoText is a helper function which displays infio text below the prompt.
|
|
|
|
// InfoText takes the line input from the prompt and the cursor position.
|
|
|
|
// It returns the info text to display.
|
|
|
|
InfoText func([]rune, int) []rune
|
|
|
|
|
|
|
|
// InfoColor is any ANSI escape codes you wish to use for info formatting. By
|
|
|
|
// default this will just be blue.
|
|
|
|
InfoFormatting string
|
|
|
|
|
|
|
|
infoText []rune // The actual info text
|
|
|
|
infoY int // Offset to info, if it spans multiple lines
|
|
|
|
|
|
|
|
//
|
|
|
|
// Hints -----------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
// HintText is a helper function which displays hint text right after the user's input.
|
|
|
|
// It takes the line input and cursor position.
|
2022-03-13 17:48:49 +00:00
|
|
|
// It returns the hint text to display.
|
|
|
|
HintText func([]rune, int) []rune
|
|
|
|
|
2022-03-26 21:34:09 +00:00
|
|
|
// HintFormatting is just a string to use as the formatting for the hint. By default
|
|
|
|
// this will be a grey color.
|
2022-03-13 17:48:49 +00:00
|
|
|
HintFormatting string
|
|
|
|
|
2022-03-26 21:34:09 +00:00
|
|
|
hintText []rune
|
2022-03-13 17:48:49 +00:00
|
|
|
|
|
|
|
//
|
|
|
|
// Vim Operatng Parameters -------------------------------------------------------------------
|
|
|
|
|
|
|
|
modeViMode ViMode //= vimInsert
|
|
|
|
viIteration string
|
|
|
|
viUndoHistory []undoItem
|
|
|
|
viUndoSkipAppend bool
|
|
|
|
viIsYanking bool
|
|
|
|
registers *registers // All memory text registers, can be consulted with Alt"
|
|
|
|
|
|
|
|
//
|
|
|
|
// Other -------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
// TempDirectory is the path to write temporary files when editing a line in
|
|
|
|
// $EDITOR. This will default to os.TempDir()
|
|
|
|
TempDirectory string
|
|
|
|
|
|
|
|
// GetMultiLine is a callback to your host program. Since multiline support
|
|
|
|
// is handled by the application rather than readline itself, this callback
|
|
|
|
// is required when calling $EDITOR. However if this function is not set
|
|
|
|
// then readline will just use the current line.
|
|
|
|
GetMultiLine func([]rune) []rune
|
|
|
|
|
|
|
|
EnableGetCursorPos bool
|
|
|
|
|
|
|
|
// event
|
|
|
|
evtKeyPress map[string]func(string, []rune, int) *EventReturn
|
|
|
|
|
|
|
|
// concurency
|
|
|
|
mutex sync.Mutex
|
|
|
|
|
|
|
|
ViModeCallback func(ViMode)
|
2022-03-13 20:44:11 +00:00
|
|
|
ViActionCallback func(ViAction, []string)
|
2022-07-11 00:34:00 +00:00
|
|
|
|
|
|
|
RawInputCallback func([]rune) // called on all input
|
2022-03-13 17:48:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewInstance is used to create a readline instance and initialise it with sane defaults.
|
|
|
|
func NewInstance() *Instance {
|
|
|
|
rl := new(Instance)
|
|
|
|
|
|
|
|
// Prompt
|
|
|
|
rl.Multiline = false
|
|
|
|
rl.mainPrompt = ""
|
|
|
|
rl.defaultPrompt = []rune{}
|
|
|
|
rl.promptLen = len(rl.computePrompt())
|
|
|
|
|
|
|
|
// Input Editing
|
|
|
|
rl.InputMode = Emacs
|
|
|
|
rl.ShowVimMode = true // In case the user sets input mode to Vim, everything is ready.
|
|
|
|
|
|
|
|
// Completion
|
|
|
|
rl.MaxTabCompleterRows = 50
|
|
|
|
|
|
|
|
// History
|
|
|
|
rl.mainHistory = new(ExampleHistory) // In-memory history by default.
|
|
|
|
rl.HistoryAutoWrite = true
|
|
|
|
|
|
|
|
// Others
|
2022-03-26 21:34:09 +00:00
|
|
|
rl.InfoFormatting = seqFgBlue
|
|
|
|
rl.HintFormatting = "\x1b[2m"
|
2022-03-13 17:48:49 +00:00
|
|
|
rl.evtKeyPress = make(map[string]func(string, []rune, int) *EventReturn)
|
|
|
|
rl.TempDirectory = os.TempDir()
|
2023-03-26 00:14:26 +00:00
|
|
|
rl.Searcher = func(needle string, haystack []string) []string {
|
|
|
|
suggs := make([]string, 0)
|
|
|
|
|
|
|
|
var err error
|
|
|
|
rl.regexSearch, err = regexp.Compile("(?i)" + string(rl.tfLine))
|
|
|
|
if err != nil {
|
|
|
|
rl.RefreshPromptLog(err.Error())
|
|
|
|
rl.infoText = []rune(Red("Failed to match search regexp"))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, hay := range haystack {
|
|
|
|
if rl.regexSearch == nil { continue }
|
|
|
|
if rl.regexSearch.MatchString(hay) {
|
|
|
|
suggs = append(suggs, hay)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return suggs
|
|
|
|
}
|
2022-03-13 17:48:49 +00:00
|
|
|
|
|
|
|
// Registers
|
|
|
|
rl.initRegisters()
|
|
|
|
|
|
|
|
return rl
|
|
|
|
}
|