Skip to content

Expand coop vscode into coop editor with Zed support#416

Open
DarkaMaul wants to merge 1 commit into
mainfrom
dm/zed-editor
Open

Expand coop vscode into coop editor with Zed support#416
DarkaMaul wants to merge 1 commit into
mainfrom
dm/zed-editor

Conversation

@DarkaMaul

Copy link
Copy Markdown

coop editor opens VS Code or Zed connected to the guest over SSH; coop vscode remains as an alias. --editor is now a clap ValueEnum (code | zed) instead of a free-form string. When omitted, coop tries VS Code first, then falls through to Zed.

`coop editor` opens VS Code or Zed connected to the guest over SSH;
`coop vscode` remains as an alias. `--editor` is now a clap ValueEnum
(`code` | `zed`) instead of a free-form string. When omitted, coop
tries VS Code first, then falls through to Zed.

Zed connects with `zed ssh://coop-<name>/<path>` (macOS fallback:
`open zed://ssh/...`), reusing the same `~/.ssh/config` alias block
that VS Code's Remote-SSH uses — no new SSH machinery.

`.cargo/mutants.toml`: the renamed `open_editor` wrapper replaces the
`vscode` exclude; the pure strategy helpers (`zed_strategies`,
`editor_strategies`) stay in scope and are unit-tested.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread src/workspace.rs
}
}

let hints: &[&str] = match editor {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This per-editor hint selection has no test, and it lives inside launch_editor, which is excluded from the mutation sweep (\blaunch_editor\b in .cargo/mutants.toml). A mutant that swaps the Code/Zed arms or collapses None to a single hint survives both cargo test and cargo mutants. Extracting it into a small pure helper kept in mutation scope (e.g. fn install_hints(editor: Option<EditorKind>) -> &'static [&'static str]), with a per-arm assertion, would close the gap.

Comment thread src/workspace.rs
}];
if cfg!(target_os = "macos") {
strategies.push(LaunchStrategy {
name: "macOS open zed:// URL",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The macOS open zed://ssh/... fallback has no coverage — zed_strategies_first_is_zed_cli only asserts strategies[0] (the CLI form). This branch uses a different scheme/shape than the CLI arm (zed://ssh/... vs ssh://...), and since the mutation sweep runs on Linux the cfg!(target_os = "macos") arm is compiled out, so a broken URL here surfaces in neither tests nor mutants. A #[cfg(target_os = "macos")] test asserting strategies[1] is open zed://ssh/coop-test/workspace would cover it (the parallel vscode_strategies macOS arm is pre-existing but equally uncovered).

Comment thread src/workspace.rs
fn zed_strategies(host: &str, remote_path: &GuestPath) -> Vec<LaunchStrategy> {
// Zed shells out to the system `ssh`, so the `coop-<name>` alias in
// ~/.ssh/config supplies host, port, user, and key.
let url = format!("ssh://{host}{remote_path}");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Zed strategies interpolate the guest path straight into a URL — ssh://{host}{remote_path} here, and zed://ssh/{host}{remote_path} in the macOS fallback — with no percent-encoding. --project is validated only by GuestPath::absolute, which checks nothing but the leading / (src/paths.rs:41). So a path with URL-significant characters produces a malformed target: --project '/a#b' yields ssh://coop-test/a#b, where Zed treats #b as a fragment and opens /a; a space stays unencoded. The VS Code path is immune because it goes through argv (--remote <arg> <path>), not a URL. Consider percent-encoding the path segment, or noting the limitation.

Comment thread src/workspace.rs
editor: Option<EditorKind>,
) -> Result<()> {
let host = ssh_config_host(inst);
let strategies = editor_strategies(editor, &host, remote_path);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default (auto-detect) order built here is code CLI → (macOS) open VS Code → zed CLI → (macOS) open zed://, and the launch loop below only falls through on ErrorKind::NotFound. Any other spawn error on an earlier strategy — e.g. an earlier binary present but non-executable (PermissionDenied) — hits the Err(e) => return Err(...) arm and returns early, so Zed is never tried even when it is installed and working. With VS Code as the only editor this was harmless (nothing left to fall back to); now that Zed is a real fallback, this defeats the documented "try VS Code first, then Zed" contract. Treating a spawn error like a miss (push to tried, continue) would preserve fallthrough.

Comment thread src/workspace.rs
host: &str,
remote_path: &GuestPath,
) -> Vec<LaunchStrategy> {
let remote_arg = format!("ssh-remote+{host}");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remote_arg is built unconditionally, but only the VS Code strategies use it (the Some(Code) and None arms). On the Some(EditorKind::Zed) arm it is a wasted allocation. Moving it into the arms that consume it (or into vscode_strategies) keeps it scoped to its users.

Comment thread src/workspace.rs
fn editor_strategies_explicit_choice_pins_editor() {
let path = GuestPath::new("/workspace");
let code = editor_strategies(Some(EditorKind::Code), "coop-test", &path);
assert!(code.iter().all(|s| s.cmd != "zed"));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These assertions only check the absence of the other editor (all(|s| s.cmd != "zed")), which is vacuously true for an empty Vec — the test still passes if the pinned arm returned no strategies at all. Add a positive assertion that the chosen editor is actually present, e.g. assert_eq!(code[0].cmd, "code") / assert_eq!(zed[0].cmd, "zed").

Comment thread docs/editor.md

1. Writes an SSH config block for the instance into `~/.ssh/config`.
2. Launches VS Code with `code --remote ssh-remote+coop-{name} /workspace`.
2. Launches VS Code with `code --remote ssh-remote+coop-{name} /workspace`, falling back to Zed with `zed ssh://coop-{name}/workspace` when `code` is not installed.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This says coop falls back to Zed "when code is not installed", but on macOS editor_strategies(None, ...) inserts an open -a 'Visual Studio Code' strategy between the code CLI and Zed, so VS Code can still launch via the app when the code CLI is absent — Zed is only reached after that fallback also fails. The ## The coop editor command and ### Zed sections describe the full chain correctly; only this quick-start summary glosses the macOS step.

@hbrodin

hbrodin commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Review summary

7 findings posted inline. Scope: commit e60cfa3 (the coop vscodecoop editor / Zed change). The other commit in range (0c825e1, review tooling + docs restructure) was already reviewed via #409/#410 and is excluded.

Themes:

  • Zed URL construction — guest path interpolated into ssh:///zed:// URLs with no percent-encoding (the VS Code path is argv-safe); auto-detect fallthrough aborts on any non-NotFound spawn error, so Zed can be skipped even when installed.
  • Test coverage — the per-editor install-hint selection and the macOS open zed:// fallback are untested (and the hint logic sits in mutation-excluded launch_editor); editor_strategies_explicit_choice_pins_editor asserts only absence (all(...)), which is vacuously true on an empty Vec.
  • Minorremote_arg allocated unconditionally but unused on the Zed arm; the docs/editor.md quick-start glosses the macOS open -a step in the fallback chain.

No security, convention, API-usage, or comment issues. The rename is consistent across code, docs, completions, and .cargo/mutants.toml; the #[command(alias = "vscode")] preserves the old invocation; EditorKind as a clap::ValueEnum is the right enum-over-string move; Zed's ssh://host/path CLI form and ~/.ssh/config reuse were verified against current Zed docs.

Coverage: ran review-correctness, review-design, review-conventions, review-security, review-api-usage, review-tests, review-docs, review-comments (all 8). None skipped.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants