6.5 KiB
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 and
../../uiflow.md; the code being transcribed is the Mplex* and Plexer* classes
of ../../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 ofMplexSpecTestsandUiflowDocTests:mplex.mdanduiflow.mdmust 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 againstTuiVtScreenandMplexKeys.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_specandPlexerFlowagainst tai.py.tests/mplex_e2e.pywithtests/mplex_cli_main.cpp: the real client under a pty, astests/mplex.pydoes.
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::wregexECMAScript): 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'sunicodedataby 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.