Add initial support for WSL (#37035)

Closes #36188

## Todo

* [x] CLI
* [x] terminals
* [x] tasks

## For future PRs
* debugging
* UI for opening WSL projects
* fixing workspace state restoration

Release Notes:

- Windows alpha: Zed now supports editing folders in WSL.

---------

Co-authored-by: Junkui Zhang <364772080@qq.com>
This commit is contained in:
Max Brunsfeld
2025-08-29 17:18:52 -07:00
committed by GitHub
co-authored by Junkui Zhang
parent 1c2e2a00fe
commit f78f3e7729
27 changed files with 1701 additions and 786 deletions
+23 -36
View File
@@ -67,14 +67,14 @@ pub use pane_group::*;
use persistence::{DB, SerializedWindowBounds, model::SerializedWorkspace};
pub use persistence::{
DB as WORKSPACE_DB, WorkspaceDb, delete_unloaded_items,
model::{ItemId, SerializedSshConnection, SerializedWorkspaceLocation},
model::{ItemId, SerializedWorkspaceLocation},
};
use postage::stream::Stream;
use project::{
DirectoryLister, Project, ProjectEntryId, ProjectPath, ResolvedPath, Worktree, WorktreeId,
debugger::{breakpoint_store::BreakpointStoreEvent, session::ThreadStatus},
};
use remote::{RemoteClientDelegate, SshConnectionOptions, remote_client::ConnectionIdentifier};
use remote::{RemoteClientDelegate, RemoteConnectionOptions, remote_client::ConnectionIdentifier};
use schemars::JsonSchema;
use serde::Deserialize;
use session::AppSession;
@@ -5262,14 +5262,7 @@ impl Workspace {
fn serialize_workspace_location(&self, cx: &App) -> WorkspaceLocation {
let paths = PathList::new(&self.root_paths(cx));
if let Some(connection) = self.project.read(cx).remote_connection_options(cx) {
WorkspaceLocation::Location(
SerializedWorkspaceLocation::Ssh(SerializedSshConnection {
host: connection.host,
port: connection.port,
user: connection.username,
}),
paths,
)
WorkspaceLocation::Location(SerializedWorkspaceLocation::Remote(connection), paths)
} else if self.project.read(cx).is_local() {
if !paths.is_empty() {
WorkspaceLocation::Location(SerializedWorkspaceLocation::Local, paths)
@@ -7282,9 +7275,9 @@ pub fn create_and_open_local_file(
})
}
pub fn open_ssh_project_with_new_connection(
pub fn open_remote_project_with_new_connection(
window: WindowHandle<Workspace>,
connection_options: SshConnectionOptions,
connection_options: RemoteConnectionOptions,
cancel_rx: oneshot::Receiver<()>,
delegate: Arc<dyn RemoteClientDelegate>,
app_state: Arc<AppState>,
@@ -7293,11 +7286,11 @@ pub fn open_ssh_project_with_new_connection(
) -> Task<Result<()>> {
cx.spawn(async move |cx| {
let (workspace_id, serialized_workspace) =
serialize_ssh_project(connection_options.clone(), paths.clone(), cx).await?;
serialize_remote_project(connection_options.clone(), paths.clone(), cx).await?;
let session = match cx
.update(|cx| {
remote::RemoteClient::ssh(
remote::RemoteClient::new(
ConnectionIdentifier::Workspace(workspace_id.0),
connection_options,
cancel_rx,
@@ -7323,7 +7316,7 @@ pub fn open_ssh_project_with_new_connection(
)
})?;
open_ssh_project_inner(
open_remote_project_inner(
project,
paths,
workspace_id,
@@ -7336,8 +7329,8 @@ pub fn open_ssh_project_with_new_connection(
})
}
pub fn open_ssh_project_with_existing_connection(
connection_options: SshConnectionOptions,
pub fn open_remote_project_with_existing_connection(
connection_options: RemoteConnectionOptions,
project: Entity<Project>,
paths: Vec<PathBuf>,
app_state: Arc<AppState>,
@@ -7346,9 +7339,9 @@ pub fn open_ssh_project_with_existing_connection(
) -> Task<Result<()>> {
cx.spawn(async move |cx| {
let (workspace_id, serialized_workspace) =
serialize_ssh_project(connection_options.clone(), paths.clone(), cx).await?;
serialize_remote_project(connection_options.clone(), paths.clone(), cx).await?;
open_ssh_project_inner(
open_remote_project_inner(
project,
paths,
workspace_id,
@@ -7361,7 +7354,7 @@ pub fn open_ssh_project_with_existing_connection(
})
}
async fn open_ssh_project_inner(
async fn open_remote_project_inner(
project: Entity<Project>,
paths: Vec<PathBuf>,
workspace_id: WorkspaceId,
@@ -7448,22 +7441,18 @@ async fn open_ssh_project_inner(
Ok(())
}
fn serialize_ssh_project(
connection_options: SshConnectionOptions,
fn serialize_remote_project(
connection_options: RemoteConnectionOptions,
paths: Vec<PathBuf>,
cx: &AsyncApp,
) -> Task<Result<(WorkspaceId, Option<SerializedWorkspace>)>> {
cx.background_spawn(async move {
let ssh_connection_id = persistence::DB
.get_or_create_ssh_connection(
connection_options.host.clone(),
connection_options.port,
connection_options.username.clone(),
)
let remote_connection_id = persistence::DB
.get_or_create_remote_connection(connection_options)
.await?;
let serialized_workspace =
persistence::DB.ssh_workspace_for_roots(&paths, ssh_connection_id);
persistence::DB.remote_workspace_for_roots(&paths, remote_connection_id);
let workspace_id = if let Some(workspace_id) =
serialized_workspace.as_ref().map(|workspace| workspace.id)
@@ -8013,22 +8002,20 @@ pub struct WorkspacePosition {
pub centered_layout: bool,
}
pub fn ssh_workspace_position_from_db(
host: String,
port: Option<u16>,
user: Option<String>,
pub fn remote_workspace_position_from_db(
connection_options: RemoteConnectionOptions,
paths_to_open: &[PathBuf],
cx: &App,
) -> Task<Result<WorkspacePosition>> {
let paths = paths_to_open.to_vec();
cx.background_spawn(async move {
let ssh_connection_id = persistence::DB
.get_or_create_ssh_connection(host, port, user)
let remote_connection_id = persistence::DB
.get_or_create_remote_connection(connection_options)
.await
.context("fetching serialized ssh project")?;
let serialized_workspace =
persistence::DB.ssh_workspace_for_roots(&paths, ssh_connection_id);
persistence::DB.remote_workspace_for_roots(&paths, remote_connection_id);
let (window_bounds, display) = if let Some(bounds) = window_bounds_env_override() {
(Some(WindowBounds::Windowed(bounds)), None)