reedline

repository·main·Indexed 21 days ago

https://github.com/nushell/reedline

A feature-rich, readline-like crate for CLI text input designed to power Nushell. It provides modern terminal conveniences including syntax highlighting, tab completions, multiline support, and fish-style autosuggestions. Reedline supports custom keybindings (including Emacs and Vi modes), history persistence via FileBackedHistory or SqliteBackedHistory, and is designed for cross-platform reliability across Windows, macOS, and Linux.

Tokens
20.2K
Snippets
62
Records
87
Agent score
71%

What's inside reedline

  1. Overview of Reedline design requirements and goals

    main

    Reedline is a feature-rich line editor designed to be language-agnostic, though it is currently the bundled editor for Nushell. It aims to provide a modern REPL experience with support for syntax highlighting, tab completions, configurable prompts, and history.

    Core Capabilities

    • Cross-platform support: Works on Windows, macOS, and Linux across various terminal emulators (e.g., iTerm2, Windows Terminal, Alacritty, Kitty, Wezterm).
    • Extensibility: Provides integration points for syntax highlighting, tab completions, and custom keybindings.
    • Unicode awareness: Supports Unicode characters with a focus on left-to-right text flow.

    Design Principles

    • Reliability: Strives to avoid panic! by using Result types for system-related errors. The most critical requirement is that the displayed line must always match the submitted line.
    • Terminal Citizenship: Aims to avoid display artifacts, maintain consistent scroll-back buffers, and handle terminal resizing gracefully.
    • Predictability: Defaults are designed to be intuitive and non-surprising.
  2. Technical implementation details of Reedline

    main

    Reedline manages terminal interaction using the following technical approaches:

    • Terminal Control: Uses the crossterm crate to abstract terminal styling, setup, and event handling. This allows Reedline to handle both ANSI escape sequences (standard on Unix) and Windows-specific API calls for non-ANSI compliant Windows terminals.
    • Input Handling: Operates in raw mode to intercept user input events directly.
    • Newline Handling: Because of raw mode, standard \n (LF) behavior may vary by platform. On Unix, \n might only move the cursor down without returning to the start of the line, necessitating \r\n (CRLF) for proper drawing operations.
    • Keybindings: Note that some Ctrl keybindings may be affected by how terminal emulators encode CTRL-<key> sequences.
  3. How to run the Reedline example binary for testing

    main

    To catch potential index overflows and other runtime issues, run the example binary.

    • Debug mode: Use cargo run to catch errors like index overflows. Note that debug mode may be significantly slower, which can impact the perceived smoothness of features like window resizing.
    • Release mode: Use cargo run --release to test the actual user experience, especially for performance-sensitive tasks like resizing.
    cargo run
    # or
    cargo run --release
  4. When to perform manual UX testing

    main

    Since Reedline lacks automated tests for user-facing terminal logic, manual verification is required when changes affect the following areas:

    • Repaint logic.
    • Key press dispatching.
    • Addition of new components.
    • Components that lack unit tests upholding the I/O facing engine contract.
    • Large refactors touching multiple components simultaneously.
  5. UX Test Checklist for manual verification

    main

    When finalizing a PR that touches core editor logic, use the following manual checklist to ensure no regressions were introduced in the user experience.

    Environment Info

    • Build type (debug/release)
    • Platform
    • Terminal emulator
    • Session type (ssh, tmux, or screen)

    Core Editing (Basics)

    • Type short lines with mixed case.
    • Test arrow key movement (left/right).
    • Test word movement: Ctrl-b or Ctrl-Left (left), Ctrl-f (right).
    • Test Enter to complete entry.
    • Clearing: Ctrl-c should abort entry and leave an empty prompt; Ctrl-l should clear the screen while preserving the current entry.
    • Unicode/Emojis: Paste Emoji test 😊 checks 🤦🏼‍♂️ unicode. Verify cursor movement, Home/End accuracy, and the ability to delete emojis.

    History

    • Recall previous entry with up-arrow on an empty line.
    • Ensure Enter on a recalled line does not duplicate it in history.
    • Test partial history matching with up-arrow while typing.
    • Test reverse search with Ctrl-r and verify navigation via Ctrl-r or up-arrow.
    • Abort search with Ctrl-c.

    Syntax Highlighting

    • Verify words (e.g., test) are highlighted correctly upon entry.

    Completion & VI Mode

    • Verify completion and VI mode behavior (refer to project-specific desired behavior).
  6. Handle asynchronous completions with CompletionStatus and CompletionResult

    main

    For completers that perform heavy or asynchronous work (like network calls), use the CompletionStatus and CompletionResult types to manage the lifecycle of the request.

    Workflow

    1. Start: Return CompletionResult::Pending and set poll_completion to return CompletionStatus::Pending.
    2. Polling: The engine calls poll_completion periodically. Return CompletionStatus::Pending while working.
    3. Finish: Once data is ready, return CompletionResult::Fresh and have poll_completion return CompletionStatus::Ready.

    CompletionStatus Variants

    • Idle: No background work is happening.
    • Pending: Background work is in progress; the engine should keep polling.
    • Ready: The background work just finished; the engine should refresh the UI with the new results.
  7. Use Partial for custom prefix replacement

    main

    The Partial struct allows a completer to tell Reedline to replace a specific Span in the buffer with a specific insert string. This is useful for implementing custom longest common prefix (LCP) logic.

    When a CompletionResult includes a Partial object, Reedline splices the insert text over the span verbatim, rather than using its default LCP derivation.

    Partial Fields

    • span: The Span (start and end bytes) in the buffer to be replaced.
    • insert: The String to be spliced in.
  8. How `put_cursor` handles selection and movement

    main

    The put_cursor method is used to update a cursor based on a motion target. It supports two primary modes of movement:

    1. Movement::Move: Collapses the cursor to a single point at the target position.
    2. Movement::Extend: Keeps the anchor fixed and moves the head to the target, extending the selection.

    It also respects CaretGeometry:

    • CaretGeometry::Block (inclusive): Used in Vi-style editors. When extending forward, the head is placed on the far edge of the target grapheme so the target is covered. If the selection direction reverses, the anchor is automatically "flipped" to the far edge of its current grapheme to ensure the starting grapheme remains covered.
    • CaretGeometry::Bar (exclusive): Used in Emacs-style editors. The head is placed exactly on the target boundary.

    This method is essential for implementing selection-aware motions like Vi Visual mode.

    // Example of extending a selection to a target using Block geometry
    let new_cursor = cursor.put_cursor(
        buffer_str,
        target_byte_index,
        Movement::Extend,
        CaretGeometry::Block,
    );
  9. Handle stale completion results in ColumnarMenu

    main

    When implementing custom completion logic, be aware of how ColumnarMenu handles stale results. A result is considered stale if its CompletionOrigin (the buffer state and insertion point when the completion was requested) no longer matches the current state of the Editor buffer.

    To prevent incorrect text splicing, ColumnarMenu performs the following checks:

    • Partial Completion: menu.can_partially_complete will return false if the suggestion's origin is mismatched with the live buffer.
    • Accepting Completion: menu.replace_in_buffer will not splice a stale span into the buffer if the origin is mismatched.

    If you are providing completions, ensure you correctly track the CompletionOrigin to allow the menu to validate whether the suggestions are still applicable to the user's current input.

  10. Configure IdeMenu description placement with DescriptionMode

    main

    The DescriptionMode enum determines how the description box (the extra information shown for a selected suggestion) is positioned relative to the completion list:

    • DescriptionMode::Left: The description is always shown on the left side of the completion box.
    • DescriptionMode::Right: The description is always shown on the right side of the completion box.
    • DescriptionMode::PreferRight: The description is shown on the right if there is sufficient terminal space; otherwise, it falls back to the left side.
    pub enum DescriptionMode {
        Left,
        Right,
        PreferRight,
    }
  11. How ColumnarMenu traversal works

    main

    The ColumnarMenu supports two modes of navigation via the TraversalDirection enum:

    1. Horizontal: The menu fills rows first. Moving 'next' moves to the next column in the current row. When the end of a row is reached, it wraps to the start of the next row.
    2. Vertical: The menu fills columns first. Moving 'next' moves down the current column. When the bottom of a column is reached, it wraps to the top of the next column.

    Navigation is handled via MenuEvents such as MoveUp, MoveDown, MoveLeft, MoveRight, NextElement, and PreviousElement.

    #[derive(Debug, PartialEq, Eq)]
    pub enum TraversalDirection {
        /// Traverse horizontally
        Horizontal,
        /// Traverse vertically
        Vertical,
    }
  12. Manage text editing with LineBuffer

    main

    The LineBuffer struct is the in-memory representation of the text being edited. It manages multiple lines of text and maintains a Cursor to facilitate editing, including text selection.

    Line-ending Contract

    Warning: The buffer may contain \r characters. While typing emits \n and certain operations normalize CRLF to LF, other entry points (like EditCommand::InsertString, external editors, or completers) can introduce \r. All line, word, and cursor logic treats \r or \r\n as a terminator. Do not assume the buffer is CR-free.

    use reedline::core_editor::LineBuffer;
    
    let mut buffer = LineBuffer::new();
    buffer.insert_str("Hello, world!");