The native line editor that makes Lush different
- Why LLE Exists
- Editing Modes
- Emacs Mode Reference
- Vi Mode
- Completion System
- Syntax Highlighting
- History Integration
- Multi-Line Editing
- Kill Ring
- Customization
- Troubleshooting
Every major shell wraps GNU Readline or implements something similar. Bash uses Readline. Zsh has ZLE. Fish has its own editor. They all follow the same pattern: a text input library adapted for shell use.
Lush took a different approach. LLE (Lush Line Editor) is a line editor designed from the ground up for interactive shell use. This isn't academic distinction - it enables capabilities that bolted-on solutions can't provide:
-
Deep completion integration: LLE's completion system understands shell syntax at a fundamental level. It doesn't just complete filenames - it knows what arguments each of the 45 shell builtins accepts and presents context-appropriate options.
-
Real-time syntax highlighting: Every character you type is tokenized and colored. Commands are validated as you type. You see errors before pressing Enter.
-
Unified editing experience: Multi-line editing, history navigation, and completion all work together without the seams you feel in other shells.
-
Foundation for the future: Features like the integrated debugger, hook system, and shell modes all build on LLE's architecture.
LLE is not readline. It doesn't load .inputrc. It doesn't try to be compatible with readline's API. It's a purpose-built editor for a purpose-built shell.
LLE supports two editing paradigms:
Emacs-style editing is the default and provides complete functionality. All 44 keybinding actions are implemented and production-ready.
Vi mode has the framework in place - mode switching, state machine, command parsing - but is not yet wired to editing operations. This is targeted for completion in the v1.4.0 release cycle.
To check the current mode:
display lle diagnosticsLLE implements 44 GNU Readline-compatible keybinding actions.
| Keybinding | Action | Description |
|---|---|---|
Ctrl-A |
Beginning of line | Move to start of current line |
Ctrl-E |
End of line | Move to end of current line |
Ctrl-F |
Forward char | Move forward one character |
Ctrl-B |
Backward char | Move backward one character |
Alt-F |
Forward word | Move forward one word |
Alt-B |
Backward word | Move backward one word |
Ctrl-P / Up |
Previous history | Previous history entry (or previous line in multi-line) |
Ctrl-N / Down |
Next history | Next history entry (or next line in multi-line) |
Alt-< |
Beginning of history | Go to first history entry |
Alt-> |
End of history | Go to last history entry / current input |
| Keybinding | Action | Description |
|---|---|---|
Ctrl-D |
Delete char | Delete character at cursor (EOF on empty line) |
Backspace |
Backward delete | Delete character before cursor |
Ctrl-K |
Kill line | Kill from cursor to end of line |
Ctrl-U |
Backward kill line | Kill from beginning of line to cursor |
Alt-D |
Kill word | Kill from cursor to end of word |
Ctrl-W |
Backward kill word | Kill from beginning of word to cursor |
Alt-Backspace |
Backward kill word | Same as Ctrl-W |
| Keybinding | Action | Description |
|---|---|---|
Ctrl-Y |
Yank | Paste most recent kill |
Alt-Y |
Yank pop | After Ctrl-Y, cycle through kill ring |
| Keybinding | Action | Description |
|---|---|---|
Ctrl-T |
Transpose chars | Swap character at cursor with previous |
Alt-T |
Transpose words | Swap word at cursor with previous word |
Alt-U |
Upcase word | Uppercase from cursor to end of word |
Alt-L |
Downcase word | Lowercase from cursor to end of word |
Alt-C |
Capitalize word | Capitalize first char, lowercase rest |
| Keybinding | Action | Description |
|---|---|---|
Tab |
Complete | Trigger completion |
Alt-? |
Possible completions | List all possible completions |
Alt-* |
Insert completions | Insert all possible completions |
| Keybinding | Action | Description |
|---|---|---|
Ctrl-R |
Reverse search | Incremental reverse history search |
Ctrl-S |
Forward search | Incremental forward history search |
Ctrl-G |
Abort | Cancel search, return to original line |
| Keybinding | Action | Description |
|---|---|---|
Ctrl-_ |
Undo | Undo last editing action |
Ctrl-X Ctrl-U |
Undo | Same as Ctrl-_ |
| Keybinding | Action | Description |
|---|---|---|
Ctrl-L |
Clear screen | Clear terminal, redraw prompt |
Ctrl-C |
Interrupt | Send SIGINT, cancel current input |
Ctrl-Z |
Suspend | Send SIGTSTP, suspend shell |
Ctrl-D |
EOF | On empty line: exit shell |
Enter |
Accept line | Execute the current line |
| Keybinding | Action | Description |
|---|---|---|
Ctrl-Q |
Quoted insert | Insert next character literally |
Ctrl-V |
Quoted insert | Insert next character literally |
Alt-# |
Insert comment | Comment out line, add to history |
Ctrl-X Ctrl-E |
Edit and execute | Open in $EDITOR, execute on save |
Vi mode is in active development. The framework includes:
- Mode state machine (normal, insert, command-line)
- Motion commands parser
- Operator-pending mode
- Count prefixes
Target: Full vi mode in the v1.4.0 release.
When complete, vi mode will support:
Normal Mode
h j k l- Movementw b e- Word movement0 $ ^- Line movementx X- Delete characterd- Delete operatorc- Change operatory- Yank operatorp P- Pastei a I A- Enter insert modeu Ctrl-R- Undo/redo/?- Searchn N- Search repeat
Insert Mode
- All text input
Escape- Return to normal modeCtrl-[- Return to normal mode
LLE's completion system is context-aware. It understands what you're typing and provides appropriate completions.
| Type | Description | Example |
|---|---|---|
| Command | Executables in $PATH | gi<Tab> -> git |
| Builtin | Shell builtins | ec<Tab> -> echo |
| File | Files and directories | /etc/pa<Tab> -> /etc/passwd |
| Variable | Shell variables | $HO<Tab> -> $HOME |
| Option | Command options | ls --co<Tab> -> ls --color |
All 45 completable shell builtins have specific completion logic:
# set builtin knows its options
set -o <Tab> # Shows: errexit nounset xtrace ...
# config builtin knows its structure
config set <Tab> # Shows: shell completion display ...
config set shell.<Tab> # Shows: errexit nounset pipefail ...
# debug builtin knows its subcommands
debug <Tab> # Shows: on off vars print trace profile
# display builtin knows its arguments
display <Tab> # Shows: lle features themes status stats
display lle <Tab> # Shows: diagnostics status infoWhen multiple completions are available, a menu appears:
| Key | Action |
|---|---|
Up / Ctrl-P |
Move up in menu |
Down / Ctrl-N |
Move down in menu |
Left / Ctrl-B |
Move left (multi-column) |
Right / Ctrl-F |
Move right (multi-column) |
Tab |
Accept selection |
Enter |
Accept and execute |
Escape |
Cancel completion |
When an external command shadows a builtin (like /usr/bin/echo vs the echo builtin), selecting the external command inserts its full path so you explicitly use the external version.
LLE tokenizes your input in real-time and applies colors based on token types.
LLE recognizes 45 distinct token types:
Commands
- Valid external command (green)
- Invalid command (red)
- Shell builtin (cyan)
- Alias (cyan)
- Function (blue)
Strings
- Single-quoted string
- Double-quoted string
- ANSI-C quoted string (
$'...') - Backtick command substitution
Variables
- Variable reference (
$var,${var}) - Special variables (
$?,$#,$@,$$,$!)
Paths
- Valid file/directory path (underlined green)
- Invalid path (underlined red)
Operators
- Pipe (
|) - Redirections (
>,<,>>,2>,&>) - Background (
&) - Command separator (
;) - Logical AND/OR (
&&,||) - Subshell parentheses
- Brace groups
Other
- Keywords (
if,then,else,fi,for,while,do,done,case,esac) - Comments (
# ...) - Options (
-v,--verbose) - Glob patterns (
*,?,[...]) - Here-documents
- Process substitution
- Arithmetic expansion
- Errors
LLE checks whether commands exist as you type:
- Commands that exist appear in green
- Commands that don't exist appear in red
- Builtins appear in cyan
- Aliases and functions have their own colors
This validation uses a cache for performance - commands are only checked once per session unless the cache is cleared.
Syntax highlighting colors come from the current theme:
# List available themes
theme list
# Switch themes
theme set dark
# Colors update immediatelyLLE integrates with Lush's history system.
| Key | Action |
|---|---|
Up / Ctrl-P |
Previous history entry |
Down / Ctrl-N |
Next history entry |
Alt-< |
First history entry |
Alt-> |
Last entry / current line |
| Key | Action |
|---|---|
Ctrl-R |
Start reverse search |
Ctrl-S |
Start forward search |
| Type text | Narrow search |
Ctrl-R again |
Find previous match |
Ctrl-S again |
Find next match |
Enter |
Accept match |
Ctrl-G |
Cancel, restore original |
Standard history expansion works:
!! # Last command
!$ # Last argument of last command
!^ # First argument of last command
!* # All arguments of last command
!n # Command number n
!-n # n commands ago
!string # Most recent command starting with string
!?string? # Most recent command containing stringMulti-line commands are stored as single history entries. Navigating to a multi-line command displays all lines.
LLE can deduplicate adjacent identical history entries:
config set history.dedup_navigation trueWith this enabled, pressing Up will skip repeated commands.
LLE handles multi-line input naturally.
When a line is syntactically incomplete, LLE provides a continuation prompt:
$ for i in 1 2 3; do
> echo "Number: $i"
> done
Number: 1
Number: 2
Number: 3Incomplete constructs that trigger continuation:
- Unclosed quotes
- Unclosed parentheses/braces
if/then/else/fiblocksfor/while/do/doneloopscase/esacstatements- Backslash at end of line
| Key | Action |
|---|---|
Up |
Move to previous line (not history) |
Down |
Move to next line (not history) |
Ctrl-A |
Start of current line |
Ctrl-E |
End of current line |
Alt-< |
Start of buffer |
Alt-> |
End of buffer |
LLE maintains column position when moving between lines (sticky column).
To insert a newline without executing:
- Backslash at end of line:
\<Enter> - Within quotes, just press Enter
LLE maintains a kill ring for cut/paste operations.
These operations add to the kill ring:
Ctrl-K- Kill to end of lineCtrl-U- Kill to beginning of lineAlt-D- Kill word forwardCtrl-W/Alt-Backspace- Kill word backward
| Key | Action |
|---|---|
Ctrl-Y |
Yank (paste) most recent kill |
Alt-Y |
After Ctrl-Y, cycle through older kills |
- Consecutive kill commands append to the same entry
- The ring holds multiple entries (typically 10)
Alt-Yonly works immediately afterCtrl-Y
LLE keybindings can be configured through the config system:
# View current bindings
config show keybindings
# Modify a binding (example syntax)
config set keybindings.ctrl_a beginning_of_line# Enable/disable completion
config set completion.enabled true
# Case sensitivity
config set completion.case_sensitive false
# Menu display
config set completion.menu_complete true# Enable/disable
config set display.syntax_highlighting true
# Command validation
config set display.validate_commands true
# Path validation
config set display.validate_paths true# History size
config set history.size 10000
# Deduplication
config set history.dedup_navigation true
# Ignore patterns
config set history.ignore_patterns "^\\s*$"Problem: Colors don't appear
# Check terminal color support
echo $TERM
tput colors
# Ensure color is enabled
config set display.syntax_highlighting trueProblem: Characters display incorrectly
# Check locale
echo $LANG $LC_ALL
# LLE requires UTF-8
export LANG=en_US.UTF-8Problem: Width calculation wrong
# Force terminal size refresh
# Resize terminal window, or:
kill -WINCH $$Problem: No completions appear
# Check completion is enabled
config get completion.enabled
# Check for errors
display lle diagnosticsProblem: Wrong completions
# Clear completion cache
# (Rebuilds command cache)Problem: History not saving
# Check history file permissions
ls -la ~/.lush_history
# Check history size
config get history.sizeProblem: Slow response
# Check display stats
display stats
# Disable expensive features temporarily
config set display.validate_paths false
config set display.validate_commands false# Full LLE status
display lle diagnostics
# Performance statistics
display stats
# Feature status
display featuresLLE is composed of several subsystems:
| Subsystem | Responsibility |
|---|---|
| Buffer | Text storage, cursor management |
| Input | Key parsing, sequence detection |
| Keybinding | Action dispatch |
| Completion | Context analysis, candidate generation |
| History | Storage, navigation, search |
| Display | Rendering, syntax highlighting |
| Terminal | ANSI output, capability detection |
LLE fully supports Unicode:
- UTF-8 input and output
- Grapheme cluster handling (emoji, combining characters)
- Correct cursor positioning for wide characters
- Unicode-aware word boundaries
LLE uses:
- Arena allocators for temporary data
- Memory pools for frequent allocations
- Reference counting for shared data
Zero memory leaks on normal operation (verified with macOS leaks tool).
LLE is not thread-safe. All operations must occur on the main thread. The async worker (for completion) uses careful synchronization.
- USER_GUIDE.md - Complete shell reference
- COMPLETION_SYSTEM.md - Detailed completion documentation
- CONFIG_SYSTEM.md - Configuration reference