package readline import ( "bufio" "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. 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 // // 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 completionOpen bool // 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 search string mainHist bool // Which history stdin do we want histInfo []rune // We store a piece of hist info, for dual history sources Searcher func(string, []string) []string // // 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 histOffset int histNavIdx int // Used for quick history navigation. // // Info ------------------------------------------------------------------------------------- // 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. // It returns the hint text to display. HintText func([]rune, int) []rune // HintFormatting is just a string to use as the formatting for the hint. By default // this will be a grey color. HintFormatting string hintText []rune // // 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) ViActionCallback func(ViAction, []string) RawInputCallback func([]rune) // called on all input bufferedOut *bufio.Writer } // 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 rl.InfoFormatting = seqFgBlue rl.HintFormatting = "\x1b[2m" rl.evtKeyPress = make(map[string]func(string, []rune, int) *EventReturn) rl.TempDirectory = os.TempDir() 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 } rl.bufferedOut = bufio.NewWriter(os.Stdout) // Registers rl.initRegisters() return rl }