Contributor and agent instructions for rmptui
Build, test, and lint
cargo build — compile the application.
cargo run -- --config FILE — run with a specific config (MPD at
127.0.0.1:6600 by default; MPD_HOST/MPD_PORT override it).
cargo fmt --check, cargo clippy --all-targets -- -D warnings, cargo test.
cargo test mpd::protocol — run the tests of one module; tests/mpd_client.rs
exercises the client against a scripted fake MPD server.
Architecture
mpd/ is a self-contained MPD client.
protocol.rs: pure command encoding (every argument quoted and escaped)
and response parsing.
connection.rs: async TCP and Unix-socket connections.
types.rs: typed models.
commands.rs: command constructors.
worker.rs: two background tasks. The idle connection reports changed
subsystems. The command connection runs Requests from an MpdHandle
and answers Querys with Replys. Both reconnect on their own.
app.rs owns the state and runs one tokio::select! loop over terminal
input, AppEvents (MPD, lyrics, tag writes) and visualizer frames. UI code
never awaits MPD: it sends requests and reacts to the resulting events.
state.rs (MpdState) mirrors the server: status, current song, the queue
(updated incrementally via plchanges), stored playlists, and a database
version used to invalidate caches.
screens/ holds one component per screen behind the Screen trait (draw,
handle_action, on_mpd, on_enter, on_mouse, search).
- Screens receive a
Ctx with read access to config and state, the
MpdHandle, and an effect queue (Effect) for things only the app can do:
notifications, popups, tag writes, lyric reloads.
- Every screen sees every
MpdEvent, so caches stay valid while hidden.
popups/ holds modal components (Popup trait); the topmost popup gets all
keys. Destructive actions go through ConfirmPopup.
ui/ holds the frame chrome (header, tabs, progress, status line), the
virtualized searchable ListView, and the LineInput editor.
config/:
default.toml is embedded and is the single source of truth for
defaults; user files are merged over it table by table.
keys.rs parses key specs and builds the per-context Keymap.
action.rs defines the Action, Context and ScreenId enums. Each action
declares the contexts it may be bound in. Lookup order is: popup or screen
context → navigation → global.
lyrics/: LRC parser and the async lookup service (local .lrc → cache →
LRCLIB). visualizer/: fifo reader thread with FFT, plus bar smoothing.
tags.rs: lofty-based tag writing.
Conventions
- Build MPD commands only through
mpd::commands or Command::arg; never
format raw command strings from user data. Filter expressions go through
protocol::filter_eq/filter_and.
- Prefer position-based commands (
move, delete) for actions users repeat
quickly. They stay correct even when sent before the local queue refreshes.
- Every new action needs:
- an
Action variant with its contexts and description;
- a default binding in
config/default.toml (a test enforces this);
- a README entry.
- Every new option needs:
- a documented entry in
default.toml;
- a field in the matching config struct (
deny_unknown_fields catches
typos);
- validation in
Config::validate if it has limits;
- README documentation.
- Screens must not block: do file and network I/O in tasks (see lyrics) or
spawn_blocking (see tag writes) and report back through AppEvent.