Files
tai/taicpp/docs/mplex.md
T

101 lines
6.5 KiB
Markdown

# mplex + uiflow (detz port)
Spec delta for the C++ port of tai's terminal multiplexer and the interactive UI flows. The
behavior specification is unchanged and lives in [../../mplex.md](../../mplex.md) and
[../../uiflow.md](../../uiflow.md); the code being transcribed is the `Mplex*` and `Plexer*` classes
of [../../tai.py](../../tai.py). This page records what the port adds: the file layout, the
Python-to-C++ mapping rules, the parity table, the tests and the known gaps.
## Goal
A literal transpilation with identical behavior: the same keys, commands, options, formats, wire
frames, control protocol, config file and error texts. Where Python and C++ differ at the language
level, the port reproduces Python's semantics (`mplex_py.h`) instead of adopting C++'s.
## Layout
All Mplex code is in `taicpp/src/mplex/`, namespace `detz::mplex`; the uiflow code is in
`taicpp/src/plexer/plexer_flow.{h,cpp}`, namespace `detz::plexer`. One class per header and source
pair; every file name is prefixed `mplex_` so the flat include path of the Makefile cannot clash with
other modules. Class and method names are the Python names (snake_case methods, `Mplex*` classes).
## Mapping rules
| Python | C++ |
|---|---|
| `str` | `std::string` (UTF-8) at API edges, `UStr` (`std::wstring`, one `wchar_t` per code point) wherever `len`, index or slice count code points |
| `bytes` | `Bytes` (`std::string` holding raw bytes) |
| `None` | `std::optional`, an empty `shared_ptr`, or `Color` 0 |
| iterated `dict` | `OrderedMap` (insertion order is iteration order) |
| `(fg, bg, attr)` / cell tuple | `Style` / `Cell` (`ch == 0` is a never written cell) |
| SGR colour fragment `"38;5;12"` | `Color` (32 bit pack, lossless for every fragment tai.py produces) |
| `MplexError`, `ValueError` | `MplexError`, `ValueError` (both `std::runtime_error`) |
| closures | `std::function` |
| back references (`pane.window`, `window.session`, layout `parent`) | `weak_ptr` with an accessor returning `shared_ptr` |
| modes and overlays on a client | `shared_ptr` held by the owner; `MplexServer::discard_pane` clears `pane.modes` to break the pane/mode cycle |
| `json`, `tomllib`, `re`, `fnmatch`, `textwrap`, `shlex`, `time.strftime`, `subprocess.run` | `mplex_json.h`, `mplex_toml.h`, `mplex_py.h` (`py::Regex`, `py::fnmatchcase`, `py::textwrap_wrap`, `py::shlex_quote`, `py::strftime`, `py::run_shell`) |
## Parity table
| tai.py symbol | C++ symbol | files |
|---|---|---|
| `MplexError` | `MplexError` | `mplex_common.h` |
| `MplexKeys` | `MplexKeys` | `mplex_keys.{h,cpp}` |
| `tui_parse_one` and helpers | `tui_parse_one` | `mplex_input.{h,cpp}` |
| `MplexArgs`, `MplexCommandParser` | same | `mplex_args.{h,cpp}` |
| `MplexStyle` | `MplexStyle` | `mplex_style.{h,cpp}` |
| `MplexOptions` | `MplexOptions` | `mplex_options.{h,cpp}` |
| `MplexBinding`, `MplexConfig`, `MPLEX_DEFAULT_KEYS` | same | `mplex_config.{h,cpp}`, `mplex_default_keys.cpp` (generated) |
| `MplexFormat` | `MplexFormat` | `mplex_format.{h,cpp}` |
| `MplexLayoutNode`, `MplexLayout` | same | `mplex_layout.{h,cpp}` |
| `TuiVtScreen` (as used by panes) | `MplexVtScreen` | `mplex_vtscreen.{h,cpp}` |
| `MplexPane`, `MplexWindow`, `MplexSession` | same | `mplex_pane.{h,cpp}` |
| `MplexCanvas`, `MplexStatusText`, `MplexFont` | same | `mplex_canvas.{h,cpp}` |
| `MplexMode`, `MplexClockMode` | same | `mplex_mode.{h,cpp}`, `mplex_clockmode.{h,cpp}` |
| `MplexCopyMode` | `MplexCopyMode` | `mplex_copymode.{h,cpp}` |
| `MplexPrompt` | `MplexPrompt` | `mplex_prompt.{h,cpp}` |
| `MplexPaneNumbers`, `MplexFlowScreen` | same | `mplex_panenumbers.{h,cpp}`, `mplex_flowscreen.{h,cpp}` |
| `MplexChoice`, `MplexChooser` | same | `mplex_chooser.{h,cpp}` |
| `MplexRenderer` | `MplexRenderer` | `mplex_renderer.{h,cpp}` |
| `MplexCtx`, `MplexTargets` | same | `mplex_ctx.{h,cpp}` |
| `MplexClientConn`, `MplexPending` | same | `mplex_clientconn.{h,cpp}` |
| `MplexServer` | `MplexServer` | `mplex_server.{h,cpp}` |
| `MplexCommands`, `MPLEX_COMMAND_SPECS`, `MPLEX_DEFAULT_FORMATS` | same | `mplex_commands.{h,cpp}`, `mplex_command_specs.cpp` (generated) |
| `MplexClient`, `MplexCli`, `NativePlexerBackend`, `plexer_should_wrap`, bashrc blocks | same | `mplex_client.{h,cpp}` |
| `PlexerController`, `NativePlexerController`, `TmuxPlexerController`, `PlexerControllerFactory` | same | `src/plexer/plexer_flow.{h,cpp}` |
| `validate_flow_*`, `PlexerFlow`, `PlexerBackend` | same | `src/plexer/plexer_flow.{h,cpp}` |
The two generated files are produced from the Python tables; they carry a header saying so and
`mplex_spec_test` compares them with `mplex.md`.
## Tests
- `tests/mplex_spec_test.cpp`: the C++ twin of `MplexSpecTests` and `UiflowDocTests`: `mplex.md`
and `uiflow.md` must list exactly the options, commands, default bindings, copy-mode commands, caps
and flow words that the C++ tables hold.
- `tests/mplex_foundation_test.cpp`: the Python-shaped helpers, JSON and TOML, against Python 3.14.
- `tests/mplex_vt_test.cpp`: the VT screen, input parser and keys against `TuiVtScreen` and `MplexKeys`.
- `tests/mplex_core_test.cpp`: args, parser, style, options, config, format and layout against tai.py.
- `tests/mplex_model_test.cpp`: panes, canvas, prompt, chooser, flow screen, renderer, targets.
- `tests/mplex_copy_test.cpp`: copy mode against tai.py's recorded tmux results and a seeded fuzz.
- `tests/mplex_server_test.cpp`, `tests/mplex_commands_test.cpp`: the real C++ server over its socket,
scripted command corpus compared with the Python server.
- `tests/mplex_flow_test.cpp`: `validate_flow_spec` and `PlexerFlow` against tai.py.
- `tests/mplex_e2e.py` with `tests/mplex_cli_main.cpp`: the real client under a pty, as `tests/mplex.py` does.
Expected values in these tests are produced by running tai.py (and tmux 3.6 where mplex.md names it
the oracle), never typed in from memory.
## Known gaps of the port
Listed next to what would close them; the final status of each is recorded in the milestone report.
- **Regular expressions** (`py::Regex`, `std::wregex` ECMAScript): Python-only syntax (lookbehind,
named groups, possessive quantifiers, `\Z`) is translated where an equivalent exists and rejected
with a loud error otherwise; copy-mode search then falls back to a literal, like Python does for an
invalid expression.
- **Unicode classification** (`str.isalpha`, `isprintable`, case mapping) uses the glibc C.UTF-8
tables, which may lag or lead CPython's `unicodedata` by a Unicode version.
- **TOML error texts** follow tomllib's wording; positions are reproduced, rare messages may differ.
- Anything else found while testing is added here, never hidden.