From a22129c343d9236277dcc0353c48cb53bc3417ce Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Tue, 29 Sep 2026 18:32:37 -0300 Subject: pardes --wait (-w) in a pane returns only once the pane its file landed in is deleted, so it can be $EDITOR A launch inside a pane forwards its file to the session's look and returned at once, so fish's Ctrl-O (edit_command_buffer), git commit and crontab -e read their still-unedited file back and went on. With --wait, as acme's E against B and read the way plan9port's E reads acme's log, it finds the pane /index names the file by (the one already showing it, if open), follows /log on one connection with 'follow new', reads /index once more to catch a Del that came first, and then blocks with no deadline until that pane's del record: exit 0, or 1 when the connection ends with the session. The 9P client's one-shot requests keep their 2 s deadline; its new follow takes it for the setup only. Outside pardes nothing changes. --help, docs/fs.md, the README and the 9P skill say to set EDITOR='pardes --wait', which GIT_EDITOR follows. Tested in fs.py (it returns within 50 ms of the Del, a second -w waits on the same pane, one whose session is killed exits 1) and end to end: fish 4.8 in a detached session, echo hi, Ctrl-O, the waiting launch idle in poll, the line edited over 9P, Save, Del, and fish ran echo edited. Co-Authored-By: Claude Opus 5.5 --- test/fs.py | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) (limited to 'test') diff --git a/test/fs.py b/test/fs.py index 7401861b..db10f32d 100644 --- a/test/fs.py +++ b/test/fs.py @@ -1224,6 +1224,47 @@ def test(binary, quic=False): cell[0] for cell in local.screen()['cells']) assert client.read('/index') == before + # --wait (acme's E): a launch inside a pane lasts until the pane it + # opened is deleted, as an $EDITOR must; one on a file already open + # waits on that pane; a session that goes away ends it with 1. + with session(binary, root, 'wait') as (client, address): + status = dict(line.split(maxsplit=1) for line in client.read('/status').decode().splitlines()) + env = {k: v for k, v in os.environ.items() if k != 'PARDES_FORWARD_LOOK'} + env.update(PARDES_9P=str(address), PARDES_PANE='1', PARDES_PID=status['pid']) + edited = root / 'wait edit.txt' + edited.write_bytes(b'edit me\n') + + def serials(): + return {int(row.split()[0]) for row in client.read('/index').splitlines()} + + def launch(flag): + before = serials() + child = subprocess.Popen([binary, flag, str(edited)], cwd=root, env=env, + stdout=subprocess.DEVNULL, stderr=subprocess.PIPE) + deadline = time.monotonic() + 3 + while child.poll() is None and not serials() - before and time.monotonic() < deadline: + time.sleep(.01) + return child + + first = launch('--wait') + opened, = serials() - {1} + second = launch('-w') + time.sleep(.5) + assert first.poll() is None and second.poll() is None, (first.stderr.read(), second.stderr.read()) + assert serials() == {1, opened} + client.write(f'/pane/{opened}/body', b'edited\n', truncate=True) + execute(client, opened, 'Save') + client.remove(f'/pane/{opened}') + deleted = time.monotonic() + assert first.wait(timeout=5) == 0 + # Woken by the `del` record, not a poll. + assert time.monotonic() - deleted < .05, time.monotonic() - deleted + assert second.wait(timeout=5) == 0 + assert edited.read_bytes() == b'edited\n' + orphan = launch('--wait') + assert orphan.poll() is None + assert orphan.wait(timeout=5) == 1 + for tty in [False, True]: name = 'lsp-tty' if tty else 'lsp-detached' with session(binary, root, name, tty=tty) as (client, _): -- cgit v1.3