Readline

repository·master·Indexed 40 days ago

https://github.com/reeflective/readline

A pure Go, cross-platform shell implementation providing advanced line editing, completion, and prompt capabilities. It supports .inputrc, Emacs and Vim editing modes, and Zsh-inspired features including fuzzy search, incremental history search, and ghost-text autosuggestions. It includes a programmable API for custom commands, history sources, and syntax highlighting.

Tokens
4.4K
Snippets
16
Records
24
Agent score
99%

What's inside Readline

  1. Overview of Readline

    master

    Readline is a modern, pure Go implementation of a readline shell. It provides full .inputrc support and legacy readline command/option compatibility, while extending functionality with modern shell features. Its architecture and completion system are heavily inspired by Z-Shell (Zsh).

    Key capabilities include:

    • Cross-platform support: Works on Linux, MacOS, and Windows.
    • Editing Modes: Native Emacs commands and near-native Vim mode (including text objects, registers, and macro recording).
    • Advanced Completion: Keymap-based, configurable completion system with fuzzy search, incremental search, and automatic suffix removal.
    • Prompt Support: Supports PS1, PS2, RPROMPT, transient, and tooltip prompts (compatible with oh-my-posh).
    • Extensibility: Programmable API for custom commands and support for an arbitrary number of history sources.
  2. Explore Readline functionality via the console library

    master
    To understand the default configuration and see the full range of functionality provided by readline, you can use the console library, which is powered by readline. Installing and running the console example binary is the recommended way to grasp the library's capabilities in a live environment.
  3. Implement custom command history with the History interface

    master
    The History type (an alias for history.Source) is an interface that allows you to implement your own command history logging tools. By default, readline uses an in-memory history that only logs to a []string. You can implement this interface to persist history to a database, a remote server, or any other storage mechanism.
  4. Initialize and use a readline shell

    master

    To use readline in your Go application, create a new shell instance using readline.NewShell(). You can customize the prompt by providing a function to rl.Prompt.Primary(). The main interaction loop is driven by calling rl.Readline(), which blocks until the user accepts a line (e.g., by pressing Enter) or an interrupt occurs.

    Key behaviors of Readline():

    • Success: Returns the input string and nil error when the user accepts the line.
    • Interrupt: Returns ErrInterrupt when the user presses the interrupt sequence (typically Ctrl-C).
    • EOF: Returns io.EOF when the input stream is closed (typically Ctrl-D).
    • History: If an error is returned, the input line is not written to history.
    // Create a new shell with a custom prompt.
    rl := readline.NewShell()
    rl.Prompt.Primary(func() string { return "> " } )
    
    // Display the prompt, read user input.
    for {
        line, err := rl.Readline()
        if err != nil {
            if err == io.EOF {
                break
            }
            // Handle other errors like ErrInterrupt
            break
        }
        fmt.Println(line)
    }
  5. Configure user-provided shell behaviors

    master

    The Shell struct exposes several function fields that allow you to inject custom logic into the readline loop. You can assign functions to these fields to customize the shell's behavior:

    • AcceptMultiline: A function func(line []rune) (accept bool) used to decide if the shell should continue reading on a new line (returning false) or return the current line (returning true).
    • SyntaxHighlighter: A function func(line []rune) string that provides syntax highlighting for the current line. Set to nil to disable.
    • PasteTransformer: A function func(text string) string that rewrites bracketed paste payloads before they are inserted into the buffer.
    • Completer: A function func(line []rune, cursor int) Completions that produces completions based on the current line and cursor position.
  6. Filter and Merge completions

    master

    Use Filter to remove specific values from a Completions set, and Merge to combine multiple Completions objects into one. When merging, existing values in the receiver are overwritten by values in the others.

    a := readline.CompleteValues("A", "B")
    b := readline.CompleteValues("B", "C")
    
    // c contains ["A", "B", "C"]
    c := a.Merge(b)
    
    // Filter removes specific values
    d := c.Filter([]string{"B"}) // contains ["A", "C"]
  7. Modify completions with Prefix and Suffix

    master

    You can wrap existing completions with a prefix or suffix. Note that these methods modify the Value (what is actually inserted into the shell) but leave the Display (what the user sees in the list) unchanged.

    // If base is CompleteValues("melon"), 
    // Prefix("water") results in Value: "watermelon", Display: "melon"
    // Suffix("juice") results in Value: "melonjuice", Display: "melon"
    
    a := readline.CompleteValues("melon", "drop").Prefix("water")
    b := a.Suffix("juice")
  8. Create file-based or in-memory history sources

    master

    To use pre-built history implementations, use the following functions. Note that the returned history source must be bound to your readline instance using shell.History.Add().

    • NewHistoryFromFile(): Creates a history source that reads from and writes to a file.
    • NewInMemoryHistory(): Creates a history source that exists only in memory.
    // Example usage pattern
    hist := readline.NewHistoryFromFile("my_history.txt")
    shell.History.Add(hist)
  9. Access the shell's core editor components

    master

    The Shell struct provides direct access to its internal editor state through several methods. These are useful for inspecting or manipulating the current input state:

    • Line(): Returns the *core.Line representing the current input line buffer. This is also used as the minibuffer during incremental search.
    • Cursor(): Returns the *core.Cursor representing the current position in the line buffer.
    • Selection(): Returns the *core.Selection managing visual or pending selections (including multiple surround selections).
    line := shell.Line()
    cursor := shell.Cursor()
    selection := shell.Selection()
  10. Create styled completions with CompleteStyledValues

    master

    Use CompleteStyledValues to provide both a value and a specific terminal color/style (e.g., cterm color codes like "30" for green). The input slice must contain pairs of (value, style).

    // Returns "yes" in green (32) and "no" in red (31)
    // Input must be pairs: value, style, value, style...
    completions := readline.CompleteStyledValues("yes", "32", "no", "31")
  11. Create basic completions with CompleteValues

    master

    Use CompleteValues to create a Completions object from a simple list of strings. Each string will be used for both the actual value inserted and the display text shown to the user.

    // Returns completions for "melon", "drop", "fall"
    completions := readline.CompleteValues("melon", "drop", "fall")
  12. Print messages to the terminal from the shell

    master

    The Shell provides two ways to print formatted messages without interfering with the user's input line:

    Printf

    Printf(msg string, args ...any) prints a formatted string below the current input line. It clears any active hints or completions, prints the message, and then redisplays the prompt and input line below the message.

    PrintTransientf

    PrintTransientf(msg string, args ...any) prints a formatted string in place of the current prompt and input line. It clears the current line/prompt, prints the message, and then "pushes" the prompt and input line below the message.

    // Prints below the current line
    shell.Printf("Error: %s", err)
    
    // Prints in place of the current line
    shell.PrintTransientf("Loading...")