Files
zed/crates/cli/Cargo.toml
T
Eric HolkandNathan Sobo afed924a56 cli: Add first-run prompt for default open behavior and abstract IPC transport (#53663)
This PR adds the `cli_default_open_behavior` setting and a first-run TUI
prompt
that appears when `zed <path>` is invoked without flags while existing
windows are
open and the setting hasn't been configured yet.

## What it does

### Setting and prompt

- Adds a new `cli_default_open_behavior` workspace setting with two
values:
  `existing_window` (default) and `new_window`.
- When the user runs `zed <path>` for the first time with existing Zed
windows
open, a `dialoguer::Select` prompt in the CLI asks them to choose their
  preferred behavior. The choice is persisted to `settings.json`.
- The prompt is skipped when:
  - An explicit flag (`-n`, `-e`, `-a`) is given
  - No existing Zed windows are open
  - The setting is already configured in `settings.json`
- The paths being opened are already contained in an existing workspace

### IPC transport abstraction

- Introduces a `CliResponseSink` trait in the `cli` crate that abstracts
`IpcSender<CliResponse>`, with an implementation for the real IPC
sender.
- Replaces `IpcSender<CliResponse>` with `Box<dyn CliResponseSink>` /
  `&dyn CliResponseSink` across all signatures in `open_listener.rs`:
  `OpenRequestKind::CliConnection`, `handle_cli_connection`,
`maybe_prompt_open_behavior`, `open_workspaces`, `open_local_workspace`.
- Extracts the inline CLI response loop from `main.rs` into a testable
  `cli::run_cli_response_loop` function.
- Switches the request channel from bounded `mpsc::channel(16)` to
`mpsc::unbounded()`, eliminating `smol::block_on` in the bridge thread.

### End-to-end tests

Seven new tests exercise both the CLI-side response loop and the
Zed-side
handler connected through in-memory channels, using `allow_parking()` so
the
real `cli::run_cli_response_loop` runs on an OS thread while the GPUI
executor
drives the Zed handler:

- No flags, no windows → no prompt, opens new window
- No flags, existing windows, user picks "existing window" → prompt,
setting persisted
- No flags, existing windows, user picks "new window" → prompt, setting
persisted
- Setting already configured → no prompt
- Paths already in existing workspace → no prompt
- Explicit `-e` flag → no prompt
- Explicit `-n` flag → no prompt

Existing tests that previously used `ipc::channel()` now use a
`DiscardResponseSink`, removing OS-level IPC from all tests.

Release Notes:

- Added a first-run prompt when using `zed <path>` to choose between
opening
in an existing window or a new window. The choice is saved to settings
and
  can be changed later via the `cli_default_open_behavior` setting.

---------

Co-authored-by: Nathan Sobo <nathan@zed.dev>
2026-04-10 21:37:43 -07:00

55 lines
1.1 KiB
TOML

[package]
name = "cli"
version = "0.1.0"
edition.workspace = true
publish.workspace = true
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/cli.rs"
doctest = false
[[bin]]
name = "cli"
path = "src/main.rs"
[features]
no-bundled-uninstall = []
default = []
[dependencies]
anyhow.workspace = true
askpass.workspace = true
clap.workspace = true
collections.workspace = true
console.workspace = true
dialoguer.workspace = true
ipc-channel = "0.19"
parking_lot.workspace = true
paths.workspace = true
release_channel.workspace = true
serde.workspace = true
util.workspace = true
tempfile.workspace = true
rayon.workspace = true
walkdir = "2.5"
[dev-dependencies]
serde_json.workspace = true
util = { workspace = true, features = ["test-support"] }
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
exec.workspace = true
fork.workspace = true
[target.'cfg(target_os = "macos")'.dependencies]
core-foundation.workspace = true
core-services = "0.2"
plist = "1.3"
[target.'cfg(target_os = "windows")'.dependencies]
windows.workspace = true