Reddish is a terminal-first Redis client built with Rust, tokio, crossterm, and ratatui.
It is designed for day-to-day Redis operations with keyboard-centric workflows: key browsing, typed value inspection, REPL execution, stats/slowlog views, Pub/Sub, and global search.
The project is under active development and tracks a milestone-driven spec (spec/reddish-tui-spec.md).
Implemented highlights:
- Key browser with namespace tree, SCAN-based loading, multi-select, and sort modes
- Typed value inspector (
string,list,hash,set,zset,stream) with editing and filtering - Set operations: union/inter/diff with multi-source selection
- ZSet score range filtering
- REPL with persistent history, command parsing, pipeline execution, and autocomplete
- REPL overlay (
:) for quick one-line commands without leaving the current tab - Info dashboard refresh loop with INFO + SLOWLOG integration, colored stats, and scrolling
- Pub/Sub publish + live subscription stream with two-panel layout
- Global search overlay with real-time SCAN-backed filtering and jump-to-key flow
- Keymap and mouse support with Vim/Emacs presets
- CLI startup overrides (
--url,--profile,--readonly,--log-level,--theme)
- Connection profiles (
standalone,cluster,sentinel) with TLS/password resolution support - Startup overrides via CLI (
--url,--profile,--readonly,--log-level,--theme)
- Key browser with namespace-aware navigation
- Multi-select with
Spaceand select-all withCtrl+A - Sort modes: alphabetical, by Redis type, by TTL (
sto cycle) - Typed Redis value loading (
string,list,hash,set,zset,stream) - Value editing, TTL editing (
t), and clipboard copy (y) - Duplicate key via
DUMP/RESTORE(d) - Set operations:
SUNIONSTORE,SINTERSTORE,SDIFFSTORE(u/i/x) - ZSet score range query (
q) with min/max filtering - Global search overlay with jump-to-key workflow (
/)
- REPL with history, parsing, pipeline execution (
;;), and tab autocomplete - REPL overlay (
:) for quick commands from any tab - Info dashboard with periodic INFO refresh, colored stats, and scrollable slowlog
- Pub/Sub tab with subscribe/unsubscribe, publish, and live message stream
- Read-only mode to block destructive writes
- Vim/Emacs-style keymaps with configurable actions
- Mouse capture and basic mouse interactions
- Context-aware clipboard copy across all value types
- Rust stable toolchain
- Redis server(s) for runtime usage
cargo build --releaseBinary will be available at target/release/reddish-tui.
A pre-generated roff man page lives at man/redis-tui.1. Install it with:
sudo install -Dm644 man/redis-tui.1 /usr/local/share/man/man1/redis-tui.1It is built from the same Cli definition the binary parses, so it cannot
drift. Regenerate after changing a flag:
cargo run --example generate-man > man/redis-tui.1A unit test compares the committed page against a freshly generated one and fails if they differ.
# Start with default config and connection flow
cargo run
# Connect directly with URL
cargo run -- --url redis://localhost:6379/0
# Connect by saved profile name
cargo run -- --profile local-dev
# Force read-only mode
cargo run -- --readonly
# Override logging level and theme for this run
cargo run -- --log-level debug --theme nordredis-tui [OPTIONS]
--url <URL> redis://[user:pass@]host:port/db
--profile <NAME> Connect using a saved profile
--readonly Disable all write operations
--log-level <LEVEL> Override config log level
--theme <THEME> Override config theme
Five themes ship with the app:
| Theme | Background |
|---|---|
default |
Black (16-color) |
dracula |
Truecolor #282a36 |
nord |
Truecolor #2e3440 |
solarized_dark |
Truecolor #002b36 |
gruvbox_dark |
Truecolor #282828 |
Select one with --theme <name> or color_scheme = "<name>" in config.toml;
the CLI flag wins. Names are case-insensitive. An unknown name is an error
listing the valid ones rather than a silent fallback, so a typo cannot leave you
staring at the wrong colours wondering why.
Theme colours cover panel borders and titles, the key list selection, type colours, TTL and memory metadata, TTL/slowlog severity ramps, prompts, JSON syntax highlighting, status-bar connection states and gauges.
One deliberate exception: the per-subscription colour cycle in Pub/Sub is not themed. Its job is to make adjacent channels visually distinct, and mapping it onto theme fields would make some of them indistinguishable under a theme whose related colours are close together.
Config file path:
- macOS:
~/Library/Application Support/redis-tui/config.toml - Linux:
~/.config/redis-tui/config.toml
Example:
namespace_separator = ":"
scan_count = 200
refresh_interval_ms = 1000
# Event-loop tick interval, clamped to 50..60000ms. It sets the cadence of the
# status-bar latency probe, the Info dashboard refresh and stream follow-mode.
# refresh_interval_ms = 1000
log_level = "info"
color_scheme = "default"
mouse_enabled = true
confirm_deletes = true
max_keys_in_memory = 500000
# Set to false to delete without a confirmation prompt.
# Read-only mode overrides this either way.
# confirm_deletes = true
# Editor used for string editing. Overrides $EDITOR when set.
# editor = "hx"
# Byte budget for rendering a single value. Larger values are truncated with a
# visible marker. Use 0 for no limit. Default: 1048576 (1 MiB).
max_value_display_bytes = 1048576redis-server --port 6379 # or use any reachable server
cargo run -- --url redis://localhost:6379/0Press ? for the full key list. The overlay is generated from whichever
keymap preset you have loaded, so it always shows the keys that actually work —
if you use the emacs preset it will not tell you to press q to quit.
Suggested first steps:
| Key | Action |
|---|---|
1 2 3 4 |
Switch between Keys, REPL, Info and Pub/Sub |
/ |
Search keys by name, fuzzy by default, Ctrl+r toggles regex |
Enter |
Inspect the selected key's value |
a |
Expand or collapse a namespace |
e |
Edit a string (see $EDITOR below) |
Ctrl+p |
Command palette |
q |
Quit |
Connection profiles are loaded from:
- macOS:
~/Library/Application Support/redis-tui/connections.toml - Linux:
~/.config/redis-tui/connections.toml
Reddish supports configurable keymaps and ships with Vim/Emacs-style defaults.
Mouse capture is enabled when the terminal starts.
| Action | Description |
|---|---|
| Click a tab label | Switch tabs. Hit regions come from the rendered labels, so the target matches what is on screen. |
| Click a key row | Move the selection to that key. Accounts for the list's scroll offset, so clicking a scrolled row selects the key that row shows. |
| Scroll wheel | Move the key selection up or down on the Keys tab. |
Clicks below the last key, in the tab-bar gaps, or in an overlay are ignored. While the search, help or connection overlays are open, clicks go to the overlay rather than the panes behind it.
| Action | Vim | Emacs | Description |
|---|---|---|---|
| quit | q |
Ctrl+c |
Quit application |
| help | ? |
? |
Show help overlay |
| palette | Ctrl+p |
Ctrl+x |
Open command palette |
| filter | / |
Ctrl+s |
Open global search |
| repl_overlay | : |
Alt+; |
One-line REPL prompt |
| tab_keys | 1 |
Alt+1 |
Keys tab |
| tab_repl | 2 |
Alt+2 |
REPL tab |
| tab_info | 3 |
Alt+3 |
Info dashboard tab |
| tab_pubsub | 4 |
Alt+4 |
Pub/Sub tab |
| nav_up | k / Up |
Ctrl+p |
Move cursor up |
| nav_down | j / Down |
Ctrl+n |
Move cursor down |
| nav_left | h / Left |
Ctrl+b |
Collapse namespace / go back |
| nav_right | l / Right |
Ctrl+f |
Expand namespace / go forward |
| confirm | Enter |
Enter |
Confirm action |
| cancel | Esc |
Ctrl+g |
Cancel / close overlay |
| refresh | R |
Ctrl+l |
Refresh key browser scan |
| copy | y |
Ctrl+w |
Copy context-aware value |
| Action | Vim | Emacs | Description |
|---|---|---|---|
| confirm | Enter |
Enter |
Inspect selected key |
| delete | D |
Ctrl+d |
Delete selected key |
| new_key | n |
Ctrl+n |
Create a new key |
| rename_key | r |
Ctrl+r |
Rename selected key |
| expire_key | e |
Ctrl+e |
Persist key (remove TTL) |
| set_ttl | t |
Ctrl+t |
Set TTL on selected key |
| duplicate_key | d |
Ctrl+shift+d |
Duplicate selected key |
| set_union | u |
Ctrl+u |
SUNIONSTORE with multi-select |
| set_inter | i |
Ctrl+i |
SINTERSTORE with multi-select |
| set_diff | x |
Ctrl+d |
SDIFFSTORE with multi-select |
| toggle_select | Space |
Space |
Toggle key selection |
| select_all | Ctrl+a |
Ctrl+a |
Select all visible keys |
| cycle_sort | s |
Ctrl+o |
Cycle sort: alpha → type → TTL |
| Action | Vim | Emacs | Description |
|---|---|---|---|
| edit | e |
Ctrl+e |
Edit current item (see $EDITOR below) |
| delete | D |
Ctrl+d |
Delete current item |
| copy | y |
Ctrl+w |
Copy current item |
| set_ttl | t |
Ctrl+t |
Set TTL on inspected key |
| inspector_list_push | a |
Ctrl+shift+a |
RPUSH to list |
| inspector_list_prepend | p |
Ctrl+shift+p |
LPUSH to list |
| inspector_list_insert | I |
Ctrl+shift+i |
Insert before/after the current list element |
| inspector_hash_add | a |
Ctrl+shift+h |
Add hash field |
| inspector_hash_filter | Ctrl+f |
Alt+f |
Filter hash fields by name (Esc clears) |
| inspector_value_search | Ctrl+shift+f |
Alt+/ |
Search within the current value's members |
| delete_selected | Ctrl+d |
Ctrl+shift+d |
Delete all selected hash fields |
| select_all | Ctrl+a |
Ctrl+a |
Select all visible hash fields |
| toggle_select | Space |
Space |
Toggle selection of the field under the cursor |
| inspector_set_add | a |
Ctrl+shift+s |
Add set member |
| inspector_zset_add | a |
Ctrl+shift+z |
Add zset member |
| inspector_zset_range_query | Q |
Ctrl+shift+q |
Filter zset by score range |
| inspector_stream_add | a |
Ctrl+shift+x |
Add stream entry |
| inspector_stream_trim | Ctrl+t |
Ctrl+shift+t |
XTRIM the stream (MAXLEN / MINID) |
| inspector_stream_follow | F |
Ctrl+shift+v |
Follow the stream tail (reloads on tick) |
| inspector_stream_groups | G |
Ctrl+shift+m |
Toggle the consumer groups panel |
| inspector_toggle_view | f |
Ctrl+shift+f |
Toggle stream compact/full |
| inspector_goto_end | g |
Ctrl+shift+g |
Jump to last stream entry |
| cycle_sort | s |
Ctrl+o |
Toggle zset score sort asc/desc |
| Tab | Tab |
Tab |
Cycle string view mode (Raw / JSON / Base64 / Hex) |
qquits the app from anywhere, so inspector bindings deliberately avoid it. The vim preset binds the hash filter toCtrl+fbecause/is already the global search key.
The message pane renders newest-first and tails the live feed by default.
| Key | Description |
|---|---|
f |
Toggle follow. Turning it on snaps back to the newest message; turning it off freezes the viewport so a busy channel cannot move your reading position. |
Down |
Scroll into older history. This detaches follow automatically, so the next message will not yank you back. |
Up |
Scroll toward the newest message. Reaching it resumes following. |
Ctrl+l |
Clear the buffer. |
The pane title always shows either [f] follow or [f] PAUSED, so it is
never ambiguous which mode you are in.
e on a string key opens an editor. Which one depends on the environment:
$EDITORset — the TUI suspends (leaves the alternate screen and disables raw mode), the value is written to a temporary file, and your editor is opened on it. The file is deleted when the editor exits. The result is written back to Redis only if it actually changed, so:qwithout edits issues noSET.$EDITORunset or blank — a built-in editor opens as a popup over the value pane, so the value stays visible while you type. The title shows the key being edited;Ctrl+Ssaves,Ctrl+XorEsccancels.
$EDITOR may include flags, e.g. code --wait or emacsclient -nw. An editor
that exits non-zero is treated as a cancel, so a failed edit cannot silently
discard the value.
To pin an editor without changing your shell environment, set editor in
config.toml; it takes precedence over $EDITOR. A blank value falls back to
$EDITOR.
max_value_display_bytes (default 1 MiB) bounds how much of a string value is
rendered. Anything longer is cut with a ... truncated at N of M bytes marker
rather than drawn in full, since a multi-megabyte value would stall the frame.
Set it to 0 to disable the limit. The value is still fetched from Redis in
full — this bounds rendering, not what the client downloads.
| Action | Vim | Emacs | Description |
|---|---|---|---|
| typing | — | — | Query is fuzzy-ranked by default |
search_toggle_mode |
Ctrl+r |
Ctrl+alt+r |
Toggle fuzzy / regex matching |
search_history_prev |
Alt+Up |
Alt+Up |
Recall an earlier query |
search_history_next |
Alt+Down |
Alt+Down |
Move forward through query history |
search_execute |
Enter |
Enter |
Open the highlighted key |
search_close |
Esc |
Ctrl+g |
Close the overlay |
Up / Down |
Up / Down |
Ctrl+p / Ctrl+n |
Move through results |
| Action | Vim | Emacs | Description |
|---|---|---|---|
| nav_up | k / Up |
Ctrl+p |
Scroll slowlog up |
| nav_down | j / Down |
Ctrl+n |
Scroll slowlog down |
| Space | Space |
Space |
Toggle slowlog compact mode |
t |
t |
t |
Cycle slowlog duration threshold |
e |
e |
e |
Export slowlog to CSV |
| Action | Vim | Emacs | Description |
|---|---|---|---|
| Tab | Tab |
Tab |
Switch channel / message input mode |
| delete | D |
Ctrl+d |
Unsubscribe from selected channel |
| Ctrl+L | Ctrl+l |
Ctrl+l |
Clear message history |
Run the full suite:
cargo test -qSome tests spin up local Redis instances and may print Redis startup warnings during execution. Integration tests expect a Redis on port 16379:
redis-server --port 16379 --daemonize yes --loglevel warning --save ''Two hidden flags enforce the spec's quality budgets. They run before any terminal setup, open no Redis connection, and exit non-zero on a violation, so they are safe in CI:
cargo run --release -- --bench # 1000 render cycles, p99 must be <= 16ms
cargo run --release -- --profile-memory # peak RSS at 10K keys must be <= 50MBMeasured on a release build with 10K keys:
| Scenario | p50 | p99 | Budget |
|---|---|---|---|
| Render, all namespaces expanded | 12.7ms | 13.5ms | 16ms ✅ |
| Render, default collapsed view | 0.14ms | 0.22ms | 16ms ✅ |
| Peak RSS (all six value types loaded) | — | 22.7MB | 50MB ✅ |
The fully-expanded case is the worst case: KeyBrowser::render builds one
ListItem per visible row, so expanding all 10K keys formats 10K rows per frame
even though only ~24 are drawn. The default view expands nothing.
Both gates assume a --release build; a debug build is roughly 3x slower and
will fail the render budget.
src/app.rs— app state and event routingsrc/redis/— Redis client, typed value model, server helperssrc/ui/— UI components (keys, inspector, REPL, dashboard, pubsub, search)src/config/— config and keybinding modelssrc/events/— input/tick event streamsrc/terminal.rs— terminal lifecycle + mouse capture setupsrc/bench.rs—--bench/--profile-memoryperformance gatestests/— milestone-oriented integration tests
Near-term focus:
- deeper keymap coverage across all panels
- richer value editing workflows
- Pub/Sub UX refinements
- tighter spec-gate test coverage
Contributions are welcome.
Suggested workflow:
- Open an issue describing the bug/feature.
- Keep PRs small and milestone-scoped.
- Run
cargo test -qbefore submitting. - Include tests for behavior changes when possible.
MIT