//! Mode-system wiring acceptance over the real daemon process. //! //! One ordered scenario keeps the dispatch assertions observable: every //! checkpoint is itself reached through a wire key event, and the final //! statusline marker is published only after all Lua-side assertions pass. //! Statusline assertions consume the daemon's real grid-render payloads. use std::os::unix::net::UnixStream; use std::path::Path; use std::time::{Duration, Instant}; use pmacs::cell::{Cell, CellSize, Glyph}; use pmacs::protocol::{ ADVERTISED_PROTOCOL_VERSION, AttachRequest, FrontendEvent, FrontendId, Hello, InstanceMessage, Key, KeyEvent, Modifiers, }; use pmacs::transport::{read_message, write_message}; mod common; use common::daemon::{TestDaemon, build_default_caps}; struct Client { stream: UnixStream, frontend_id: FrontendId, } const ROWS: u32 = 30; // Each split must leave room for a macOS temp path plus the mode segment. const COLS: u32 = 320; struct Grid { cells: Vec, } impl Grid { fn new() -> Self { Self { cells: vec![Cell::default(); (ROWS * COLS) as usize], } } fn apply(&mut self, spans: Vec) { for span in spans { let start = (span.start.row * COLS + span.start.col) as usize; for (offset, cell) in span.cells.into_iter().enumerate() { self.cells[start + offset] = cell; } } } fn text(&self) -> String { let mut text = String::with_capacity((ROWS * (COLS + 1)) as usize); for row in 0..ROWS { for column in 0..COLS { let cell = &self.cells[(row * COLS + column) as usize]; let ch = match &cell.glyph { Glyph::Char(ch) => *ch, Glyph::Cluster(bytes) => std::str::from_utf8(bytes) .ok() .and_then(|value| value.chars().next()) .unwrap_or(' '), Glyph::Continuation => ' ', }; text.push(ch); } text.push('\n'); } text } } fn attach(daemon: &TestDaemon) -> (Client, Grid) { let mut stream = daemon.connect(); stream .set_read_timeout(Some(Duration::from_secs(5))) .expect("set daemon handshake timeout"); let hello: Hello = read_message(&mut stream).expect("read daemon Hello"); assert_eq!(hello.protocol_version, ADVERTISED_PROTOCOL_VERSION); write_message( &mut stream, &AttachRequest { protocol_version: hello.protocol_version, frontend_capabilities: build_default_caps(), initial_size: CellSize::new(ROWS, COLS), }, ) .expect("attach grid frontend"); stream .set_read_timeout(Some(Duration::from_millis(100))) .expect("set daemon frame timeout"); let deadline = Instant::now() + Duration::from_secs(5); let mut grid = Grid::new(); loop { assert!( Instant::now() < deadline, "initial full-grid frame timed out" ); if let Ok(InstanceMessage::CellDelta { spans, full_grid: true, }) = read_message::(&mut stream) { grid.apply(spans); break; } } ( Client { stream, frontend_id: hello.assigned_frontend_id, }, grid, ) } fn send_key(client: &mut Client, key: Key, mods: Modifiers) { write_message( &mut client.stream, &FrontendEvent::Key(KeyEvent { frontend_id: client.frontend_id, key, mods, timestamp_ns: 0, }), ) .expect("send daemon key event"); } fn send_ctrl_chord(client: &mut Client, second: char) { send_key(client, Key::Char('c'), Modifiers::CTRL); send_key(client, Key::Char(second), Modifiers::CTRL); } fn checkpoint(client: &mut Client, n: u8) { send_key(client, Key::F(n), Modifiers::NONE); } fn pump_grid_until( client: &mut Client, grid: &mut Grid, what: &str, predicate: impl Fn(&str) -> bool, ) -> String { let deadline = Instant::now() + Duration::from_secs(10); loop { let text = grid.text(); assert!( !text.contains("MSW_ERROR:"), "daemon-side Lua checkpoint failed:\n{text}" ); if predicate(&text) { return text; } assert!( Instant::now() < deadline, "grid update timed out waiting for {what}; current grid:\n{text}" ); if let Ok(InstanceMessage::CellDelta { spans, .. }) = read_message::(&mut client.stream) { grid.apply(spans); } } } fn lua_string(path: &Path) -> String { format!("{:?}", path.to_string_lossy()) } #[allow( clippy::too_many_lines, reason = "one ordered daemon session preserves dispatch state across all ten acceptance checks" )] #[test] fn mode_system_wiring_is_observable_end_to_end() { let fixtures = tempfile::tempdir().expect("fixture tempdir"); let rust = fixtures.path().join("dispatch.rs"); let python = fixtures.path().join("dispatch.py"); let unknown = fixtures.path().join("dispatch.txt"); let server = fixtures.path().join("dispatch.msw"); std::fs::write(&rust, "// MSW_RUST_FIXTURE\nfn main() {}\n").unwrap(); std::fs::write(&python, "# MSW_PYTHON_FIXTURE\nprint('ok')\n").unwrap(); std::fs::write(&unknown, "MSW_UNKNOWN_FIXTURE\n").unwrap(); std::fs::write(&server, "MSW_SERVER_ONLY_FIXTURE\n").unwrap(); let init_template = r#" -- Ordinary fixtures must never inherit the built-in real-server registry. pmacs.lsp.config = {} pmacs.lsp.filetypes.msw = "serveronly" local RUST_PATH = __RUST_PATH__ local PYTHON_PATH = __PYTHON_PATH__ local UNKNOWN_PATH = __UNKNOWN_PATH__ local SERVER_PATH = __SERVER_PATH__ local S = { rust_hits = 0 } _G.MSW_STATE = S _G.MSW_RESULT = false _G.MSW_ERROR = nil local function eq(actual, expected, label) assert(actual == expected, label .. ": expected " .. tostring(expected) .. ", got " .. tostring(actual)) end local function current_modes(expected) local modes = pmacs.editor.active_modes() if expected == nil then eq(#modes, 0, "active mode count") else eq(#modes, 1, "active mode count") eq(modes[1], expected, "active mode") end end local function command(name, body) pmacs.command.define { name = name, description = "mode-system wiring acceptance command " .. name, fn = function() local ok, err = pcall(body) if not ok then _G.MSW_ERROR = ("MSW_ERROR:" .. tostring(err)):sub(1, 200) end end, } end local function global_key(sequence, name) pmacs.keymap.bind { scope = "global", sequence = sequence, command = name } end command("test.rust-only", function() S.rust_hits = S.rust_hits + 1 end) command("test.priority-buffer", function() S.priority_hit = "buffer" end) command("test.priority-mode", function() S.priority_hit = "mode" end) command("test.priority-global", function() S.priority_hit = "global" end) command("test.parity-mode", function() S.parity_hit = "mode" end) command("test.parity-global", function() S.parity_hit = "global" end) pmacs.keymap.bind { scope = "mode", mode = "rust", sequence = "C-c C-c", command = "test.rust-only", } pmacs.keymap.bind { scope = "mode", mode = "rust", sequence = "C-c C-p", command = "test.priority-mode", } pmacs.keymap.bind { scope = "global", sequence = "C-c C-p", command = "test.priority-global", } pmacs.keymap.bind { scope = "mode", mode = "rust", sequence = "C-c C-d", command = "test.parity-mode", } pmacs.keymap.bind { scope = "global", sequence = "C-c C-d", command = "test.parity-global", } pmacs.statusline.register { name = "mode-system-result", side = "right", priority = 999, face = "ui.modeline", fn = function() if _G.MSW_ERROR then return _G.MSW_ERROR end if _G.MSW_RESULT then return "MODE-SYSTEM-WIRING-PASS" end return nil end, } command("test.step-1", function() local provider for _, candidate in ipairs(pmacs.statusline.providers()) do if candidate.name == "mode" then provider = candidate end end assert(provider ~= nil, "built-in mode provider is registered") eq(provider.side, "left", "mode provider side") eq(provider.priority, 0, "mode provider priority") eq(provider.face, "ui.modeline", "mode provider face") eq(provider.enabled, true, "mode provider enabled") S.rust = pmacs.buffer.find_or_open(RUST_PATH) eq(pmacs.buffer.major_mode(S.rust), "rust", "rust initialization") current_modes("rust") S.python = pmacs.buffer.find_or_open(PYTHON_PATH) eq(pmacs.buffer.major_mode(S.python), "python", "python initialization") current_modes("python") S.unknown = pmacs.buffer.find_or_open(UNKNOWN_PATH) eq(pmacs.buffer.major_mode(S.unknown), nil, "unknown initialization") current_modes(nil) S.server = pmacs.buffer.find_or_open(SERVER_PATH) eq(pmacs.buffer.major_mode(S.server), "serveronly", "server-only initialization") current_modes("serveronly") eq(pmacs.parse._has_view(S.server), false, "server-only parse view") pmacs.keymap.bind { scope = "buffer", buffer = S.rust, sequence = "C-c C-p", command = "test.priority-buffer", } -- Leave a Rust-active/Python-passive split for real statusline evaluation. pmacs.window.switch_buffer(S.rust) pmacs.window.split_vertical() pmacs.window.focus_next() pmacs.window.switch_buffer(S.python) pmacs.window.focus_next() eq(tostring(pmacs.window.buffer()), tostring(S.rust), "focused split buffer") eq(pmacs.buffer.major_mode(S.rust), "rust", "rust survived switching") eq(pmacs.buffer.major_mode(S.python), "python", "python survived switching") end) command("test.step-2", function() eq(S.rust_hits, 1, "Rust mode dispatch") pmacs.window.switch_buffer(S.python) current_modes("python") end) command("test.step-3", function() eq(S.rust_hits, 1, "Python must not dispatch Rust binding") pmacs.window.switch_buffer(S.unknown) eq(pmacs.buffer.major_mode(S.unknown), nil, "unknown stays mode-less") current_modes(nil) end) command("test.step-4", function() eq(S.rust_hits, 1, "mode-less buffer must not dispatch Rust binding") pmacs.window.switch_buffer(S.rust) S.priority_hit = nil end) command("test.step-5", function() eq(S.priority_hit, "buffer", "buffer scope precedence") pmacs.keymap.unbind { scope = "buffer", buffer = S.rust, sequence = "C-c C-p", } S.priority_hit = nil end) command("test.step-6", function() eq(S.priority_hit, "mode", "mode scope precedence") pmacs.window.switch_buffer(S.python) S.priority_hit = nil end) command("test.step-7", function() eq(S.priority_hit, "global", "global scope fallback") pmacs.window.switch_buffer(S.rust) S.parity_hit = nil end) command("test.step-8", function() eq(S.parity_hit, "mode", "parity binding dispatch") local described = pmacs.describe.key("C-c C-d") assert(described ~= nil, "describe.key returns the mode binding") eq(described.command, "test.parity-mode", "describe.key command") eq(described.scope, "mode:rust", "describe.key scope") local help_id = pmacs.help.show_key("C-c C-d") assert(help_id ~= nil, "help.show_key returns a help buffer") local body = help_id:slice(0, help_id:len()) assert(body:find("Runs: %[command: test%.parity%-mode%]"), body) assert(body:find("Scope: mode:rust", 1, true), body) help_id = pmacs.help.show_command("test.parity-mode") body = help_id:slice(0, help_id:len()) local link_start = body:find("%[key: C%-c C%-d @mode:rust%]") assert(link_start ~= nil, "mode command help link carries @mode:rust: " .. body) pmacs.window.switch_buffer(help_id) local followed = pmacs.help.follow_link(link_start + 6) assert(followed ~= nil, "mode key link follows while *help* is active") local followed_body = followed:slice(0, followed:len()) assert(followed_body:find("Runs: %[command: test%.parity%-mode%]"), followed_body) assert(followed_body:find("Scope: mode:rust", 1, true), followed_body) pmacs.window.switch_buffer(S.rust) pmacs.buffer.set_major_mode(S.rust, "markdown") pmacs.window.switch_buffer(S.python) pmacs.window.switch_buffer(S.rust) eq(pmacs.buffer.major_mode(S.rust), "markdown", "explicit override survives switches") current_modes("markdown") end) command("test.step-9", function() eq(pmacs.buffer.major_mode(S.rust), "markdown", "override remains live") pmacs.buffer.set_major_mode(S.rust, nil) pmacs.window.switch_buffer(S.python) pmacs.window.switch_buffer(S.rust) eq(pmacs.buffer.major_mode(S.rust), nil, "explicit clear survives switches") current_modes(nil) S.clear_baseline = S.rust_hits end) command("test.step-10", function() eq(S.rust_hits, S.clear_baseline, "cleared Rust mode must not dispatch") pmacs.window.switch_buffer(S.server) eq(pmacs.buffer.major_mode(S.server), "serveronly", "server-only mode survives") current_modes("serveronly") eq(pmacs.parse._has_view(S.server), false, "server-only language stays parser-free") pmacs.window.switch_buffer(S.unknown) eq(pmacs.buffer.major_mode(S.unknown), nil, "unknown mode remains nil") current_modes(nil) _G.MSW_RESULT = true end) for n = 1, 10 do global_key("", "test.step-" .. n) end "#; let init = init_template .replace("__RUST_PATH__", &lua_string(&rust)) .replace("__PYTHON_PATH__", &lua_string(&python)) .replace("__UNKNOWN_PATH__", &lua_string(&unknown)) .replace("__SERVER_PATH__", &lua_string(&server)); let daemon = TestDaemon::spawn_with_config(&init); let (mut client, mut grid) = attach(&daemon); // Initialize through real after-load hooks and leave Rust focused with // Python in the passive split. The daemon's ordinary grid painter must // render each window from its own buffer context. checkpoint(&mut client, 1); let split_text = pump_grid_until( &mut client, &mut grid, "Rust-active/Python-passive mode lines", |text| { text.contains("dispatch.rs") && text.contains("dispatch.py") && text.contains("(rust)") && text.contains("(python)") }, ); let split_modeline = split_text .lines() .find(|line| line.contains("dispatch.rs") && line.contains("dispatch.py")) .expect("both split mode lines occupy the split's modeline row"); assert!(split_modeline.contains("(rust)"), "{split_modeline}"); assert!(split_modeline.contains("(python)"), "{split_modeline}"); // 1-2: the exact mode binding fires in Rust, not Python or no-mode text. send_ctrl_chord(&mut client, 'c'); checkpoint(&mut client, 2); send_ctrl_chord(&mut client, 'c'); checkpoint(&mut client, 3); send_ctrl_chord(&mut client, 'c'); // The active unknown-language pane omits its mode segment while the // passive Python pane retains its own. This distinguishes empty output // from a provider accidentally reading the focused/other buffer. let unknown_text = pump_grid_until(&mut client, &mut grid, "mode-less active buffer", |text| { text.contains("dispatch.txt") && text.contains("dispatch.py") }); let unknown_modeline = unknown_text .lines() .find(|line| line.contains("dispatch.txt") && line.contains("dispatch.py")) .expect("unknown and passive Python mode lines share a row"); let passive_start = unknown_modeline .find("dispatch.py") .expect("passive Python buffer name"); assert!( !unknown_modeline[..passive_start].contains('('), "unknown-language mode line must omit a mode segment: {unknown_modeline}" ); assert!( unknown_modeline[passive_start..].contains("(python)"), "passive Python mode line keeps its own mode: {unknown_modeline}" ); // 5: buffer-local, then mode, then global, all driven through dispatch. checkpoint(&mut client, 4); send_ctrl_chord(&mut client, 'p'); checkpoint(&mut client, 5); send_ctrl_chord(&mut client, 'p'); checkpoint(&mut client, 6); send_ctrl_chord(&mut client, 'p'); // 10: first prove the mode binding dispatched, then compare describe, // show-key, and followed @mode link rendering against that result. checkpoint(&mut client, 7); send_ctrl_chord(&mut client, 'd'); checkpoint(&mut client, 8); // 7-8: override and clear each survive switch-away/back; the cleared mode // no longer dispatches the detected-language binding. checkpoint(&mut client, 9); send_ctrl_chord(&mut client, 'c'); checkpoint(&mut client, 10); // The marker is painted only if every Lua assertion, including // active_modes and server-only parse gating, completed successfully. pump_grid_until( &mut client, &mut grid, "mode-system success marker", |text| text.contains("MODE-SYSTEM-WIRING-PASS"), ); }