limn
    ↑↓ ChooseEnter OpenEsc Close
    GitHub

    Components/Data

    CodeEditor

    A code editor over a text buffer the host owns: carets and selections, undo, line numbers, syntax colors, and optional soft wrap, minimap, status bar and Vim mode. The host opens and saves files; the editor never touches one.

    Native componentGuide source

    Preview

    CodeSource
    fn open(text: &str) -> Result<EditorController, limn_editor::EditorError> {
        Ok(EditorController::new(TextBuffer::new(text)?))
    }
    
    fn view(state: &State, palette: ComponentPalette) -> Element {
        CodeEditor::new("source", state.controller.clone())
            .palette(palette)
            .language(CodeLanguage::Rust)
            .height(420.0)
            .render()
    }
    
    fn update(state: &mut State, action: &UiAction) {
        match action {
            UiAction::EditorChanged { id, revision, .. } if id == "source" => state.edited(*revision),
            UiAction::EditorSaveRequested { id, .. } if id == "source" => {
                if let Ok(snapshot) = state.controller.snapshot() {
                    state.save(&snapshot);
                }
            }
            UiAction::EditorFailed { id, error } if id == "source" => state.report(error),
            _ => {}
        }
    }

    CodeEditor

    A code editor over a text buffer the host owns: carets and selections, undo, line numbers, syntax colors, and optional soft wrap, minimap, status bar and Vim mode. The host opens and saves files; the editor never touches one.

    Import

    use limn::{CodeEditor, CodeLanguage, Component, EditorController};
    use limn_editor::TextBuffer;

    The buffer comes from the limn-editor crate, which the host adds to its dependencies beside limn.

    Usage

    The text lives in a TextBuffer behind an EditorController. Create the controller once, keep it in the state, and pass a clone of it to the editor each frame. The editor edits the buffer in place and reports that it did.

    fn open(text: &str) -> Result<EditorController, limn_editor::EditorError> {
        Ok(EditorController::new(TextBuffer::new(text)?))
    }
    
    fn view(state: &State, palette: ComponentPalette) -> Element {
        CodeEditor::new("source", state.controller.clone())
            .palette(palette)
            .language(CodeLanguage::Rust)
            .height(420.0)
            .render()
    }
    
    fn update(state: &mut State, action: &UiAction) {
        match action {
            UiAction::EditorChanged { id, revision, .. } if id == "source" => state.edited(*revision),
            UiAction::EditorSaveRequested { id, .. } if id == "source" => {
                if let Ok(snapshot) = state.controller.snapshot() {
                    state.save(&snapshot);
                }
            }
            UiAction::EditorFailed { id, error } if id == "source" => state.report(error),
            _ => {}
        }
    }

    TextBuffer::new fails for a text larger than the buffer’s limit. Without palette the editor uses the neutral dark palette. It fills the width it is given, and the tree keeps its scroll position under its ID.

    Reading the text

    controller.snapshot() gives a BufferSnapshot: the text at one revision, which the host can read without holding the buffer’s lock. snapshot.write_to(writer) writes it out, snapshot.line_text(row) gives one line, and snapshot.document_id() and snapshot.revision() are the numbers the editor’s actions carry. After a save, controller.shared_buffer() gives the buffer to lock, and mark_saved_snapshot(&snapshot) on it records that revision as the saved one.

    Language and height

    language takes a CodeLanguage, which chooses the syntax colors; the default, CodeLanguage::Plain, colors nothing. height is in logical pixels, never under 24, and 320 by default.

    Options

    CodeEditor::new("source", controller)
        .line_numbers(false)
        .soft_wrap(true)
        .minimap(true)
        .status_bar(true)
        .tab_width(2)
        .render()
    MethodDefaultWhat it does
    line_numbers(bool)onThe column of line numbers
    soft_wrap(bool)offWraps long lines to the editor’s width
    minimap(bool)offAn overview of the document to navigate by; a narrow editor hides it
    status_bar(bool)offA bar under the text with the caret, the language, the line ending and whether the document is saved
    tab_width(usize)4Columns of a tab and of an indent level, from 1 to 32
    indent_style(IndentStyle)IndentStyle::Spaces(4)Whether Tab indents with spaces or with tabs, and by how many columns
    auto_pairs(bool)onTyping an opening bracket or quote also inserts its closing one
    vim_mode(bool)offModal editing with Vim’s keys

    IndentStyle is in limn_editor.

    font_size(size) sets code and gutter text, 13 logical pixels by default. Rows, scrolling, soft wrap, selections, the caret and pointer positioning use the same size. Invalid numbers and nonpositive sizes are ignored; valid sizes are kept within 1 to 96. diff_backgrounds(false) hides the row tints of review hunks while keeping their edge markers, removed lines and controls.

    Read-only

    CodeEditor::new("source", controller).read_only(true).render()

    A read-only editor still moves the caret, selects and copies. Typing, pasting, deleting, Enter, undo and redo change nothing, and it does not take Tab.

    Decorations

    The host can draw its own analysis over the text: highlights for syntax colors of its own, diagnostics for ranges with a message that shows when the pointer rests on one, search_matches for the matches of a search, structure for folds and sticky scopes, breakpoints and debugger for the gutter, and review for the hunks of a change. Each takes the document ID and the revision of the snapshot it was computed for, or the revision alone for search_matches: that is how the editor tells whether a result belongs to the text it is showing. context_actions takes the entries of the editor’s context menu.

    For find and replace, controller.search(query, &options) searches the buffer, and Editing tools has the bar that goes with it. reveal_top_inset keeps the caret below a bar that floats over the top of the editor.

    Keyboard

    KeyDoes
    ArrowsMove the caret; with Ctrl, Left and Right move by a word
    Home, EndTo the first character of the line after its indentation, then to its first column on a second Home, and to its end; with Ctrl, to the start and the end of the document
    Page Up, Page DownMove by the lines the editor shows
    Shift with any of thoseExtends the selection
    Ctrl+ASelects everything
    Ctrl+DAlso selects the next occurrence of the selection; with Shift, every occurrence
    Tab, Shift+TabIndent and outdent
    Backspace, DeleteDelete a character; with Ctrl, a word
    Ctrl+ZUndo
    Ctrl+Shift+Z, Ctrl+YRedo
    Ctrl+C, Ctrl+XReport CopyRequested with the selected text; Ctrl+X also deletes it
    Ctrl+SReports EditorSaveRequested

    Actions

    ActionWhen
    UiAction::EditorChanged { id, document_id, revision }An edit changed the text; revision is the buffer’s new revision.
    UiAction::EditorSaveRequested { id, document_id, revision }Ctrl+S is pressed. The host saves.
    UiAction::CopyRequested { text }Ctrl+C or Ctrl+X with a selection.
    UiAction::EditorFailed { id, error }An edit could not be applied, or a selection is too large to copy.
    UiAction::Clicked { id } with {prefix}:{row}A row of the breakpoint gutter is clicked, with the prefix given to breakpoints and the row counted from zero.

    Accessibility

    The platform is told the editor is a multiline text field.

    API reference

    The guide covers composition, options and actions. Generate the complete Rust type and builder reference from the checkout:

    cargo doc -p limn --open

    Component API overview · State and actions

    The example is drawn by crates/limn-gallery/src/pages/code.rs.