This fixes a regression where `zed -n .` in a subdirectory of an
already-open
project would redirect to the parent window instead of creating a new
one.
The root cause was that commit b3566fcf55 ("Adjust `zed -n` behavior")
made
`-n` run the worktree matching loop with subdirectory matching enabled,
when
previously `-n` skipped matching entirely.
## Changes
### Bug fix
- **Restore `-n` to always create a new window.** No worktree matching,
no
exceptions. This matches the behavior from when `-n` was first
introduced.
### New `--classic` flag
- Adds a hidden `--classic` CLI flag that explicitly selects the
pre-sidebar
default behavior: new window for directories, reuse existing window for
files already in an open worktree.
- The `cli_default_open_behavior` setting now toggles between `-e` (add
to
sidebar) and `--classic` behavior. When set to `new_window`, the classic
logic is used instead of unconditionally opening a new window.
### Refactor CLI open options
Replaces the old grab-bag of `open_new_workspace: Option<bool>`,
`force_existing_window: bool`, `classic: bool`, and `reuse: bool` with:
- **`cli::CliOpenBehavior` enum** — a single enum on the IPC boundary
with
variants `Default`, `AlwaysNew`, `Add`, `ExistingWindow`, `Classic`, and
`Reuse`.
- **`workspace::WorkspaceMatching` enum** — describes how to match paths
against existing worktrees (`None`, `MatchExact`, `MatchSubdirectory`).
- **`workspace::OpenOptions`** — uses `WorkspaceMatching` plus a simple
`add_dirs_to_sidebar: bool` instead of overlapping boolean flags.
The translation from CLI enum to workspace options happens in
`open_listener.rs`, keeping both layers clean and independent.
Release Notes:
- N/A
103 lines
3.5 KiB
Rust
103 lines
3.5 KiB
Rust
use anyhow::Result;
|
|
use collections::HashMap;
|
|
pub use ipc_channel::ipc;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
pub struct IpcHandshake {
|
|
pub requests: ipc::IpcSender<CliRequest>,
|
|
pub responses: ipc::IpcReceiver<CliResponse>,
|
|
}
|
|
|
|
/// Controls how CLI paths are opened — whether to reuse existing windows,
|
|
/// create new ones, or add to the sidebar.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum OpenBehavior {
|
|
/// Consult the user's `cli_default_open_behavior` setting to choose between
|
|
/// `ExistingWindow` or `Classic`.
|
|
#[default]
|
|
Default,
|
|
/// Always create a new window. No matching against existing worktrees.
|
|
/// Corresponds to `zed -n`.
|
|
AlwaysNew,
|
|
/// Match broadly including subdirectories, and fall back to any existing
|
|
/// window if no worktree matched. Corresponds to `zed -a`.
|
|
Add,
|
|
/// Open directories as a new workspace in the current Zed window's sidebar.
|
|
/// Reuse existing windows for files in open worktrees.
|
|
/// Corresponds to `zed -e`.
|
|
ExistingWindow,
|
|
/// New window for directories, reuse existing window for files in open
|
|
/// worktrees. The classic pre-sidebar behavior.
|
|
/// Corresponds to `zed --classic`.
|
|
Classic,
|
|
/// Replace the content of an existing window with a new workspace.
|
|
/// Corresponds to `zed -r`.
|
|
Reuse,
|
|
}
|
|
|
|
/// The setting-level enum for configuring default behavior. This only has
|
|
/// two values because the other modes are always explicitly requested via
|
|
/// CLI flags.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum CliBehaviorSetting {
|
|
/// Open directories as a new workspace in the current Zed window's sidebar.
|
|
ExistingWindow,
|
|
/// Classic behavior: open directories in a new window, but reuse an
|
|
/// existing window when opening files that are already part of an open
|
|
/// project.
|
|
NewWindow,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub enum CliRequest {
|
|
Open {
|
|
paths: Vec<String>,
|
|
urls: Vec<String>,
|
|
diff_paths: Vec<[String; 2]>,
|
|
diff_all: bool,
|
|
wsl: Option<String>,
|
|
wait: bool,
|
|
#[serde(default)]
|
|
open_behavior: OpenBehavior,
|
|
env: Option<HashMap<String, String>>,
|
|
user_data_dir: Option<String>,
|
|
dev_container: bool,
|
|
},
|
|
SetOpenBehavior {
|
|
behavior: CliBehaviorSetting,
|
|
},
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub enum CliResponse {
|
|
Ping,
|
|
Stdout { message: String },
|
|
Stderr { message: String },
|
|
Exit { status: i32 },
|
|
PromptOpenBehavior,
|
|
}
|
|
|
|
/// When Zed started not as an *.app but as a binary (e.g. local development),
|
|
/// there's a possibility to tell it to behave "regularly".
|
|
///
|
|
/// Note that in the main zed binary, this variable is unset after it's read for the first time,
|
|
/// therefore it should always be accessed through the `FORCE_CLI_MODE` static.
|
|
pub const FORCE_CLI_MODE_ENV_VAR_NAME: &str = "ZED_FORCE_CLI_MODE";
|
|
|
|
/// Abstracts the transport for sending CLI responses (Zed → CLI).
|
|
///
|
|
/// Production code uses `IpcSender<CliResponse>`. Tests can provide in-memory
|
|
/// implementations to avoid OS-level IPC.
|
|
pub trait CliResponseSink: Send + 'static {
|
|
fn send(&self, response: CliResponse) -> Result<()>;
|
|
}
|
|
|
|
impl CliResponseSink for ipc::IpcSender<CliResponse> {
|
|
fn send(&self, response: CliResponse) -> Result<()> {
|
|
ipc::IpcSender::send(self, response).map_err(|error| anyhow::anyhow!("{error}"))
|
|
}
|
|
}
|