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
+310 -128
View File
@@ -20,6 +20,7 @@ use project::debugger::breakpoint_store::{BreakpointState, SourceBreakpoint};
use language::{LanguageName, Toolchain};
use project::WorktreeId;
use remote::{RemoteConnectionOptions, SshConnectionOptions, WslConnectionOptions};
use sqlez::{
bindable::{Bind, Column, StaticColumnCount},
statement::{SqlType, Statement},
@@ -33,11 +34,12 @@ use uuid::Uuid;
use crate::{
WorkspaceId,
path_list::{PathList, SerializedPathList},
persistence::model::RemoteConnectionKind,
};
use model::{
GroupId, ItemId, PaneId, SerializedItem, SerializedPane, SerializedPaneGroup,
SerializedSshConnection, SerializedWorkspace, SshConnectionId,
GroupId, ItemId, PaneId, RemoteConnectionId, SerializedItem, SerializedPane,
SerializedPaneGroup, SerializedWorkspace,
};
use self::model::{DockStructure, SerializedWorkspaceLocation};
@@ -627,6 +629,88 @@ impl Domain for WorkspaceDb {
END
WHERE paths IS NOT NULL
),
sql!(
CREATE TABLE remote_connections(
id INTEGER PRIMARY KEY,
kind TEXT NOT NULL,
host TEXT,
port INTEGER,
user TEXT,
distro TEXT
);
CREATE TABLE workspaces_2(
workspace_id INTEGER PRIMARY KEY,
paths TEXT,
paths_order TEXT,
remote_connection_id INTEGER REFERENCES remote_connections(id),
timestamp TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL,
window_state TEXT,
window_x REAL,
window_y REAL,
window_width REAL,
window_height REAL,
display BLOB,
left_dock_visible INTEGER,
left_dock_active_panel TEXT,
right_dock_visible INTEGER,
right_dock_active_panel TEXT,
bottom_dock_visible INTEGER,
bottom_dock_active_panel TEXT,
left_dock_zoom INTEGER,
right_dock_zoom INTEGER,
bottom_dock_zoom INTEGER,
fullscreen INTEGER,
centered_layout INTEGER,
session_id TEXT,
window_id INTEGER
) STRICT;
INSERT INTO remote_connections
SELECT
id,
"ssh" as kind,
host,
port,
user,
NULL as distro
FROM ssh_connections;
INSERT
INTO workspaces_2
SELECT
workspace_id,
paths,
paths_order,
ssh_connection_id as remote_connection_id,
timestamp,
window_state,
window_x,
window_y,
window_width,
window_height,
display,
left_dock_visible,
left_dock_active_panel,
right_dock_visible,
right_dock_active_panel,
bottom_dock_visible,
bottom_dock_active_panel,
left_dock_zoom,
right_dock_zoom,
bottom_dock_zoom,
fullscreen,
centered_layout,
session_id,
window_id
FROM
workspaces;
DROP TABLE workspaces;
ALTER TABLE workspaces_2 RENAME TO workspaces;
CREATE UNIQUE INDEX ix_workspaces_location ON workspaces(remote_connection_id, paths);
),
];
// Allow recovering from bad migration that was initially shipped to nightly
@@ -650,10 +734,10 @@ impl WorkspaceDb {
self.workspace_for_roots_internal(worktree_roots, None)
}
pub(crate) fn ssh_workspace_for_roots<P: AsRef<Path>>(
pub(crate) fn remote_workspace_for_roots<P: AsRef<Path>>(
&self,
worktree_roots: &[P],
ssh_project_id: SshConnectionId,
ssh_project_id: RemoteConnectionId,
) -> Option<SerializedWorkspace> {
self.workspace_for_roots_internal(worktree_roots, Some(ssh_project_id))
}
@@ -661,7 +745,7 @@ impl WorkspaceDb {
pub(crate) fn workspace_for_roots_internal<P: AsRef<Path>>(
&self,
worktree_roots: &[P],
ssh_connection_id: Option<SshConnectionId>,
remote_connection_id: Option<RemoteConnectionId>,
) -> Option<SerializedWorkspace> {
// paths are sorted before db interactions to ensure that the order of the paths
// doesn't affect the workspace selection for existing workspaces
@@ -713,13 +797,13 @@ impl WorkspaceDb {
FROM workspaces
WHERE
paths IS ? AND
ssh_connection_id IS ?
remote_connection_id IS ?
LIMIT 1
})
.map(|mut prepared_statement| {
(prepared_statement)((
root_paths.serialize().paths,
ssh_connection_id.map(|id| id.0 as i32),
remote_connection_id.map(|id| id.0 as i32),
))
.unwrap()
})
@@ -803,14 +887,12 @@ impl WorkspaceDb {
log::debug!("Saving workspace at location: {:?}", workspace.location);
self.write(move |conn| {
conn.with_savepoint("update_worktrees", || {
let ssh_connection_id = match &workspace.location {
let remote_connection_id = match workspace.location.clone() {
SerializedWorkspaceLocation::Local => None,
SerializedWorkspaceLocation::Ssh(connection) => {
Some(Self::get_or_create_ssh_connection_query(
SerializedWorkspaceLocation::Remote(connection_options) => {
Some(Self::get_or_create_remote_connection_internal(
conn,
connection.host.clone(),
connection.port,
connection.user.clone(),
connection_options
)?.0)
}
};
@@ -860,11 +942,11 @@ impl WorkspaceDb {
WHERE
workspace_id != ?1 AND
paths IS ?2 AND
ssh_connection_id IS ?3
remote_connection_id IS ?3
))?((
workspace.id,
paths.paths.clone(),
ssh_connection_id,
remote_connection_id,
))
.context("clearing out old locations")?;
@@ -874,7 +956,7 @@ impl WorkspaceDb {
workspace_id,
paths,
paths_order,
ssh_connection_id,
remote_connection_id,
left_dock_visible,
left_dock_active_panel,
left_dock_zoom,
@@ -893,7 +975,7 @@ impl WorkspaceDb {
UPDATE SET
paths = ?2,
paths_order = ?3,
ssh_connection_id = ?4,
remote_connection_id = ?4,
left_dock_visible = ?5,
left_dock_active_panel = ?6,
left_dock_zoom = ?7,
@@ -912,7 +994,7 @@ impl WorkspaceDb {
workspace.id,
paths.paths.clone(),
paths.order.clone(),
ssh_connection_id,
remote_connection_id,
workspace.docks,
workspace.session_id,
workspace.window_id,
@@ -931,39 +1013,78 @@ impl WorkspaceDb {
.await;
}
pub(crate) async fn get_or_create_ssh_connection(
pub(crate) async fn get_or_create_remote_connection(
&self,
host: String,
port: Option<u16>,
user: Option<String>,
) -> Result<SshConnectionId> {
self.write(move |conn| Self::get_or_create_ssh_connection_query(conn, host, port, user))
options: RemoteConnectionOptions,
) -> Result<RemoteConnectionId> {
self.write(move |conn| Self::get_or_create_remote_connection_internal(conn, options))
.await
}
fn get_or_create_ssh_connection_query(
fn get_or_create_remote_connection_internal(
this: &Connection,
host: String,
options: RemoteConnectionOptions,
) -> Result<RemoteConnectionId> {
let kind;
let user;
let mut host = None;
let mut port = None;
let mut distro = None;
match options {
RemoteConnectionOptions::Ssh(options) => {
kind = RemoteConnectionKind::Ssh;
host = Some(options.host);
port = options.port;
user = options.username;
}
RemoteConnectionOptions::Wsl(options) => {
kind = RemoteConnectionKind::Wsl;
distro = Some(options.distro_name);
user = options.user;
}
}
Self::get_or_create_remote_connection_query(this, kind, host, port, user, distro)
}
fn get_or_create_remote_connection_query(
this: &Connection,
kind: RemoteConnectionKind,
host: Option<String>,
port: Option<u16>,
user: Option<String>,
) -> Result<SshConnectionId> {
distro: Option<String>,
) -> Result<RemoteConnectionId> {
if let Some(id) = this.select_row_bound(sql!(
SELECT id FROM ssh_connections WHERE host IS ? AND port IS ? AND user IS ? LIMIT 1
))?((host.clone(), port, user.clone()))?
{
Ok(SshConnectionId(id))
SELECT id
FROM remote_connections
WHERE
kind IS ? AND
host IS ? AND
port IS ? AND
user IS ? AND
distro IS ?
LIMIT 1
))?((
kind.serialize(),
host.clone(),
port,
user.clone(),
distro.clone(),
))? {
Ok(RemoteConnectionId(id))
} else {
log::debug!("Inserting SSH project at host {host}");
let id = this.select_row_bound(sql!(
INSERT INTO ssh_connections (
INSERT INTO remote_connections (
kind,
host,
port,
user
) VALUES (?1, ?2, ?3)
user,
distro
) VALUES (?1, ?2, ?3, ?4, ?5)
RETURNING id
))?((host, port, user))?
.context("failed to insert ssh project")?;
Ok(SshConnectionId(id))
))?((kind.serialize(), host, port, user, distro))?
.context("failed to insert remote project")?;
Ok(RemoteConnectionId(id))
}
}
@@ -973,15 +1094,17 @@ impl WorkspaceDb {
}
}
fn recent_workspaces(&self) -> Result<Vec<(WorkspaceId, PathList, Option<u64>)>> {
fn recent_workspaces(
&self,
) -> Result<Vec<(WorkspaceId, PathList, Option<RemoteConnectionId>)>> {
Ok(self
.recent_workspaces_query()?
.into_iter()
.map(|(id, paths, order, ssh_connection_id)| {
.map(|(id, paths, order, remote_connection_id)| {
(
id,
PathList::deserialize(&SerializedPathList { paths, order }),
ssh_connection_id,
remote_connection_id.map(RemoteConnectionId),
)
})
.collect())
@@ -1001,7 +1124,7 @@ impl WorkspaceDb {
fn session_workspaces(
&self,
session_id: String,
) -> Result<Vec<(PathList, Option<u64>, Option<SshConnectionId>)>> {
) -> Result<Vec<(PathList, Option<u64>, Option<RemoteConnectionId>)>> {
Ok(self
.session_workspaces_query(session_id)?
.into_iter()
@@ -1009,7 +1132,7 @@ impl WorkspaceDb {
(
PathList::deserialize(&SerializedPathList { paths, order }),
window_id,
ssh_connection_id.map(SshConnectionId),
ssh_connection_id.map(RemoteConnectionId),
)
})
.collect())
@@ -1017,7 +1140,7 @@ impl WorkspaceDb {
query! {
fn session_workspaces_query(session_id: String) -> Result<Vec<(String, String, Option<u64>, Option<u64>)>> {
SELECT paths, paths_order, window_id, ssh_connection_id
SELECT paths, paths_order, window_id, remote_connection_id
FROM workspaces
WHERE session_id = ?1
ORDER BY timestamp DESC
@@ -1039,40 +1162,55 @@ impl WorkspaceDb {
}
}
fn ssh_connections(&self) -> Result<HashMap<SshConnectionId, SerializedSshConnection>> {
Ok(self
.ssh_connections_query()?
.into_iter()
.map(|(id, host, port, user)| {
(
SshConnectionId(id),
SerializedSshConnection { host, port, user },
)
})
.collect())
}
query! {
pub fn ssh_connections_query() -> Result<Vec<(u64, String, Option<u16>, Option<String>)>> {
SELECT id, host, port, user
FROM ssh_connections
}
}
pub(crate) fn ssh_connection(&self, id: SshConnectionId) -> Result<SerializedSshConnection> {
let row = self.ssh_connection_query(id.0)?;
Ok(SerializedSshConnection {
host: row.0,
port: row.1,
user: row.2,
fn remote_connections(&self) -> Result<HashMap<RemoteConnectionId, RemoteConnectionOptions>> {
Ok(self.select(sql!(
SELECT
id, kind, host, port, user, distro
FROM
remote_connections
))?()?
.into_iter()
.filter_map(|(id, kind, host, port, user, distro)| {
Some((
RemoteConnectionId(id),
Self::remote_connection_from_row(kind, host, port, user, distro)?,
))
})
.collect())
}
query! {
fn ssh_connection_query(id: u64) -> Result<(String, Option<u16>, Option<String>)> {
SELECT host, port, user
FROM ssh_connections
pub(crate) fn remote_connection(
&self,
id: RemoteConnectionId,
) -> Result<RemoteConnectionOptions> {
let (kind, host, port, user, distro) = self.select_row_bound(sql!(
SELECT kind, host, port, user, distro
FROM remote_connections
WHERE id = ?
))?(id.0)?
.context("no such remote connection")?;
Self::remote_connection_from_row(kind, host, port, user, distro)
.context("invalid remote_connection row")
}
fn remote_connection_from_row(
kind: String,
host: Option<String>,
port: Option<u16>,
user: Option<String>,
distro: Option<String>,
) -> Option<RemoteConnectionOptions> {
match RemoteConnectionKind::deserialize(&kind)? {
RemoteConnectionKind::Wsl => Some(RemoteConnectionOptions::Wsl(WslConnectionOptions {
distro_name: distro?,
user: user,
})),
RemoteConnectionKind::Ssh => Some(RemoteConnectionOptions::Ssh(SshConnectionOptions {
host: host?,
port,
username: user,
..Default::default()
})),
}
}
@@ -1108,14 +1246,14 @@ impl WorkspaceDb {
) -> Result<Vec<(WorkspaceId, SerializedWorkspaceLocation, PathList)>> {
let mut result = Vec::new();
let mut delete_tasks = Vec::new();
let ssh_connections = self.ssh_connections()?;
let remote_connections = self.remote_connections()?;
for (id, paths, ssh_connection_id) in self.recent_workspaces()? {
if let Some(ssh_connection_id) = ssh_connection_id.map(SshConnectionId) {
if let Some(ssh_connection) = ssh_connections.get(&ssh_connection_id) {
for (id, paths, remote_connection_id) in self.recent_workspaces()? {
if let Some(remote_connection_id) = remote_connection_id {
if let Some(connection_options) = remote_connections.get(&remote_connection_id) {
result.push((
id,
SerializedWorkspaceLocation::Ssh(ssh_connection.clone()),
SerializedWorkspaceLocation::Remote(connection_options.clone()),
paths,
));
} else {
@@ -1157,12 +1295,14 @@ impl WorkspaceDb {
) -> Result<Vec<(SerializedWorkspaceLocation, PathList)>> {
let mut workspaces = Vec::new();
for (paths, window_id, ssh_connection_id) in
for (paths, window_id, remote_connection_id) in
self.session_workspaces(last_session_id.to_owned())?
{
if let Some(ssh_connection_id) = ssh_connection_id {
if let Some(remote_connection_id) = remote_connection_id {
workspaces.push((
SerializedWorkspaceLocation::Ssh(self.ssh_connection(ssh_connection_id)?),
SerializedWorkspaceLocation::Remote(
self.remote_connection(remote_connection_id)?,
),
paths,
window_id.map(WindowId::from),
));
@@ -1545,6 +1685,7 @@ mod tests {
};
use gpui;
use pretty_assertions::assert_eq;
use remote::SshConnectionOptions;
use std::{thread, time::Duration};
#[gpui::test]
@@ -2196,14 +2337,20 @@ mod tests {
};
let connection_id = db
.get_or_create_ssh_connection("my-host".to_string(), Some(1234), None)
.get_or_create_remote_connection(RemoteConnectionOptions::Ssh(SshConnectionOptions {
host: "my-host".to_string(),
port: Some(1234),
..Default::default()
}))
.await
.unwrap();
let workspace_5 = SerializedWorkspace {
id: WorkspaceId(5),
paths: PathList::default(),
location: SerializedWorkspaceLocation::Ssh(db.ssh_connection(connection_id).unwrap()),
location: SerializedWorkspaceLocation::Remote(
db.remote_connection(connection_id).unwrap(),
),
center_group: Default::default(),
window_bounds: Default::default(),
display: Default::default(),
@@ -2362,13 +2509,12 @@ mod tests {
}
#[gpui::test]
async fn test_last_session_workspace_locations_ssh_projects() {
let db = WorkspaceDb::open_test_db(
"test_serializing_workspaces_last_session_workspaces_ssh_projects",
)
.await;
async fn test_last_session_workspace_locations_remote() {
let db =
WorkspaceDb::open_test_db("test_serializing_workspaces_last_session_workspaces_remote")
.await;
let ssh_connections = [
let remote_connections = [
("host-1", "my-user-1"),
("host-2", "my-user-2"),
("host-3", "my-user-3"),
@@ -2376,30 +2522,31 @@ mod tests {
]
.into_iter()
.map(|(host, user)| async {
db.get_or_create_ssh_connection(host.to_string(), None, Some(user.to_string()))
let options = RemoteConnectionOptions::Ssh(SshConnectionOptions {
host: host.to_string(),
username: Some(user.to_string()),
..Default::default()
});
db.get_or_create_remote_connection(options.clone())
.await
.unwrap();
SerializedSshConnection {
host: host.into(),
port: None,
user: Some(user.into()),
}
options
})
.collect::<Vec<_>>();
let ssh_connections = futures::future::join_all(ssh_connections).await;
let remote_connections = futures::future::join_all(remote_connections).await;
let workspaces = [
(1, ssh_connections[0].clone(), 9),
(2, ssh_connections[1].clone(), 5),
(3, ssh_connections[2].clone(), 8),
(4, ssh_connections[3].clone(), 2),
(1, remote_connections[0].clone(), 9),
(2, remote_connections[1].clone(), 5),
(3, remote_connections[2].clone(), 8),
(4, remote_connections[3].clone(), 2),
]
.into_iter()
.map(|(id, ssh_connection, window_id)| SerializedWorkspace {
.map(|(id, remote_connection, window_id)| SerializedWorkspace {
id: WorkspaceId(id),
paths: PathList::default(),
location: SerializedWorkspaceLocation::Ssh(ssh_connection),
location: SerializedWorkspaceLocation::Remote(remote_connection),
center_group: Default::default(),
window_bounds: Default::default(),
display: Default::default(),
@@ -2429,28 +2576,28 @@ mod tests {
assert_eq!(
have[0],
(
SerializedWorkspaceLocation::Ssh(ssh_connections[3].clone()),
SerializedWorkspaceLocation::Remote(remote_connections[3].clone()),
PathList::default()
)
);
assert_eq!(
have[1],
(
SerializedWorkspaceLocation::Ssh(ssh_connections[2].clone()),
SerializedWorkspaceLocation::Remote(remote_connections[2].clone()),
PathList::default()
)
);
assert_eq!(
have[2],
(
SerializedWorkspaceLocation::Ssh(ssh_connections[1].clone()),
SerializedWorkspaceLocation::Remote(remote_connections[1].clone()),
PathList::default()
)
);
assert_eq!(
have[3],
(
SerializedWorkspaceLocation::Ssh(ssh_connections[0].clone()),
SerializedWorkspaceLocation::Remote(remote_connections[0].clone()),
PathList::default()
)
);
@@ -2465,13 +2612,23 @@ mod tests {
let user = Some("user".to_string());
let connection_id = db
.get_or_create_ssh_connection(host.clone(), port, user.clone())
.get_or_create_remote_connection(RemoteConnectionOptions::Ssh(SshConnectionOptions {
host: host.clone(),
port,
username: user.clone(),
..Default::default()
}))
.await
.unwrap();
// Test that calling the function again with the same parameters returns the same project
let same_connection = db
.get_or_create_ssh_connection(host.clone(), port, user.clone())
.get_or_create_remote_connection(RemoteConnectionOptions::Ssh(SshConnectionOptions {
host: host.clone(),
port,
username: user.clone(),
..Default::default()
}))
.await
.unwrap();
@@ -2483,7 +2640,12 @@ mod tests {
let user2 = Some("otheruser".to_string());
let different_connection = db
.get_or_create_ssh_connection(host2.clone(), port2, user2.clone())
.get_or_create_remote_connection(RemoteConnectionOptions::Ssh(SshConnectionOptions {
host: host2.clone(),
port: port2,
username: user2.clone(),
..Default::default()
}))
.await
.unwrap();
@@ -2497,12 +2659,22 @@ mod tests {
let (host, port, user) = ("example.com".to_string(), None, None);
let connection_id = db
.get_or_create_ssh_connection(host.clone(), port, None)
.get_or_create_remote_connection(RemoteConnectionOptions::Ssh(SshConnectionOptions {
host: host.clone(),
port,
username: None,
..Default::default()
}))
.await
.unwrap();
let same_connection_id = db
.get_or_create_ssh_connection(host.clone(), port, user.clone())
.get_or_create_remote_connection(RemoteConnectionOptions::Ssh(SshConnectionOptions {
host: host.clone(),
port,
username: user.clone(),
..Default::default()
}))
.await
.unwrap();
@@ -2510,8 +2682,8 @@ mod tests {
}
#[gpui::test]
async fn test_get_ssh_connections() {
let db = WorkspaceDb::open_test_db("test_get_ssh_connections").await;
async fn test_get_remote_connections() {
let db = WorkspaceDb::open_test_db("test_get_remote_connections").await;
let connections = [
("example.com".to_string(), None, None),
@@ -2526,39 +2698,49 @@ mod tests {
let mut ids = Vec::new();
for (host, port, user) in connections.iter() {
ids.push(
db.get_or_create_ssh_connection(host.clone(), *port, user.clone())
.await
.unwrap(),
db.get_or_create_remote_connection(RemoteConnectionOptions::Ssh(
SshConnectionOptions {
host: host.clone(),
port: *port,
username: user.clone(),
..Default::default()
},
))
.await
.unwrap(),
);
}
let stored_projects = db.ssh_connections().unwrap();
let stored_connections = db.remote_connections().unwrap();
assert_eq!(
stored_projects,
stored_connections,
[
(
ids[0],
SerializedSshConnection {
RemoteConnectionOptions::Ssh(SshConnectionOptions {
host: "example.com".into(),
port: None,
user: None,
}
username: None,
..Default::default()
}),
),
(
ids[1],
SerializedSshConnection {
RemoteConnectionOptions::Ssh(SshConnectionOptions {
host: "anotherexample.com".into(),
port: Some(123),
user: Some("user2".into()),
}
username: Some("user2".into()),
..Default::default()
}),
),
(
ids[2],
SerializedSshConnection {
RemoteConnectionOptions::Ssh(SshConnectionOptions {
host: "yetanother.com".into(),
port: Some(345),
user: None,
}
username: None,
..Default::default()
}),
),
]
.into_iter()
+24 -8
View File
@@ -12,7 +12,7 @@ use db::sqlez::{
use gpui::{AsyncWindowContext, Entity, WeakEntity};
use project::{Project, debugger::breakpoint_store::SourceBreakpoint};
use serde::{Deserialize, Serialize};
use remote::RemoteConnectionOptions;
use std::{
collections::BTreeMap,
path::{Path, PathBuf},
@@ -24,19 +24,18 @@ use uuid::Uuid;
#[derive(
Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, serde::Serialize, serde::Deserialize,
)]
pub(crate) struct SshConnectionId(pub u64);
pub(crate) struct RemoteConnectionId(pub u64);
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct SerializedSshConnection {
pub host: String,
pub port: Option<u16>,
pub user: Option<String>,
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub(crate) enum RemoteConnectionKind {
Ssh,
Wsl,
}
#[derive(Debug, PartialEq, Clone)]
pub enum SerializedWorkspaceLocation {
Local,
Ssh(SerializedSshConnection),
Remote(RemoteConnectionOptions),
}
impl SerializedWorkspaceLocation {
@@ -68,6 +67,23 @@ pub struct DockStructure {
pub(crate) bottom: DockData,
}
impl RemoteConnectionKind {
pub(crate) fn serialize(&self) -> &'static str {
match self {
RemoteConnectionKind::Ssh => "ssh",
RemoteConnectionKind::Wsl => "wsl",
}
}
pub(crate) fn deserialize(text: &str) -> Option<Self> {
match text {
"ssh" => Some(Self::Ssh),
"wsl" => Some(Self::Wsl),
_ => None,
}
}
}
impl Column for DockStructure {
fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
let (left, next_index) = DockData::column(statement, start_index)?;
+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)