Line Editor

The blessed.line_editor module provides LineEditor, a headless single-line editor with readline-style keybindings, grapheme-aware cursor movement, auto-suggest from history, password masking, and horizontal scrolling.

Note

The editor never writes to the terminal directly, your application controls when and where output appears.

Use the built-in render methods to produce ready-to-print escape sequences, or read display for raw display state if you need fully custom rendering.

Overview

Feed keystrokes (from inkey() or async_inkey()) into feed_key(). It returns a LineEditResult with these fields:

  • line the accepted string when Enter is pressed, otherwise None.

  • interrupt, eof: True on Ctrl+C / Ctrl+D respectively.

  • changed True when the display needs redrawing.

  • bell a bell string to emit (empty when silent).

For bracketed paste, feed the pasted text through insert_text() instead of feed_key().

For async usage, simply replace term.inkey() with await term.async_inkey(). See Async Input in the keyboard documentation for more on async_inkey.

Example

A line editor with history, auto-suggest, password mode, and styled rendering:

 1#!/usr/bin/env python3
 2"""Line editor demo with clipboard, bracketed paste, history, and scrolling."""
 3from functools import partial
 4
 5from blessed import Terminal
 6from blessed.line_editor import LineEditor, LineHistory, LineEditResult
 7
 8term = Terminal()
 9history = LineHistory()
10echo = partial(print, end='\r\n', flush=True)
11
12has_clipboard = term.does_osc52_clipboard()
13
14
15def copy_line(ed):
16    term.clipboard_copy(ed.line)
17    return LineEditResult()
18
19
20def paste_line(ed):
21    text = term.clipboard_paste()
22    return ed.insert_text(text) if text else LineEditResult()
23
24
25with term.raw(), term.cursor_shape(term.CursorShape.BLINKING_BLOCK), term.bracketed_paste():
26    term.detect_ambiguous_width()
27    if has_clipboard:
28        echo("press ^C and ^V for OS clipboard, type 'quit' to exit")
29    else:
30        echo("type 'quit' to exit")
31    echo()
32    while True:
33        margin = max(1, term.width // 5)
34        width = term.width - margin * 2
35        prompt = "Prompt> "
36        col = margin
37        ed_width = width - len(prompt)
38        keymap = {}
39        if has_clipboard:
40            keymap['KEY_CTRL_C'] = copy_line
41            keymap['KEY_CTRL_V'] = paste_line
42        ed = LineEditor(history=history, max_width=ed_width, limit=200,
43                        ambiguous_width=term.ambiguous_width,
44                        term_program=term.term_program,
45                        bg_sgr=term.on_brown,
46                        keymap=keymap or None)
47        echo(term.move_x(col) + prompt, end='')
48        row = term.get_location()[0]
49        ed_col = col + len(prompt)
50        echo(ed.render(term, row, ed_width, col=ed_col), end='')
51        while True:
52            key = term.inkey()
53            if key.name == 'BRACKETED_PASTE':
54                result = ed.insert_text(key.text)
55            else:
56                result = ed.feed_key(key)
57            if result.bell:
58                echo(result.bell, end='')
59            if result.changed:
60                key_str = str(key)
61                out = None
62                if key_str and key_str.isprintable() and len(key_str) == 1:
63                    out = ed.render_insert(term, row, key_str)
64                elif getattr(key, "name", None) == "KEY_BACKSPACE":
65                    out = ed.render_backspace(term, row)
66                if out is None:
67                    out = ed.render(term, row, ed_width, col=ed_col)
68                echo(out, end='')
69            if result.line is not None:
70                echo()
71                if result.line:
72                    echo(term.move_x(col) + f"  => {result.line!r}")
73                break
74        if (result.line or '').strip() == 'quit':
75            break

History

LineHistory stores command history in memory. History navigation (Up/Down) and auto-suggest (type a prefix, press Right to accept) are enabled automatically when a history instance is attached to the editor.

For on-disk persistence, read/write the entries list directly (most recent entry last).

Display State & Styling

Each call to display returns a DisplayState with the visible text, cursor position, suggestion suffix, and clipping indicators.

The editor ships with default SGR styling (light cream text, dark suggestion). Override with text_sgr, suggestion_sgr, bg_sgr, or ellipsis_sgr:

editor = LineEditor(bg_sgr=term.on_brown, max_width=term.width)

When max_width is set, display width is measured with wcwidth; pass ambiguous_width and term_program so that ambiguous and terminal-corrected characters are measured as the terminal renders them.

When max_width is set and text overflows, overflow_left and overflow_right indicate which edges are truncated. Use ellipsis_sgr to style the overflow indicator.

Rendering Helpers

LineEditor provides three render methods that build complete escape-sequence strings from the current display state:

render()

Full redraw, always produces correct output.

render_insert()

Fast-path after a character insert at end of buffer. Returns None when a full redraw is needed instead.

render_backspace()

Fast-path after a backspace at end of buffer. Returns None when a full redraw is needed instead.

Try the fast-path first and fall back to render() when it returns None. See the example above for the complete pattern.

Other Methods

clear()

Reset the buffer, cursor, and undo history.

set_password_mode()

Toggle password masking on or off mid-session.

Constructor Options

Beyond the styling and keybinding options shown above, LineEditor accepts:

password

If True, start in password mode (characters are masked). Toggle at runtime with set_password_mode().

password_char

Replacement character shown in password mode (default "⚻").

limit

Maximum buffer length in characters (default 65536).

limit_bell

Bell string emitted when the limit is reached (default "\\a").

scroll_jump

Fraction of max_width to scroll when the cursor overflows (default 0.5).

Custom Keybindings

Pass a keymap dict to override or extend the default emacs/readline bindings. Keys are Keystroke .name strings (e.g. "KEY_CTRL_K"), values are callables accepting a LineEditor and returning a LineEditResult.

Override an existing binding:

def my_enter(editor):
    # custom accept logic
    return LineEditResult(line=editor.line, changed=True)

editor = LineEditor(keymap={"KEY_ENTER": my_enter})

Add a new binding:

def handle_f1(editor):
    editor.insert_text("help!")
    return LineEditResult(changed=True)

editor = LineEditor(keymap={"KEY_F1": handle_f1})

Disable a binding by setting it to None:

# Ctrl+C becomes a silent no-op instead of raising interrupt
editor = LineEditor(keymap={"KEY_CTRL_C": None})

Default Keybindings

These are the default emacs/readline bindings. All can be overridden via keymap.

Key

Action

Enter

Accept line

Ctrl+C

Cancel (interrupt)

Ctrl+D

EOF on empty line, delete at cursor otherwise

Left, Ctrl+B

Move cursor left

Right

Accept auto-suggest at end, otherwise move right

Ctrl+F

Move cursor right

Home, Ctrl+A

Move to start of line

End, Ctrl+E

Move to end of line

Shift+Left, Ctrl+Left

Move word left

Shift+Right, Ctrl+Right

Move word right

Backspace

Delete character before cursor

Delete

Delete character at cursor

Ctrl+K

Kill to end of line

Ctrl+U

Kill to start of line

Ctrl+W

Kill word backward

Ctrl+Y

Yank (paste from kill ring)

Up, Ctrl+P

Previous history entry

Down, Ctrl+N

Next history entry

Ctrl+Z

Undo