Implements a VS Code-style command palette with fuzzy search, workspace/surface switching, rename mode, and keyboard navigation. Closes https://github.com/manaflow-ai/cmux/issues/133
194 lines
6.9 KiB
Python
194 lines
6.9 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Regression test: VSCode-like command palette modes.
|
|
|
|
Validates:
|
|
- Cmd+Shift+P opens commands mode (leading '>' semantics).
|
|
- Cmd+P opens workspace/tab switcher mode.
|
|
- Repeating Cmd+Shift+P or Cmd+P toggles visibility (open/close).
|
|
- Switcher search can jump to another workspace by pressing Enter.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent))
|
|
from cmux import cmux, cmuxError
|
|
|
|
|
|
SOCKET_PATH = os.environ.get("CMUX_SOCKET", "/tmp/cmux-debug.sock")
|
|
|
|
|
|
def _wait_until(predicate, timeout_s: float = 5.0, interval_s: float = 0.05, message: str = "timeout") -> None:
|
|
start = time.time()
|
|
while time.time() - start < timeout_s:
|
|
if predicate():
|
|
return
|
|
time.sleep(interval_s)
|
|
raise cmuxError(message)
|
|
|
|
|
|
def _palette_visible(client: cmux, window_id: str) -> bool:
|
|
payload = client._call("debug.command_palette.visible", {"window_id": window_id}) or {}
|
|
return bool(payload.get("visible"))
|
|
|
|
|
|
def _palette_results(client: cmux, window_id: str, limit: int = 20) -> dict:
|
|
return client.command_palette_results(window_id=window_id, limit=limit)
|
|
|
|
|
|
def _palette_input_selection(client: cmux, window_id: str) -> dict:
|
|
return client._call("debug.command_palette.rename_input.selection", {"window_id": window_id}) or {}
|
|
|
|
|
|
def _wait_for_palette_input_caret_at_end(
|
|
client: cmux,
|
|
window_id: str,
|
|
expected_text_length: int,
|
|
message: str,
|
|
timeout_s: float = 1.2,
|
|
) -> None:
|
|
def _matches() -> bool:
|
|
selection = _palette_input_selection(client, window_id)
|
|
if not selection.get("focused"):
|
|
return False
|
|
text_length = int(selection.get("text_length") or 0)
|
|
selection_location = int(selection.get("selection_location") or 0)
|
|
selection_length = int(selection.get("selection_length") or 0)
|
|
return (
|
|
text_length == expected_text_length
|
|
and selection_location == expected_text_length
|
|
and selection_length == 0
|
|
)
|
|
|
|
_wait_until(_matches, timeout_s=timeout_s, message=message)
|
|
|
|
|
|
def _set_palette_visible(client: cmux, window_id: str, visible: bool) -> None:
|
|
if _palette_visible(client, window_id) == visible:
|
|
return
|
|
client._call("debug.command_palette.toggle", {"window_id": window_id})
|
|
_wait_until(
|
|
lambda: _palette_visible(client, window_id) == visible,
|
|
timeout_s=3.0,
|
|
message=f"palette visibility did not become {visible}",
|
|
)
|
|
|
|
|
|
def main() -> int:
|
|
with cmux(SOCKET_PATH) as client:
|
|
client.activate_app()
|
|
time.sleep(0.2)
|
|
|
|
window_id = client.current_window()
|
|
for row in client.list_windows():
|
|
other_id = str(row.get("id") or "")
|
|
if other_id and other_id != window_id:
|
|
client.close_window(other_id)
|
|
time.sleep(0.2)
|
|
|
|
client.focus_window(window_id)
|
|
client.activate_app()
|
|
time.sleep(0.2)
|
|
|
|
ws_a = client.new_workspace(window_id=window_id)
|
|
client.select_workspace(ws_a)
|
|
client.rename_workspace("alpha-workspace", workspace=ws_a)
|
|
|
|
ws_b = client.new_workspace(window_id=window_id)
|
|
client.select_workspace(ws_b)
|
|
client.rename_workspace("bravo-workspace", workspace=ws_b)
|
|
|
|
client.select_workspace(ws_a)
|
|
_wait_until(
|
|
lambda: client.current_workspace() == ws_a,
|
|
message="failed to select workspace alpha before switcher jump",
|
|
)
|
|
|
|
_set_palette_visible(client, window_id, False)
|
|
|
|
# Cmd+P: switcher mode.
|
|
client.simulate_shortcut("cmd+p")
|
|
_wait_until(
|
|
lambda: _palette_visible(client, window_id),
|
|
message="cmd+p did not open command palette",
|
|
)
|
|
_wait_until(
|
|
lambda: str(_palette_results(client, window_id).get("mode") or "") == "switcher",
|
|
message="cmd+p did not open switcher mode",
|
|
)
|
|
|
|
time.sleep(0.2)
|
|
client.simulate_type("bravo")
|
|
_wait_until(
|
|
lambda: "bravo" in str(_palette_results(client, window_id).get("query") or "").strip().lower(),
|
|
message="switcher query did not include bravo",
|
|
)
|
|
switched_rows = (_palette_results(client, window_id, limit=12).get("results") or [])
|
|
if not switched_rows:
|
|
raise cmuxError("switcher returned no rows for workspace query")
|
|
top_id = str((switched_rows[0] or {}).get("command_id") or "")
|
|
if not top_id.startswith("switcher."):
|
|
raise cmuxError(f"expected switcher row on top for cmd+p query, got: {switched_rows[0]}")
|
|
|
|
client.simulate_shortcut("enter")
|
|
_wait_until(
|
|
lambda: not _palette_visible(client, window_id),
|
|
message="palette did not close after selecting switcher row",
|
|
)
|
|
_wait_until(
|
|
lambda: client.current_workspace() == ws_b,
|
|
message="Enter on switcher result did not move to target workspace",
|
|
)
|
|
|
|
# Cmd+Shift+P: commands mode.
|
|
client.simulate_shortcut("cmd+shift+p")
|
|
_wait_until(
|
|
lambda: _palette_visible(client, window_id),
|
|
message="cmd+shift+p did not open command palette",
|
|
)
|
|
_wait_until(
|
|
lambda: str(_palette_results(client, window_id).get("mode") or "") == "commands",
|
|
message="cmd+shift+p did not open commands mode",
|
|
)
|
|
_wait_for_palette_input_caret_at_end(
|
|
client,
|
|
window_id,
|
|
expected_text_length=1,
|
|
message="cmd+shift+p should prefill '>' with caret at end (not selected)",
|
|
)
|
|
|
|
command_rows = (_palette_results(client, window_id, limit=8).get("results") or [])
|
|
if not command_rows:
|
|
raise cmuxError("commands mode returned no rows")
|
|
top_command_id = str((command_rows[0] or {}).get("command_id") or "")
|
|
if not top_command_id.startswith("palette."):
|
|
raise cmuxError(f"expected command row in commands mode, got: {command_rows[0]}")
|
|
|
|
# Repeating either shortcut should toggle visibility.
|
|
client.simulate_shortcut("cmd+shift+p")
|
|
_wait_until(
|
|
lambda: not _palette_visible(client, window_id),
|
|
message="second cmd+shift+p did not close the command palette",
|
|
)
|
|
|
|
client.simulate_shortcut("cmd+p")
|
|
_wait_until(
|
|
lambda: _palette_visible(client, window_id)
|
|
and str(_palette_results(client, window_id).get("mode") or "") == "switcher",
|
|
message="cmd+p did not reopen switcher mode after toggle-close",
|
|
)
|
|
client.simulate_shortcut("cmd+p")
|
|
_wait_until(
|
|
lambda: not _palette_visible(client, window_id),
|
|
message="second cmd+p did not close the command palette",
|
|
)
|
|
|
|
print("PASS: command palette cmd+p/cmd+shift+p open correct modes and toggle on repeat")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|