Detailed changes
@@ -19829,7 +19829,6 @@ dependencies = [
"any_vec",
"anyhow",
"async-recursion",
- "bincode",
"call",
"client",
"clock",
@@ -19848,6 +19847,7 @@ dependencies = [
"node_runtime",
"parking_lot",
"postage",
+ "pretty_assertions",
"project",
"remote",
"schemars",
@@ -46,11 +46,6 @@ impl ProjectId {
}
}
-#[derive(
- Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, serde::Serialize, serde::Deserialize,
-)]
-pub struct DevServerProjectId(pub u64);
-
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ParticipantIndex(pub u32);
@@ -1,5 +1,3 @@
-use std::path::PathBuf;
-
use gpui::{ClickEvent, DismissEvent, EventEmitter, FocusHandle, Focusable, Render, WeakEntity};
use project::project_settings::ProjectSettings;
use remote::SshConnectionOptions;
@@ -103,17 +101,17 @@ impl DisconnectedOverlay {
return;
};
- let Some(ssh_project) = workspace.read(cx).serialized_ssh_project() else {
- return;
- };
-
let Some(window_handle) = window.window_handle().downcast::<Workspace>() else {
return;
};
let app_state = workspace.read(cx).app_state().clone();
-
- let paths = ssh_project.paths.iter().map(PathBuf::from).collect();
+ let paths = workspace
+ .read(cx)
+ .root_paths(cx)
+ .iter()
+ .map(|path| path.to_path_buf())
+ .collect();
cx.spawn_in(window, async move |_, cx| {
open_ssh_project(
@@ -19,15 +19,12 @@ use picker::{
pub use remote_servers::RemoteServerProjects;
use settings::Settings;
pub use ssh_connections::SshSettings;
-use std::{
- path::{Path, PathBuf},
- sync::Arc,
-};
+use std::{path::Path, sync::Arc};
use ui::{KeyBinding, ListItem, ListItemSpacing, Tooltip, prelude::*, tooltip_container};
use util::{ResultExt, paths::PathExt};
use workspace::{
- CloseIntent, HistoryManager, ModalView, OpenOptions, SerializedWorkspaceLocation, WORKSPACE_DB,
- Workspace, WorkspaceId, with_active_or_new_workspace,
+ CloseIntent, HistoryManager, ModalView, OpenOptions, PathList, SerializedWorkspaceLocation,
+ WORKSPACE_DB, Workspace, WorkspaceId, with_active_or_new_workspace,
};
use zed_actions::{OpenRecent, OpenRemote};
@@ -154,7 +151,7 @@ impl Render for RecentProjects {
pub struct RecentProjectsDelegate {
workspace: WeakEntity<Workspace>,
- workspaces: Vec<(WorkspaceId, SerializedWorkspaceLocation)>,
+ workspaces: Vec<(WorkspaceId, SerializedWorkspaceLocation, PathList)>,
selected_match_index: usize,
matches: Vec<StringMatch>,
render_paths: bool,
@@ -178,12 +175,15 @@ impl RecentProjectsDelegate {
}
}
- pub fn set_workspaces(&mut self, workspaces: Vec<(WorkspaceId, SerializedWorkspaceLocation)>) {
+ pub fn set_workspaces(
+ &mut self,
+ workspaces: Vec<(WorkspaceId, SerializedWorkspaceLocation, PathList)>,
+ ) {
self.workspaces = workspaces;
self.has_any_non_local_projects = !self
.workspaces
.iter()
- .all(|(_, location)| matches!(location, SerializedWorkspaceLocation::Local(_, _)));
+ .all(|(_, location, _)| matches!(location, SerializedWorkspaceLocation::Local));
}
}
impl EventEmitter<DismissEvent> for RecentProjectsDelegate {}
@@ -236,15 +236,14 @@ impl PickerDelegate for RecentProjectsDelegate {
.workspaces
.iter()
.enumerate()
- .filter(|(_, (id, _))| !self.is_current_workspace(*id, cx))
- .map(|(id, (_, location))| {
- let combined_string = location
- .sorted_paths()
+ .filter(|(_, (id, _, _))| !self.is_current_workspace(*id, cx))
+ .map(|(id, (_, _, paths))| {
+ let combined_string = paths
+ .paths()
.iter()
.map(|path| path.compact().to_string_lossy().into_owned())
.collect::<Vec<_>>()
.join("");
-
StringMatchCandidate::new(id, &combined_string)
})
.collect::<Vec<_>>();
@@ -279,7 +278,7 @@ impl PickerDelegate for RecentProjectsDelegate {
.get(self.selected_index())
.zip(self.workspace.upgrade())
{
- let (candidate_workspace_id, candidate_workspace_location) =
+ let (candidate_workspace_id, candidate_workspace_location, candidate_workspace_paths) =
&self.workspaces[selected_match.candidate_id];
let replace_current_window = if self.create_new_window {
secondary
@@ -292,8 +291,8 @@ impl PickerDelegate for RecentProjectsDelegate {
Task::ready(Ok(()))
} else {
match candidate_workspace_location {
- SerializedWorkspaceLocation::Local(paths, _) => {
- let paths = paths.paths().to_vec();
+ SerializedWorkspaceLocation::Local => {
+ let paths = candidate_workspace_paths.paths().to_vec();
if replace_current_window {
cx.spawn_in(window, async move |workspace, cx| {
let continue_replacing = workspace
@@ -321,7 +320,7 @@ impl PickerDelegate for RecentProjectsDelegate {
workspace.open_workspace_for_paths(false, paths, window, cx)
}
}
- SerializedWorkspaceLocation::Ssh(ssh_project) => {
+ SerializedWorkspaceLocation::Ssh(connection) => {
let app_state = workspace.app_state().clone();
let replace_window = if replace_current_window {
@@ -337,12 +336,12 @@ impl PickerDelegate for RecentProjectsDelegate {
let connection_options = SshSettings::get_global(cx)
.connection_options_for(
- ssh_project.host.clone(),
- ssh_project.port,
- ssh_project.user.clone(),
+ connection.host.clone(),
+ connection.port,
+ connection.user.clone(),
);
- let paths = ssh_project.paths.iter().map(PathBuf::from).collect();
+ let paths = candidate_workspace_paths.paths().to_vec();
cx.spawn_in(window, async move |_, cx| {
open_ssh_project(
@@ -383,12 +382,12 @@ impl PickerDelegate for RecentProjectsDelegate {
) -> Option<Self::ListItem> {
let hit = self.matches.get(ix)?;
- let (_, location) = self.workspaces.get(hit.candidate_id)?;
+ let (_, location, paths) = self.workspaces.get(hit.candidate_id)?;
let mut path_start_offset = 0;
- let (match_labels, paths): (Vec<_>, Vec<_>) = location
- .sorted_paths()
+ let (match_labels, paths): (Vec<_>, Vec<_>) = paths
+ .paths()
.iter()
.map(|p| p.compact())
.map(|path| {
@@ -416,11 +415,9 @@ impl PickerDelegate for RecentProjectsDelegate {
.gap_3()
.when(self.has_any_non_local_projects, |this| {
this.child(match location {
- SerializedWorkspaceLocation::Local(_, _) => {
- Icon::new(IconName::Screen)
- .color(Color::Muted)
- .into_any_element()
- }
+ SerializedWorkspaceLocation::Local => Icon::new(IconName::Screen)
+ .color(Color::Muted)
+ .into_any_element(),
SerializedWorkspaceLocation::Ssh(_) => Icon::new(IconName::Server)
.color(Color::Muted)
.into_any_element(),
@@ -568,7 +565,7 @@ impl RecentProjectsDelegate {
cx: &mut Context<Picker<Self>>,
) {
if let Some(selected_match) = self.matches.get(ix) {
- let (workspace_id, _) = self.workspaces[selected_match.candidate_id];
+ let (workspace_id, _, _) = self.workspaces[selected_match.candidate_id];
cx.spawn_in(window, async move |this, cx| {
let _ = WORKSPACE_DB.delete_workspace_by_id(workspace_id).await;
let workspaces = WORKSPACE_DB
@@ -707,7 +704,8 @@ mod tests {
}];
delegate.set_workspaces(vec![(
WorkspaceId::default(),
- SerializedWorkspaceLocation::from_local_paths(vec![path!("/test/path/")]),
+ SerializedWorkspaceLocation::Local,
+ PathList::new(&[path!("/test/path")]),
)]);
});
})
@@ -29,7 +29,6 @@ test-support = [
any_vec.workspace = true
anyhow.workspace = true
async-recursion.workspace = true
-bincode.workspace = true
call.workspace = true
client.workspace = true
clock.workspace = true
@@ -80,5 +79,6 @@ project = { workspace = true, features = ["test-support"] }
session = { workspace = true, features = ["test-support"] }
settings = { workspace = true, features = ["test-support"] }
http_client = { workspace = true, features = ["test-support"] }
+pretty_assertions.workspace = true
tempfile.workspace = true
zlog.workspace = true
@@ -5,7 +5,9 @@ use smallvec::SmallVec;
use ui::App;
use util::{ResultExt, paths::PathExt};
-use crate::{NewWindow, SerializedWorkspaceLocation, WORKSPACE_DB, WorkspaceId};
+use crate::{
+ NewWindow, SerializedWorkspaceLocation, WORKSPACE_DB, WorkspaceId, path_list::PathList,
+};
pub fn init(cx: &mut App) {
let manager = cx.new(|_| HistoryManager::new());
@@ -44,7 +46,13 @@ impl HistoryManager {
.unwrap_or_default()
.into_iter()
.rev()
- .map(|(id, location)| HistoryManagerEntry::new(id, &location))
+ .filter_map(|(id, location, paths)| {
+ if matches!(location, SerializedWorkspaceLocation::Local) {
+ Some(HistoryManagerEntry::new(id, &paths))
+ } else {
+ None
+ }
+ })
.collect::<Vec<_>>();
this.update(cx, |this, cx| {
this.history = recent_folders;
@@ -118,9 +126,9 @@ impl HistoryManager {
}
impl HistoryManagerEntry {
- pub fn new(id: WorkspaceId, location: &SerializedWorkspaceLocation) -> Self {
- let path = location
- .sorted_paths()
+ pub fn new(id: WorkspaceId, paths: &PathList) -> Self {
+ let path = paths
+ .paths()
.iter()
.map(|path| path.compact())
.collect::<SmallVec<[PathBuf; 2]>>();
@@ -0,0 +1,121 @@
+use std::{
+ path::{Path, PathBuf},
+ sync::Arc,
+};
+
+use util::paths::SanitizedPath;
+
+/// A list of absolute paths, in a specific order.
+///
+/// The paths are stored in lexicographic order, so that they can be compared to
+/// other path lists without regard to the order of the paths.
+#[derive(Default, PartialEq, Eq, Debug, Clone)]
+pub struct PathList {
+ paths: Arc<[PathBuf]>,
+ order: Arc<[usize]>,
+}
+
+#[derive(Debug)]
+pub struct SerializedPathList {
+ pub paths: String,
+ pub order: String,
+}
+
+impl PathList {
+ pub fn new<P: AsRef<Path>>(paths: &[P]) -> Self {
+ let mut indexed_paths: Vec<(usize, PathBuf)> = paths
+ .iter()
+ .enumerate()
+ .map(|(ix, path)| (ix, SanitizedPath::from(path).into()))
+ .collect();
+ indexed_paths.sort_by(|(_, a), (_, b)| a.cmp(b));
+ let order = indexed_paths.iter().map(|e| e.0).collect::<Vec<_>>().into();
+ let paths = indexed_paths
+ .into_iter()
+ .map(|e| e.1)
+ .collect::<Vec<_>>()
+ .into();
+ Self { order, paths }
+ }
+
+ pub fn is_empty(&self) -> bool {
+ self.paths.is_empty()
+ }
+
+ pub fn paths(&self) -> &[PathBuf] {
+ self.paths.as_ref()
+ }
+
+ pub fn order(&self) -> &[usize] {
+ self.order.as_ref()
+ }
+
+ pub fn is_lexicographically_ordered(&self) -> bool {
+ self.order.iter().enumerate().all(|(i, &j)| i == j)
+ }
+
+ pub fn deserialize(serialized: &SerializedPathList) -> Self {
+ let mut paths: Vec<PathBuf> = if serialized.paths.is_empty() {
+ Vec::new()
+ } else {
+ serde_json::from_str::<Vec<PathBuf>>(&serialized.paths)
+ .unwrap_or(Vec::new())
+ .into_iter()
+ .map(|s| SanitizedPath::from(s).into())
+ .collect()
+ };
+
+ let mut order: Vec<usize> = serialized
+ .order
+ .split(',')
+ .filter_map(|s| s.parse().ok())
+ .collect();
+
+ if !paths.is_sorted() || order.len() != paths.len() {
+ order = (0..paths.len()).collect();
+ paths.sort();
+ }
+
+ Self {
+ paths: paths.into(),
+ order: order.into(),
+ }
+ }
+
+ pub fn serialize(&self) -> SerializedPathList {
+ use std::fmt::Write as _;
+
+ let paths = serde_json::to_string(&self.paths).unwrap_or_default();
+
+ let mut order = String::new();
+ for ix in self.order.iter() {
+ if !order.is_empty() {
+ order.push(',');
+ }
+ write!(&mut order, "{}", *ix).unwrap();
+ }
+ SerializedPathList { paths, order }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_path_list() {
+ let list1 = PathList::new(&["a/d", "a/c"]);
+ let list2 = PathList::new(&["a/c", "a/d"]);
+
+ assert_eq!(list1.paths(), list2.paths());
+ assert_ne!(list1, list2);
+ assert_eq!(list1.order(), &[1, 0]);
+ assert_eq!(list2.order(), &[0, 1]);
+
+ let list1_deserialized = PathList::deserialize(&list1.serialize());
+ assert_eq!(list1_deserialized, list1);
+
+ let list2_deserialized = PathList::deserialize(&list2.serialize());
+ assert_eq!(list2_deserialized, list2);
+ }
+}
@@ -9,10 +9,8 @@ use std::{
};
use anyhow::{Context as _, Result, bail};
-use client::DevServerProjectId;
use db::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql};
use gpui::{Axis, Bounds, Task, WindowBounds, WindowId, point, size};
-use itertools::Itertools;
use project::debugger::breakpoint_store::{BreakpointState, SourceBreakpoint};
use language::{LanguageName, Toolchain};
@@ -28,14 +26,17 @@ use ui::{App, px};
use util::{ResultExt, maybe};
use uuid::Uuid;
-use crate::WorkspaceId;
+use crate::{
+ WorkspaceId,
+ path_list::{PathList, SerializedPathList},
+};
use model::{
- GroupId, ItemId, LocalPaths, PaneId, SerializedItem, SerializedPane, SerializedPaneGroup,
- SerializedSshProject, SerializedWorkspace,
+ GroupId, ItemId, PaneId, SerializedItem, SerializedPane, SerializedPaneGroup,
+ SerializedSshConnection, SerializedWorkspace,
};
-use self::model::{DockStructure, LocalPathsOrder, SerializedWorkspaceLocation};
+use self::model::{DockStructure, SerializedWorkspaceLocation};
#[derive(Copy, Clone, Debug, PartialEq)]
pub(crate) struct SerializedAxis(pub(crate) gpui::Axis);
@@ -275,70 +276,9 @@ impl sqlez::bindable::Bind for SerializedPixels {
}
define_connection! {
- // Current schema shape using pseudo-rust syntax:
- //
- // workspaces(
- // workspace_id: usize, // Primary key for workspaces
- // local_paths: Bincode<Vec<PathBuf>>,
- // local_paths_order: Bincode<Vec<usize>>,
- // dock_visible: bool, // Deprecated
- // dock_anchor: DockAnchor, // Deprecated
- // dock_pane: Option<usize>, // Deprecated
- // left_sidebar_open: boolean,
- // timestamp: String, // UTC YYYY-MM-DD HH:MM:SS
- // window_state: String, // WindowBounds Discriminant
- // window_x: Option<f32>, // WindowBounds::Fixed RectF x
- // window_y: Option<f32>, // WindowBounds::Fixed RectF y
- // window_width: Option<f32>, // WindowBounds::Fixed RectF width
- // window_height: Option<f32>, // WindowBounds::Fixed RectF height
- // display: Option<Uuid>, // Display id
- // fullscreen: Option<bool>, // Is the window fullscreen?
- // centered_layout: Option<bool>, // Is the Centered Layout mode activated?
- // session_id: Option<String>, // Session id
- // window_id: Option<u64>, // Window Id
- // )
- //
- // pane_groups(
- // group_id: usize, // Primary key for pane_groups
- // workspace_id: usize, // References workspaces table
- // parent_group_id: Option<usize>, // None indicates that this is the root node
- // position: Option<usize>, // None indicates that this is the root node
- // axis: Option<Axis>, // 'Vertical', 'Horizontal'
- // flexes: Option<Vec<f32>>, // A JSON array of floats
- // )
- //
- // panes(
- // pane_id: usize, // Primary key for panes
- // workspace_id: usize, // References workspaces table
- // active: bool,
- // )
- //
- // center_panes(
- // pane_id: usize, // Primary key for center_panes
- // parent_group_id: Option<usize>, // References pane_groups. If none, this is the root
- // position: Option<usize>, // None indicates this is the root
- // )
- //
- // CREATE TABLE items(
- // item_id: usize, // This is the item's view id, so this is not unique
- // workspace_id: usize, // References workspaces table
- // pane_id: usize, // References panes table
- // kind: String, // Indicates which view this connects to. This is the key in the item_deserializers global
- // position: usize, // Position of the item in the parent pane. This is equivalent to panes' position column
- // active: bool, // Indicates if this item is the active one in the pane
- // preview: bool // Indicates if this item is a preview item
- // )
- //
- // CREATE TABLE breakpoints(
- // workspace_id: usize Foreign Key, // References workspace table
- // path: PathBuf, // The absolute path of the file that this breakpoint belongs to
- // breakpoint_location: Vec<u32>, // A list of the locations of breakpoints
- // kind: int, // The kind of breakpoint (standard, log)
- // log_message: String, // log message for log breakpoints, otherwise it's Null
- // )
pub static ref DB: WorkspaceDb<()> =
&[
- sql!(
+ sql!(
CREATE TABLE workspaces(
workspace_id INTEGER PRIMARY KEY,
workspace_location BLOB UNIQUE,
@@ -555,7 +495,109 @@ define_connection! {
SELECT * FROM toolchains;
DROP TABLE toolchains;
ALTER TABLE toolchains2 RENAME TO toolchains;
- )
+ ),
+ sql!(
+ CREATE TABLE ssh_connections (
+ id INTEGER PRIMARY KEY,
+ host TEXT NOT NULL,
+ port INTEGER,
+ user TEXT
+ );
+
+ INSERT INTO ssh_connections (host, port, user)
+ SELECT DISTINCT host, port, user
+ FROM ssh_projects;
+
+ CREATE TABLE workspaces_2(
+ workspace_id INTEGER PRIMARY KEY,
+ paths TEXT,
+ paths_order TEXT,
+ ssh_connection_id INTEGER REFERENCES ssh_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 workspaces_2
+ SELECT
+ workspaces.workspace_id,
+ CASE
+ WHEN ssh_projects.id IS NOT NULL THEN ssh_projects.paths
+ ELSE
+ CASE
+ WHEN workspaces.local_paths_array IS NULL OR workspaces.local_paths_array = "" THEN
+ NULL
+ ELSE
+ json('[' || '"' || replace(workspaces.local_paths_array, ',', '"' || "," || '"') || '"' || ']')
+ END
+ END as paths,
+
+ CASE
+ WHEN ssh_projects.id IS NOT NULL THEN ""
+ ELSE workspaces.local_paths_order_array
+ END as paths_order,
+
+ CASE
+ WHEN ssh_projects.id IS NOT NULL THEN (
+ SELECT ssh_connections.id
+ FROM ssh_connections
+ WHERE
+ ssh_connections.host IS ssh_projects.host AND
+ ssh_connections.port IS ssh_projects.port AND
+ ssh_connections.user IS ssh_projects.user
+ )
+ ELSE NULL
+ END as ssh_connection_id,
+
+ workspaces.timestamp,
+ workspaces.window_state,
+ workspaces.window_x,
+ workspaces.window_y,
+ workspaces.window_width,
+ workspaces.window_height,
+ workspaces.display,
+ workspaces.left_dock_visible,
+ workspaces.left_dock_active_panel,
+ workspaces.right_dock_visible,
+ workspaces.right_dock_active_panel,
+ workspaces.bottom_dock_visible,
+ workspaces.bottom_dock_active_panel,
+ workspaces.left_dock_zoom,
+ workspaces.right_dock_zoom,
+ workspaces.bottom_dock_zoom,
+ workspaces.fullscreen,
+ workspaces.centered_layout,
+ workspaces.session_id,
+ workspaces.window_id
+ FROM
+ workspaces LEFT JOIN
+ ssh_projects ON
+ workspaces.ssh_project_id = ssh_projects.id;
+
+ DROP TABLE ssh_projects;
+ DROP TABLE workspaces;
+ ALTER TABLE workspaces_2 RENAME TO workspaces;
+
+ CREATE UNIQUE INDEX ix_workspaces_location ON workspaces(ssh_connection_id, paths);
+ ),
];
}
@@ -566,17 +608,33 @@ impl WorkspaceDb {
pub(crate) fn workspace_for_roots<P: AsRef<Path>>(
&self,
worktree_roots: &[P],
+ ) -> Option<SerializedWorkspace> {
+ self.workspace_for_roots_internal(worktree_roots, None)
+ }
+
+ pub(crate) fn ssh_workspace_for_roots<P: AsRef<Path>>(
+ &self,
+ worktree_roots: &[P],
+ ssh_project_id: SshProjectId,
+ ) -> Option<SerializedWorkspace> {
+ self.workspace_for_roots_internal(worktree_roots, Some(ssh_project_id))
+ }
+
+ pub(crate) fn workspace_for_roots_internal<P: AsRef<Path>>(
+ &self,
+ worktree_roots: &[P],
+ ssh_connection_id: Option<SshProjectId>,
) -> 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
- let local_paths = LocalPaths::new(worktree_roots);
+ let root_paths = PathList::new(worktree_roots);
// Note that we re-assign the workspace_id here in case it's empty
// and we've grabbed the most recent workspace
let (
workspace_id,
- local_paths,
- local_paths_order,
+ paths,
+ paths_order,
window_bounds,
display,
centered_layout,
@@ -584,8 +642,8 @@ impl WorkspaceDb {
window_id,
): (
WorkspaceId,
- Option<LocalPaths>,
- Option<LocalPathsOrder>,
+ String,
+ String,
Option<SerializedWindowBounds>,
Option<Uuid>,
Option<bool>,
@@ -595,8 +653,8 @@ impl WorkspaceDb {
.select_row_bound(sql! {
SELECT
workspace_id,
- local_paths,
- local_paths_order,
+ paths,
+ paths_order,
window_state,
window_x,
window_y,
@@ -615,25 +673,31 @@ impl WorkspaceDb {
bottom_dock_zoom,
window_id
FROM workspaces
- WHERE local_paths = ?
+ WHERE
+ paths IS ? AND
+ ssh_connection_id IS ?
+ LIMIT 1
+ })
+ .map(|mut prepared_statement| {
+ (prepared_statement)((
+ root_paths.serialize().paths,
+ ssh_connection_id.map(|id| id.0 as i32),
+ ))
+ .unwrap()
})
- .and_then(|mut prepared_statement| (prepared_statement)(&local_paths))
.context("No workspaces found")
.warn_on_err()
.flatten()?;
- let local_paths = local_paths?;
- let location = match local_paths_order {
- Some(order) => SerializedWorkspaceLocation::Local(local_paths, order),
- None => {
- let order = LocalPathsOrder::default_for_paths(&local_paths);
- SerializedWorkspaceLocation::Local(local_paths, order)
- }
- };
+ let paths = PathList::deserialize(&SerializedPathList {
+ paths,
+ order: paths_order,
+ });
Some(SerializedWorkspace {
id: workspace_id,
- location,
+ location: SerializedWorkspaceLocation::Local,
+ paths,
center_group: self
.get_center_pane_group(workspace_id)
.context("Getting center group")
@@ -648,63 +712,6 @@ impl WorkspaceDb {
})
}
- pub(crate) fn workspace_for_ssh_project(
- &self,
- ssh_project: &SerializedSshProject,
- ) -> Option<SerializedWorkspace> {
- let (workspace_id, window_bounds, display, centered_layout, docks, window_id): (
- WorkspaceId,
- Option<SerializedWindowBounds>,
- Option<Uuid>,
- Option<bool>,
- DockStructure,
- Option<u64>,
- ) = self
- .select_row_bound(sql! {
- SELECT
- workspace_id,
- window_state,
- window_x,
- window_y,
- window_width,
- window_height,
- display,
- centered_layout,
- left_dock_visible,
- left_dock_active_panel,
- left_dock_zoom,
- right_dock_visible,
- right_dock_active_panel,
- right_dock_zoom,
- bottom_dock_visible,
- bottom_dock_active_panel,
- bottom_dock_zoom,
- window_id
- FROM workspaces
- WHERE ssh_project_id = ?
- })
- .and_then(|mut prepared_statement| (prepared_statement)(ssh_project.id.0))
- .context("No workspaces found")
- .warn_on_err()
- .flatten()?;
-
- Some(SerializedWorkspace {
- id: workspace_id,
- location: SerializedWorkspaceLocation::Ssh(ssh_project.clone()),
- center_group: self
- .get_center_pane_group(workspace_id)
- .context("Getting center group")
- .log_err()?,
- window_bounds,
- centered_layout: centered_layout.unwrap_or(false),
- breakpoints: self.breakpoints(workspace_id),
- display,
- docks,
- session_id: None,
- window_id,
- })
- }
-
fn breakpoints(&self, workspace_id: WorkspaceId) -> BTreeMap<Arc<Path>, Vec<SourceBreakpoint>> {
let breakpoints: Result<Vec<(PathBuf, Breakpoint)>> = self
.select_bound(sql! {
@@ -754,6 +761,13 @@ impl WorkspaceDb {
/// Saves a workspace using the worktree roots. Will garbage collect any workspaces
/// that used this workspace previously
pub(crate) async fn save_workspace(&self, workspace: SerializedWorkspace) {
+ let paths = workspace.paths.serialize();
+ let ssh_connection_id = match &workspace.location {
+ SerializedWorkspaceLocation::Local => None,
+ SerializedWorkspaceLocation::Ssh(serialized_ssh_connection) => {
+ Some(serialized_ssh_connection.id.0)
+ }
+ };
log::debug!("Saving workspace at location: {:?}", workspace.location);
self.write(move |conn| {
conn.with_savepoint("update_worktrees", || {
@@ -763,7 +777,12 @@ impl WorkspaceDb {
DELETE FROM panes WHERE workspace_id = ?1;))?(workspace.id)
.context("Clearing old panes")?;
- conn.exec_bound(sql!(DELETE FROM breakpoints WHERE workspace_id = ?1))?(workspace.id).context("Clearing old breakpoints")?;
+ conn.exec_bound(
+ sql!(
+ DELETE FROM breakpoints WHERE workspace_id = ?1;
+ DELETE FROM toolchains WHERE workspace_id = ?1;
+ )
+ )?(workspace.id).context("Clearing old breakpoints")?;
for (path, breakpoints) in workspace.breakpoints {
for bp in breakpoints {
@@ -790,115 +809,73 @@ impl WorkspaceDb {
}
}
}
-
}
+ conn.exec_bound(sql!(
+ DELETE
+ FROM workspaces
+ WHERE
+ workspace_id != ?1 AND
+ paths IS ?2 AND
+ ssh_connection_id IS ?3
+ ))?((
+ workspace.id,
+ paths.paths.clone(),
+ ssh_connection_id,
+ ))
+ .context("clearing out old locations")?;
- match workspace.location {
- SerializedWorkspaceLocation::Local(local_paths, local_paths_order) => {
- conn.exec_bound(sql!(
- DELETE FROM toolchains WHERE workspace_id = ?1;
- DELETE FROM workspaces WHERE local_paths = ? AND workspace_id != ?
- ))?((&local_paths, workspace.id))
- .context("clearing out old locations")?;
-
- // Upsert
- let query = sql!(
- INSERT INTO workspaces(
- workspace_id,
- local_paths,
- local_paths_order,
- left_dock_visible,
- left_dock_active_panel,
- left_dock_zoom,
- right_dock_visible,
- right_dock_active_panel,
- right_dock_zoom,
- bottom_dock_visible,
- bottom_dock_active_panel,
- bottom_dock_zoom,
- session_id,
- window_id,
- timestamp,
- local_paths_array,
- local_paths_order_array
- )
- VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, CURRENT_TIMESTAMP, ?15, ?16)
- ON CONFLICT DO
- UPDATE SET
- local_paths = ?2,
- local_paths_order = ?3,
- left_dock_visible = ?4,
- left_dock_active_panel = ?5,
- left_dock_zoom = ?6,
- right_dock_visible = ?7,
- right_dock_active_panel = ?8,
- right_dock_zoom = ?9,
- bottom_dock_visible = ?10,
- bottom_dock_active_panel = ?11,
- bottom_dock_zoom = ?12,
- session_id = ?13,
- window_id = ?14,
- timestamp = CURRENT_TIMESTAMP,
- local_paths_array = ?15,
- local_paths_order_array = ?16
- );
- let mut prepared_query = conn.exec_bound(query)?;
- let args = (workspace.id, &local_paths, &local_paths_order, workspace.docks, workspace.session_id, workspace.window_id, local_paths.paths().iter().map(|path| path.to_string_lossy().to_string()).join(","), local_paths_order.order().iter().map(|order| order.to_string()).join(","));
-
- prepared_query(args).context("Updating workspace")?;
- }
- SerializedWorkspaceLocation::Ssh(ssh_project) => {
- conn.exec_bound(sql!(
- DELETE FROM toolchains WHERE workspace_id = ?1;
- DELETE FROM workspaces WHERE ssh_project_id = ? AND workspace_id != ?
- ))?((ssh_project.id.0, workspace.id))
- .context("clearing out old locations")?;
-
- // Upsert
- conn.exec_bound(sql!(
- INSERT INTO workspaces(
- workspace_id,
- ssh_project_id,
- left_dock_visible,
- left_dock_active_panel,
- left_dock_zoom,
- right_dock_visible,
- right_dock_active_panel,
- right_dock_zoom,
- bottom_dock_visible,
- bottom_dock_active_panel,
- bottom_dock_zoom,
- session_id,
- window_id,
- timestamp
- )
- VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, CURRENT_TIMESTAMP)
- ON CONFLICT DO
- UPDATE SET
- ssh_project_id = ?2,
- left_dock_visible = ?3,
- left_dock_active_panel = ?4,
- left_dock_zoom = ?5,
- right_dock_visible = ?6,
- right_dock_active_panel = ?7,
- right_dock_zoom = ?8,
- bottom_dock_visible = ?9,
- bottom_dock_active_panel = ?10,
- bottom_dock_zoom = ?11,
- session_id = ?12,
- window_id = ?13,
- timestamp = CURRENT_TIMESTAMP
- ))?((
- workspace.id,
- ssh_project.id.0,
- workspace.docks,
- workspace.session_id,
- workspace.window_id
- ))
- .context("Updating workspace")?;
- }
- }
+ // Upsert
+ let query = sql!(
+ INSERT INTO workspaces(
+ workspace_id,
+ paths,
+ paths_order,
+ ssh_connection_id,
+ left_dock_visible,
+ left_dock_active_panel,
+ left_dock_zoom,
+ right_dock_visible,
+ right_dock_active_panel,
+ right_dock_zoom,
+ bottom_dock_visible,
+ bottom_dock_active_panel,
+ bottom_dock_zoom,
+ session_id,
+ window_id,
+ timestamp
+ )
+ VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, CURRENT_TIMESTAMP)
+ ON CONFLICT DO
+ UPDATE SET
+ paths = ?2,
+ paths_order = ?3,
+ ssh_connection_id = ?4,
+ left_dock_visible = ?5,
+ left_dock_active_panel = ?6,
+ left_dock_zoom = ?7,
+ right_dock_visible = ?8,
+ right_dock_active_panel = ?9,
+ right_dock_zoom = ?10,
+ bottom_dock_visible = ?11,
+ bottom_dock_active_panel = ?12,
+ bottom_dock_zoom = ?13,
+ session_id = ?14,
+ window_id = ?15,
+ timestamp = CURRENT_TIMESTAMP
+ );
+ let mut prepared_query = conn.exec_bound(query)?;
+ let args = (
+ workspace.id,
+ paths.paths.clone(),
+ paths.order.clone(),
+ ssh_connection_id,
+ workspace.docks,
+ workspace.session_id,
+ workspace.window_id,
+ );
+
+ prepared_query(args).context("Updating workspace")?;
// Save center pane group
Self::save_pane_group(conn, workspace.id, &workspace.center_group, None)
@@ -911,89 +888,100 @@ impl WorkspaceDb {
.await;
}
- pub(crate) async fn get_or_create_ssh_project(
+ pub(crate) async fn get_or_create_ssh_connection(
&self,
host: String,
port: Option<u16>,
- paths: Vec<String>,
user: Option<String>,
- ) -> Result<SerializedSshProject> {
- let paths = serde_json::to_string(&paths)?;
- if let Some(project) = self
- .get_ssh_project(host.clone(), port, paths.clone(), user.clone())
+ ) -> Result<SshProjectId> {
+ if let Some(id) = self
+ .get_ssh_connection(host.clone(), port, user.clone())
.await?
{
- Ok(project)
+ Ok(SshProjectId(id))
} else {
log::debug!("Inserting SSH project at host {host}");
- self.insert_ssh_project(host, port, paths, user)
+ let id = self
+ .insert_ssh_connection(host, port, user)
.await?
- .context("failed to insert ssh project")
+ .context("failed to insert ssh project")?;
+ Ok(SshProjectId(id))
}
}
query! {
- async fn get_ssh_project(host: String, port: Option<u16>, paths: String, user: Option<String>) -> Result<Option<SerializedSshProject>> {
- SELECT id, host, port, paths, user
- FROM ssh_projects
- WHERE host IS ? AND port IS ? AND paths IS ? AND user IS ?
+ async fn get_ssh_connection(host: String, port: Option<u16>, user: Option<String>) -> Result<Option<u64>> {
+ SELECT id
+ FROM ssh_connections
+ WHERE host IS ? AND port IS ? AND user IS ?
LIMIT 1
}
}
query! {
- async fn insert_ssh_project(host: String, port: Option<u16>, paths: String, user: Option<String>) -> Result<Option<SerializedSshProject>> {
- INSERT INTO ssh_projects(
+ async fn insert_ssh_connection(host: String, port: Option<u16>, user: Option<String>) -> Result<Option<u64>> {
+ INSERT INTO ssh_connections (
host,
port,
- paths,
user
- ) VALUES (?1, ?2, ?3, ?4)
- RETURNING id, host, port, paths, user
+ ) VALUES (?1, ?2, ?3)
+ RETURNING id
}
}
- query! {
- pub async fn update_ssh_project_paths_query(ssh_project_id: u64, paths: String) -> Result<Option<SerializedSshProject>> {
- UPDATE ssh_projects
- SET paths = ?2
- WHERE id = ?1
- RETURNING id, host, port, paths, user
- }
- }
-
- pub(crate) async fn update_ssh_project_paths(
- &self,
- ssh_project_id: SshProjectId,
- new_paths: Vec<String>,
- ) -> Result<SerializedSshProject> {
- let paths = serde_json::to_string(&new_paths)?;
- self.update_ssh_project_paths_query(ssh_project_id.0, paths)
- .await?
- .context("failed to update ssh project paths")
- }
-
query! {
pub async fn next_id() -> Result<WorkspaceId> {
INSERT INTO workspaces DEFAULT VALUES RETURNING workspace_id
}
}
+ fn recent_workspaces(&self) -> Result<Vec<(WorkspaceId, PathList, Option<u64>)>> {
+ Ok(self
+ .recent_workspaces_query()?
+ .into_iter()
+ .map(|(id, paths, order, ssh_connection_id)| {
+ (
+ id,
+ PathList::deserialize(&SerializedPathList { paths, order }),
+ ssh_connection_id,
+ )
+ })
+ .collect())
+ }
+
query! {
- fn recent_workspaces() -> Result<Vec<(WorkspaceId, LocalPaths, LocalPathsOrder, Option<u64>)>> {
- SELECT workspace_id, local_paths, local_paths_order, ssh_project_id
+ fn recent_workspaces_query() -> Result<Vec<(WorkspaceId, String, String, Option<u64>)>> {
+ SELECT workspace_id, paths, paths_order, ssh_connection_id
FROM workspaces
- WHERE local_paths IS NOT NULL
- OR ssh_project_id IS NOT NULL
+ WHERE
+ paths IS NOT NULL OR
+ ssh_connection_id IS NOT NULL
ORDER BY timestamp DESC
}
}
+ fn session_workspaces(
+ &self,
+ session_id: String,
+ ) -> Result<Vec<(PathList, Option<u64>, Option<SshProjectId>)>> {
+ Ok(self
+ .session_workspaces_query(session_id)?
+ .into_iter()
+ .map(|(paths, order, window_id, ssh_connection_id)| {
+ (
+ PathList::deserialize(&SerializedPathList { paths, order }),
+ window_id,
+ ssh_connection_id.map(SshProjectId),
+ )
+ })
+ .collect())
+ }
+
query! {
- fn session_workspaces(session_id: String) -> Result<Vec<(LocalPaths, LocalPathsOrder, Option<u64>, Option<u64>)>> {
- SELECT local_paths, local_paths_order, window_id, ssh_project_id
+ fn session_workspaces_query(session_id: String) -> Result<Vec<(String, String, Option<u64>, Option<u64>)>> {
+ SELECT paths, paths_order, window_id, ssh_connection_id
FROM workspaces
- WHERE session_id = ?1 AND dev_server_project_id IS NULL
+ WHERE session_id = ?1
ORDER BY timestamp DESC
}
}
@@ -1013,17 +1001,40 @@ impl WorkspaceDb {
}
}
+ fn ssh_connections(&self) -> Result<Vec<SerializedSshConnection>> {
+ Ok(self
+ .ssh_connections_query()?
+ .into_iter()
+ .map(|(id, host, port, user)| SerializedSshConnection {
+ id: SshProjectId(id),
+ host,
+ port,
+ user,
+ })
+ .collect())
+ }
+
query! {
- fn ssh_projects() -> Result<Vec<SerializedSshProject>> {
- SELECT id, host, port, paths, user
- FROM ssh_projects
+ pub fn ssh_connections_query() -> Result<Vec<(u64, String, Option<u16>, Option<String>)>> {
+ SELECT id, host, port, user
+ FROM ssh_connections
}
}
+ pub fn ssh_connection(&self, id: SshProjectId) -> Result<SerializedSshConnection> {
+ let row = self.ssh_connection_query(id.0)?;
+ Ok(SerializedSshConnection {
+ id: SshProjectId(row.0),
+ host: row.1,
+ port: row.2,
+ user: row.3,
+ })
+ }
+
query! {
- fn ssh_project(id: u64) -> Result<SerializedSshProject> {
- SELECT id, host, port, paths, user
- FROM ssh_projects
+ fn ssh_connection_query(id: u64) -> Result<(u64, String, Option<u16>, Option<String>)> {
+ SELECT id, host, port, user
+ FROM ssh_connections
WHERE id = ?
}
}
@@ -1037,7 +1048,7 @@ impl WorkspaceDb {
display,
window_state, window_x, window_y, window_width, window_height
FROM workspaces
- WHERE local_paths
+ WHERE paths
IS NOT NULL
ORDER BY timestamp DESC
LIMIT 1
@@ -1054,46 +1065,35 @@ impl WorkspaceDb {
}
}
- pub async fn delete_workspace_by_dev_server_project_id(
- &self,
- id: DevServerProjectId,
- ) -> Result<()> {
- self.write(move |conn| {
- conn.exec_bound(sql!(
- DELETE FROM dev_server_projects WHERE id = ?
- ))?(id.0)?;
- conn.exec_bound(sql!(
- DELETE FROM toolchains WHERE workspace_id = ?1;
- DELETE FROM workspaces
- WHERE dev_server_project_id IS ?
- ))?(id.0)
- })
- .await
- }
-
// Returns the recent locations which are still valid on disk and deletes ones which no longer
// exist.
pub async fn recent_workspaces_on_disk(
&self,
- ) -> Result<Vec<(WorkspaceId, SerializedWorkspaceLocation)>> {
+ ) -> Result<Vec<(WorkspaceId, SerializedWorkspaceLocation, PathList)>> {
let mut result = Vec::new();
let mut delete_tasks = Vec::new();
- let ssh_projects = self.ssh_projects()?;
-
- for (id, location, order, ssh_project_id) in self.recent_workspaces()? {
- if let Some(ssh_project_id) = ssh_project_id.map(SshProjectId) {
- if let Some(ssh_project) = ssh_projects.iter().find(|rp| rp.id == ssh_project_id) {
- result.push((id, SerializedWorkspaceLocation::Ssh(ssh_project.clone())));
+ let ssh_connections = self.ssh_connections()?;
+
+ for (id, paths, ssh_connection_id) in self.recent_workspaces()? {
+ if let Some(ssh_connection_id) = ssh_connection_id.map(SshProjectId) {
+ if let Some(ssh_connection) =
+ ssh_connections.iter().find(|rp| rp.id == ssh_connection_id)
+ {
+ result.push((
+ id,
+ SerializedWorkspaceLocation::Ssh(ssh_connection.clone()),
+ paths,
+ ));
} else {
delete_tasks.push(self.delete_workspace_by_id(id));
}
continue;
}
- if location.paths().iter().all(|path| path.exists())
- && location.paths().iter().any(|path| path.is_dir())
+ if paths.paths().iter().all(|path| path.exists())
+ && paths.paths().iter().any(|path| path.is_dir())
{
- result.push((id, SerializedWorkspaceLocation::Local(location, order)));
+ result.push((id, SerializedWorkspaceLocation::Local, paths));
} else {
delete_tasks.push(self.delete_workspace_by_id(id));
}
@@ -1103,13 +1103,13 @@ impl WorkspaceDb {
Ok(result)
}
- pub async fn last_workspace(&self) -> Result<Option<SerializedWorkspaceLocation>> {
+ pub async fn last_workspace(&self) -> Result<Option<(SerializedWorkspaceLocation, PathList)>> {
Ok(self
.recent_workspaces_on_disk()
.await?
.into_iter()
.next()
- .map(|(_, location)| location))
+ .map(|(_, location, paths)| (location, paths)))
}
// Returns the locations of the workspaces that were still opened when the last
@@ -1120,25 +1120,31 @@ impl WorkspaceDb {
&self,
last_session_id: &str,
last_session_window_stack: Option<Vec<WindowId>>,
- ) -> Result<Vec<SerializedWorkspaceLocation>> {
+ ) -> Result<Vec<(SerializedWorkspaceLocation, PathList)>> {
let mut workspaces = Vec::new();
- for (location, order, window_id, ssh_project_id) in
+ for (paths, window_id, ssh_connection_id) in
self.session_workspaces(last_session_id.to_owned())?
{
- if let Some(ssh_project_id) = ssh_project_id {
- let location = SerializedWorkspaceLocation::Ssh(self.ssh_project(ssh_project_id)?);
- workspaces.push((location, window_id.map(WindowId::from)));
- } else if location.paths().iter().all(|path| path.exists())
- && location.paths().iter().any(|path| path.is_dir())
+ if let Some(ssh_connection_id) = ssh_connection_id {
+ workspaces.push((
+ SerializedWorkspaceLocation::Ssh(self.ssh_connection(ssh_connection_id)?),
+ paths,
+ window_id.map(WindowId::from),
+ ));
+ } else if paths.paths().iter().all(|path| path.exists())
+ && paths.paths().iter().any(|path| path.is_dir())
{
- let location = SerializedWorkspaceLocation::Local(location, order);
- workspaces.push((location, window_id.map(WindowId::from)));
+ workspaces.push((
+ SerializedWorkspaceLocation::Local,
+ paths,
+ window_id.map(WindowId::from),
+ ));
}
}
if let Some(stack) = last_session_window_stack {
- workspaces.sort_by_key(|(_, window_id)| {
+ workspaces.sort_by_key(|(_, _, window_id)| {
window_id
.and_then(|id| stack.iter().position(|&order_id| order_id == id))
.unwrap_or(usize::MAX)
@@ -1147,7 +1153,7 @@ impl WorkspaceDb {
Ok(workspaces
.into_iter()
- .map(|(paths, _)| paths)
+ .map(|(location, paths, _)| (location, paths))
.collect::<Vec<_>>())
}
@@ -1499,13 +1505,13 @@ pub fn delete_unloaded_items(
#[cfg(test)]
mod tests {
- use std::thread;
- use std::time::Duration;
-
use super::*;
- use crate::persistence::model::SerializedWorkspace;
- use crate::persistence::model::{SerializedItem, SerializedPane, SerializedPaneGroup};
+ use crate::persistence::model::{
+ SerializedItem, SerializedPane, SerializedPaneGroup, SerializedWorkspace,
+ };
use gpui;
+ use pretty_assertions::assert_eq;
+ use std::{thread, time::Duration};
#[gpui::test]
async fn test_breakpoints() {
@@ -1558,7 +1564,8 @@ mod tests {
let workspace = SerializedWorkspace {
id,
- location: SerializedWorkspaceLocation::from_local_paths(["/tmp"]),
+ paths: PathList::new(&["/tmp"]),
+ location: SerializedWorkspaceLocation::Local,
center_group: Default::default(),
window_bounds: Default::default(),
display: Default::default(),
@@ -1711,7 +1718,8 @@ mod tests {
let workspace = SerializedWorkspace {
id,
- location: SerializedWorkspaceLocation::from_local_paths(["/tmp"]),
+ paths: PathList::new(&["/tmp"]),
+ location: SerializedWorkspaceLocation::Local,
center_group: Default::default(),
window_bounds: Default::default(),
display: Default::default(),
@@ -1757,7 +1765,8 @@ mod tests {
let workspace_without_breakpoint = SerializedWorkspace {
id,
- location: SerializedWorkspaceLocation::from_local_paths(["/tmp"]),
+ paths: PathList::new(&["/tmp"]),
+ location: SerializedWorkspaceLocation::Local,
center_group: Default::default(),
window_bounds: Default::default(),
display: Default::default(),
@@ -1851,7 +1860,8 @@ mod tests {
let mut workspace_1 = SerializedWorkspace {
id: WorkspaceId(1),
- location: SerializedWorkspaceLocation::from_local_paths(["/tmp", "/tmp2"]),
+ paths: PathList::new(&["/tmp", "/tmp2"]),
+ location: SerializedWorkspaceLocation::Local,
center_group: Default::default(),
window_bounds: Default::default(),
display: Default::default(),
@@ -1864,7 +1874,8 @@ mod tests {
let workspace_2 = SerializedWorkspace {
id: WorkspaceId(2),
- location: SerializedWorkspaceLocation::from_local_paths(["/tmp"]),
+ paths: PathList::new(&["/tmp"]),
+ location: SerializedWorkspaceLocation::Local,
center_group: Default::default(),
window_bounds: Default::default(),
display: Default::default(),
@@ -1893,7 +1904,7 @@ mod tests {
})
.await;
- workspace_1.location = SerializedWorkspaceLocation::from_local_paths(["/tmp", "/tmp3"]);
+ workspace_1.paths = PathList::new(&["/tmp", "/tmp3"]);
db.save_workspace(workspace_1.clone()).await;
db.save_workspace(workspace_1).await;
db.save_workspace(workspace_2).await;
@@ -1969,10 +1980,8 @@ mod tests {
let workspace = SerializedWorkspace {
id: WorkspaceId(5),
- location: SerializedWorkspaceLocation::Local(
- LocalPaths::new(["/tmp", "/tmp2"]),
- LocalPathsOrder::new([1, 0]),
- ),
+ paths: PathList::new(&["/tmp", "/tmp2"]),
+ location: SerializedWorkspaceLocation::Local,
center_group,
window_bounds: Default::default(),
breakpoints: Default::default(),
@@ -1,15 +1,16 @@
use super::{SerializedAxis, SerializedWindowBounds};
use crate::{
Member, Pane, PaneAxis, SerializableItemRegistry, Workspace, WorkspaceId, item::ItemHandle,
+ path_list::PathList,
};
-use anyhow::{Context as _, Result};
+use anyhow::Result;
use async_recursion::async_recursion;
use db::sqlez::{
bindable::{Bind, Column, StaticColumnCount},
statement::Statement,
};
use gpui::{AsyncWindowContext, Entity, WeakEntity};
-use itertools::Itertools as _;
+
use project::{Project, debugger::breakpoint_store::SourceBreakpoint};
use remote::ssh_session::SshProjectId;
use serde::{Deserialize, Serialize};
@@ -18,239 +19,27 @@ use std::{
path::{Path, PathBuf},
sync::Arc,
};
-use util::{ResultExt, paths::SanitizedPath};
+use util::ResultExt;
use uuid::Uuid;
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
-pub struct SerializedSshProject {
+pub struct SerializedSshConnection {
pub id: SshProjectId,
pub host: String,
pub port: Option<u16>,
- pub paths: Vec<String>,
pub user: Option<String>,
}
-impl SerializedSshProject {
- pub fn ssh_urls(&self) -> Vec<PathBuf> {
- self.paths
- .iter()
- .map(|path| {
- let mut result = String::new();
- if let Some(user) = &self.user {
- result.push_str(user);
- result.push('@');
- }
- result.push_str(&self.host);
- if let Some(port) = &self.port {
- result.push(':');
- result.push_str(&port.to_string());
- }
- result.push_str(path);
- PathBuf::from(result)
- })
- .collect()
- }
-}
-
-impl StaticColumnCount for SerializedSshProject {
- fn column_count() -> usize {
- 5
- }
-}
-
-impl Bind for &SerializedSshProject {
- fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
- let next_index = statement.bind(&self.id.0, start_index)?;
- let next_index = statement.bind(&self.host, next_index)?;
- let next_index = statement.bind(&self.port, next_index)?;
- let raw_paths = serde_json::to_string(&self.paths)?;
- let next_index = statement.bind(&raw_paths, next_index)?;
- statement.bind(&self.user, next_index)
- }
-}
-
-impl Column for SerializedSshProject {
- fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
- let id = statement.column_int64(start_index)?;
- let host = statement.column_text(start_index + 1)?.to_string();
- let (port, _) = Option::<u16>::column(statement, start_index + 2)?;
- let raw_paths = statement.column_text(start_index + 3)?.to_string();
- let paths: Vec<String> = serde_json::from_str(&raw_paths)?;
-
- let (user, _) = Option::<String>::column(statement, start_index + 4)?;
-
- Ok((
- Self {
- id: SshProjectId(id as u64),
- host,
- port,
- paths,
- user,
- },
- start_index + 5,
- ))
- }
-}
-
-#[derive(Debug, PartialEq, Clone)]
-pub struct LocalPaths(Arc<Vec<PathBuf>>);
-
-impl LocalPaths {
- pub fn new<P: AsRef<Path>>(paths: impl IntoIterator<Item = P>) -> Self {
- let mut paths: Vec<PathBuf> = paths
- .into_iter()
- .map(|p| SanitizedPath::from(p).into())
- .collect();
- // Ensure all future `zed workspace1 workspace2` and `zed workspace2 workspace1` calls are using the same workspace.
- // The actual workspace order is stored in the `LocalPathsOrder` struct.
- paths.sort();
- Self(Arc::new(paths))
- }
-
- pub fn paths(&self) -> &Arc<Vec<PathBuf>> {
- &self.0
- }
-}
-
-impl StaticColumnCount for LocalPaths {}
-impl Bind for &LocalPaths {
- fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
- statement.bind(&bincode::serialize(&self.0)?, start_index)
- }
-}
-
-impl Column for LocalPaths {
- fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
- let path_blob = statement.column_blob(start_index)?;
- let paths: Arc<Vec<PathBuf>> = if path_blob.is_empty() {
- Default::default()
- } else {
- bincode::deserialize(path_blob).context("Bincode deserialization of paths failed")?
- };
-
- Ok((Self(paths), start_index + 1))
- }
-}
-
-#[derive(Debug, PartialEq, Clone)]
-pub struct LocalPathsOrder(Vec<usize>);
-
-impl LocalPathsOrder {
- pub fn new(order: impl IntoIterator<Item = usize>) -> Self {
- Self(order.into_iter().collect())
- }
-
- pub fn order(&self) -> &[usize] {
- self.0.as_slice()
- }
-
- pub fn default_for_paths(paths: &LocalPaths) -> Self {
- Self::new(0..paths.0.len())
- }
-}
-
-impl StaticColumnCount for LocalPathsOrder {}
-impl Bind for &LocalPathsOrder {
- fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
- statement.bind(&bincode::serialize(&self.0)?, start_index)
- }
-}
-
-impl Column for LocalPathsOrder {
- fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
- let order_blob = statement.column_blob(start_index)?;
- let order = if order_blob.is_empty() {
- Vec::new()
- } else {
- bincode::deserialize(order_blob).context("deserializing workspace root order")?
- };
-
- Ok((Self(order), start_index + 1))
- }
-}
-
#[derive(Debug, PartialEq, Clone)]
pub enum SerializedWorkspaceLocation {
- Local(LocalPaths, LocalPathsOrder),
- Ssh(SerializedSshProject),
+ Local,
+ Ssh(SerializedSshConnection),
}
impl SerializedWorkspaceLocation {
- /// Create a new `SerializedWorkspaceLocation` from a list of local paths.
- ///
- /// The paths will be sorted and the order will be stored in the `LocalPathsOrder` struct.
- ///
- /// # Examples
- ///
- /// ```
- /// use std::path::Path;
- /// use zed_workspace::SerializedWorkspaceLocation;
- ///
- /// let location = SerializedWorkspaceLocation::from_local_paths(vec![
- /// Path::new("path/to/workspace1"),
- /// Path::new("path/to/workspace2"),
- /// ]);
- /// assert_eq!(location, SerializedWorkspaceLocation::Local(
- /// LocalPaths::new(vec![
- /// Path::new("path/to/workspace1"),
- /// Path::new("path/to/workspace2"),
- /// ]),
- /// LocalPathsOrder::new(vec![0, 1]),
- /// ));
- /// ```
- ///
- /// ```
- /// use std::path::Path;
- /// use zed_workspace::SerializedWorkspaceLocation;
- ///
- /// let location = SerializedWorkspaceLocation::from_local_paths(vec![
- /// Path::new("path/to/workspace2"),
- /// Path::new("path/to/workspace1"),
- /// ]);
- ///
- /// assert_eq!(location, SerializedWorkspaceLocation::Local(
- /// LocalPaths::new(vec![
- /// Path::new("path/to/workspace1"),
- /// Path::new("path/to/workspace2"),
- /// ]),
- /// LocalPathsOrder::new(vec![1, 0]),
- /// ));
- /// ```
- pub fn from_local_paths<P: AsRef<Path>>(paths: impl IntoIterator<Item = P>) -> Self {
- let mut indexed_paths: Vec<_> = paths
- .into_iter()
- .map(|p| p.as_ref().to_path_buf())
- .enumerate()
- .collect();
-
- indexed_paths.sort_by(|(_, a), (_, b)| a.cmp(b));
-
- let sorted_paths: Vec<_> = indexed_paths.iter().map(|(_, path)| path.clone()).collect();
- let order: Vec<_> = indexed_paths.iter().map(|(index, _)| *index).collect();
-
- Self::Local(LocalPaths::new(sorted_paths), LocalPathsOrder::new(order))
- }
-
/// Get sorted paths
pub fn sorted_paths(&self) -> Arc<Vec<PathBuf>> {
- match self {
- SerializedWorkspaceLocation::Local(paths, order) => {
- if order.order().is_empty() {
- paths.paths().clone()
- } else {
- Arc::new(
- order
- .order()
- .iter()
- .zip(paths.paths().iter())
- .sorted_by_key(|(i, _)| **i)
- .map(|(_, p)| p.clone())
- .collect(),
- )
- }
- }
- SerializedWorkspaceLocation::Ssh(ssh_project) => Arc::new(ssh_project.ssh_urls()),
- }
+ unimplemented!()
}
}
@@ -258,6 +47,7 @@ impl SerializedWorkspaceLocation {
pub(crate) struct SerializedWorkspace {
pub(crate) id: WorkspaceId,
pub(crate) location: SerializedWorkspaceLocation,
+ pub(crate) paths: PathList,
pub(crate) center_group: SerializedPaneGroup,
pub(crate) window_bounds: Option<SerializedWindowBounds>,
pub(crate) centered_layout: bool,
@@ -581,80 +371,3 @@ impl Column for SerializedItem {
))
}
}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_serialize_local_paths() {
- let paths = vec!["b", "a", "c"];
- let serialized = SerializedWorkspaceLocation::from_local_paths(paths);
-
- assert_eq!(
- serialized,
- SerializedWorkspaceLocation::Local(
- LocalPaths::new(vec!["a", "b", "c"]),
- LocalPathsOrder::new(vec![1, 0, 2])
- )
- );
- }
-
- #[test]
- fn test_sorted_paths() {
- let paths = vec!["b", "a", "c"];
- let serialized = SerializedWorkspaceLocation::from_local_paths(paths);
- assert_eq!(
- serialized.sorted_paths(),
- Arc::new(vec![
- PathBuf::from("b"),
- PathBuf::from("a"),
- PathBuf::from("c"),
- ])
- );
-
- let paths = Arc::new(vec![
- PathBuf::from("a"),
- PathBuf::from("b"),
- PathBuf::from("c"),
- ]);
- let order = vec![2, 0, 1];
- let serialized =
- SerializedWorkspaceLocation::Local(LocalPaths(paths), LocalPathsOrder(order));
- assert_eq!(
- serialized.sorted_paths(),
- Arc::new(vec![
- PathBuf::from("b"),
- PathBuf::from("c"),
- PathBuf::from("a"),
- ])
- );
-
- let paths = Arc::new(vec![
- PathBuf::from("a"),
- PathBuf::from("b"),
- PathBuf::from("c"),
- ]);
- let order = vec![];
- let serialized =
- SerializedWorkspaceLocation::Local(LocalPaths(paths.clone()), LocalPathsOrder(order));
- assert_eq!(serialized.sorted_paths(), paths);
-
- let urls = ["/a", "/b", "/c"];
- let serialized = SerializedWorkspaceLocation::Ssh(SerializedSshProject {
- id: SshProjectId(0),
- host: "host".to_string(),
- port: Some(22),
- paths: urls.iter().map(|s| s.to_string()).collect(),
- user: Some("user".to_string()),
- });
- assert_eq!(
- serialized.sorted_paths(),
- Arc::new(
- urls.iter()
- .map(|p| PathBuf::from(format!("user@host:22{}", p)))
- .collect()
- )
- );
- }
-}
@@ -6,6 +6,7 @@ mod modal_layer;
pub mod notifications;
pub mod pane;
pub mod pane_group;
+mod path_list;
mod persistence;
pub mod searchable;
pub mod shared_screen;
@@ -18,6 +19,7 @@ mod workspace_settings;
pub use crate::notifications::NotificationFrame;
pub use dock::Panel;
+pub use path_list::PathList;
pub use toast_layer::{ToastAction, ToastLayer, ToastView};
use anyhow::{Context as _, Result, anyhow};
@@ -62,20 +64,20 @@ use notifications::{
};
pub use pane::*;
pub use pane_group::*;
-use persistence::{
- DB, SerializedWindowBounds,
- model::{SerializedSshProject, SerializedWorkspace},
-};
+use persistence::{DB, SerializedWindowBounds, model::SerializedWorkspace};
pub use persistence::{
DB as WORKSPACE_DB, WorkspaceDb, delete_unloaded_items,
- model::{ItemId, LocalPaths, SerializedWorkspaceLocation},
+ model::{ItemId, SerializedSshConnection, SerializedWorkspaceLocation},
};
use postage::stream::Stream;
use project::{
DirectoryLister, Project, ProjectEntryId, ProjectPath, ResolvedPath, Worktree, WorktreeId,
debugger::{breakpoint_store::BreakpointStoreEvent, session::ThreadStatus},
};
-use remote::{SshClientDelegate, SshConnectionOptions, ssh_session::ConnectionIdentifier};
+use remote::{
+ SshClientDelegate, SshConnectionOptions,
+ ssh_session::{ConnectionIdentifier, SshProjectId},
+};
use schemars::JsonSchema;
use serde::Deserialize;
use session::AppSession;
@@ -1042,7 +1044,7 @@ pub enum OpenVisible {
enum WorkspaceLocation {
// Valid local paths or SSH project to serialize
- Location(SerializedWorkspaceLocation),
+ Location(SerializedWorkspaceLocation, PathList),
// No valid location found hence clear session id
DetachFromSession,
// No valid location found to serialize
@@ -1126,7 +1128,7 @@ pub struct Workspace {
terminal_provider: Option<Box<dyn TerminalProvider>>,
debugger_provider: Option<Arc<dyn DebuggerProvider>>,
serializable_items_tx: UnboundedSender<Box<dyn SerializableItemHandle>>,
- serialized_ssh_project: Option<SerializedSshProject>,
+ serialized_ssh_connection_id: Option<SshProjectId>,
_items_serializer: Task<Result<()>>,
session_id: Option<String>,
scheduled_tasks: Vec<Task<()>>,
@@ -1175,8 +1177,6 @@ impl Workspace {
project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded(_) => {
this.update_window_title(window, cx);
- this.update_ssh_paths(cx);
- this.serialize_ssh_paths(window, cx);
this.serialize_workspace(window, cx);
// This event could be triggered by `AddFolderToProject` or `RemoveFromProject`.
this.update_history(cx);
@@ -1461,7 +1461,7 @@ impl Workspace {
serializable_items_tx,
_items_serializer,
session_id: Some(session_id),
- serialized_ssh_project: None,
+ serialized_ssh_connection_id: None,
scheduled_tasks: Vec::new(),
}
}
@@ -1501,20 +1501,9 @@ impl Workspace {
let serialized_workspace =
persistence::DB.workspace_for_roots(paths_to_open.as_slice());
- let workspace_location = serialized_workspace
- .as_ref()
- .map(|ws| &ws.location)
- .and_then(|loc| match loc {
- SerializedWorkspaceLocation::Local(_, order) => {
- Some((loc.sorted_paths(), order.order()))
- }
- _ => None,
- });
-
- if let Some((paths, order)) = workspace_location {
- paths_to_open = paths.iter().cloned().collect();
-
- if order.iter().enumerate().any(|(i, &j)| i != j) {
+ if let Some(paths) = serialized_workspace.as_ref().map(|ws| &ws.paths) {
+ paths_to_open = paths.paths().to_vec();
+ if !paths.is_lexicographically_ordered() {
project_handle
.update(cx, |project, cx| {
project.set_worktrees_reordered(true, cx);
@@ -2034,14 +2023,6 @@ impl Workspace {
self.debugger_provider.clone()
}
- pub fn serialized_ssh_project(&self) -> Option<SerializedSshProject> {
- self.serialized_ssh_project.clone()
- }
-
- pub fn set_serialized_ssh_project(&mut self, serialized_ssh_project: SerializedSshProject) {
- self.serialized_ssh_project = Some(serialized_ssh_project);
- }
-
pub fn prompt_for_open_path(
&mut self,
path_prompt_options: PathPromptOptions,
@@ -5088,59 +5069,12 @@ impl Workspace {
self.session_id.clone()
}
- fn local_paths(&self, cx: &App) -> Option<Vec<Arc<Path>>> {
- let project = self.project().read(cx);
-
- if project.is_local() {
- Some(
- project
- .visible_worktrees(cx)
- .map(|worktree| worktree.read(cx).abs_path())
- .collect::<Vec<_>>(),
- )
- } else {
- None
- }
- }
-
- fn update_ssh_paths(&mut self, cx: &App) {
+ pub fn root_paths(&self, cx: &App) -> Vec<Arc<Path>> {
let project = self.project().read(cx);
- if !project.is_local() {
- let paths: Vec<String> = project
- .visible_worktrees(cx)
- .map(|worktree| worktree.read(cx).abs_path().to_string_lossy().to_string())
- .collect();
- if let Some(ssh_project) = &mut self.serialized_ssh_project {
- ssh_project.paths = paths;
- }
- }
- }
-
- fn serialize_ssh_paths(&mut self, window: &mut Window, cx: &mut Context<Workspace>) {
- if self._schedule_serialize_ssh_paths.is_none() {
- self._schedule_serialize_ssh_paths =
- Some(cx.spawn_in(window, async move |this, cx| {
- cx.background_executor()
- .timer(SERIALIZATION_THROTTLE_TIME)
- .await;
- this.update_in(cx, |this, window, cx| {
- let task = if let Some(ssh_project) = &this.serialized_ssh_project {
- let ssh_project_id = ssh_project.id;
- let ssh_project_paths = ssh_project.paths.clone();
- window.spawn(cx, async move |_| {
- persistence::DB
- .update_ssh_project_paths(ssh_project_id, ssh_project_paths)
- .await
- })
- } else {
- Task::ready(Err(anyhow::anyhow!("No SSH project to serialize")))
- };
- task.detach();
- this._schedule_serialize_ssh_paths.take();
- })
- .log_err();
- }));
- }
+ project
+ .visible_worktrees(cx)
+ .map(|worktree| worktree.read(cx).abs_path())
+ .collect::<Vec<_>>()
}
fn remove_panes(&mut self, member: Member, window: &mut Window, cx: &mut Context<Workspace>) {
@@ -5313,7 +5247,7 @@ impl Workspace {
}
match self.serialize_workspace_location(cx) {
- WorkspaceLocation::Location(location) => {
+ WorkspaceLocation::Location(location, paths) => {
let breakpoints = self.project.update(cx, |project, cx| {
project
.breakpoint_store()
@@ -5327,6 +5261,7 @@ impl Workspace {
let serialized_workspace = SerializedWorkspace {
id: database_id,
location,
+ paths,
center_group,
window_bounds,
display: Default::default(),
@@ -5352,13 +5287,21 @@ impl Workspace {
}
fn serialize_workspace_location(&self, cx: &App) -> WorkspaceLocation {
- if let Some(ssh_project) = &self.serialized_ssh_project {
- WorkspaceLocation::Location(SerializedWorkspaceLocation::Ssh(ssh_project.clone()))
- } else if let Some(local_paths) = self.local_paths(cx) {
- if !local_paths.is_empty() {
- WorkspaceLocation::Location(SerializedWorkspaceLocation::from_local_paths(
- local_paths,
- ))
+ let paths = PathList::new(&self.root_paths(cx));
+ let connection = self.project.read(cx).ssh_connection_options(cx);
+ if let Some((id, connection)) = self.serialized_ssh_connection_id.zip(connection) {
+ WorkspaceLocation::Location(
+ SerializedWorkspaceLocation::Ssh(SerializedSshConnection {
+ id,
+ host: connection.host,
+ port: connection.port,
+ user: connection.username,
+ }),
+ paths,
+ )
+ } else if self.project.read(cx).is_local() {
+ if !paths.is_empty() {
+ WorkspaceLocation::Location(SerializedWorkspaceLocation::Local, paths)
} else {
WorkspaceLocation::DetachFromSession
}
@@ -5371,13 +5314,13 @@ impl Workspace {
let Some(id) = self.database_id() else {
return;
};
- let location = match self.serialize_workspace_location(cx) {
- WorkspaceLocation::Location(location) => location,
- _ => return,
- };
+ if !self.project.read(cx).is_local() {
+ return;
+ }
if let Some(manager) = HistoryManager::global(cx) {
+ let paths = PathList::new(&self.root_paths(cx));
manager.update(cx, |this, cx| {
- this.update_history(id, HistoryManagerEntry::new(id, &location), cx);
+ this.update_history(id, HistoryManagerEntry::new(id, &paths), cx);
});
}
}
@@ -6843,14 +6786,14 @@ impl WorkspaceHandle for Entity<Workspace> {
}
}
-pub async fn last_opened_workspace_location() -> Option<SerializedWorkspaceLocation> {
+pub async fn last_opened_workspace_location() -> Option<(SerializedWorkspaceLocation, PathList)> {
DB.last_workspace().await.log_err().flatten()
}
pub fn last_session_workspace_locations(
last_session_id: &str,
last_session_window_stack: Option<Vec<WindowId>>,
-) -> Option<Vec<SerializedWorkspaceLocation>> {
+) -> Option<Vec<(SerializedWorkspaceLocation, PathList)>> {
DB.last_session_workspace_locations(last_session_id, last_session_window_stack)
.log_err()
}
@@ -7353,7 +7296,7 @@ pub fn open_ssh_project_with_new_connection(
cx: &mut App,
) -> Task<Result<()>> {
cx.spawn(async move |cx| {
- let (serialized_ssh_project, workspace_id, serialized_workspace) =
+ let (workspace_id, serialized_workspace) =
serialize_ssh_project(connection_options.clone(), paths.clone(), cx).await?;
let session = match cx
@@ -7387,7 +7330,6 @@ pub fn open_ssh_project_with_new_connection(
open_ssh_project_inner(
project,
paths,
- serialized_ssh_project,
workspace_id,
serialized_workspace,
app_state,
@@ -7407,13 +7349,12 @@ pub fn open_ssh_project_with_existing_connection(
cx: &mut AsyncApp,
) -> Task<Result<()>> {
cx.spawn(async move |cx| {
- let (serialized_ssh_project, workspace_id, serialized_workspace) =
+ let (workspace_id, serialized_workspace) =
serialize_ssh_project(connection_options.clone(), paths.clone(), cx).await?;
open_ssh_project_inner(
project,
paths,
- serialized_ssh_project,
workspace_id,
serialized_workspace,
app_state,
@@ -7427,7 +7368,6 @@ pub fn open_ssh_project_with_existing_connection(
async fn open_ssh_project_inner(
project: Entity<Project>,
paths: Vec<PathBuf>,
- serialized_ssh_project: SerializedSshProject,
workspace_id: WorkspaceId,
serialized_workspace: Option<SerializedWorkspace>,
app_state: Arc<AppState>,
@@ -7480,7 +7420,6 @@ async fn open_ssh_project_inner(
let mut workspace =
Workspace::new(Some(workspace_id), project, app_state.clone(), window, cx);
- workspace.set_serialized_ssh_project(serialized_ssh_project);
workspace.update_history(cx);
if let Some(ref serialized) = serialized_workspace {
@@ -7517,28 +7456,18 @@ fn serialize_ssh_project(
connection_options: SshConnectionOptions,
paths: Vec<PathBuf>,
cx: &AsyncApp,
-) -> Task<
- Result<(
- SerializedSshProject,
- WorkspaceId,
- Option<SerializedWorkspace>,
- )>,
-> {
+) -> Task<Result<(WorkspaceId, Option<SerializedWorkspace>)>> {
cx.background_spawn(async move {
- let serialized_ssh_project = persistence::DB
- .get_or_create_ssh_project(
+ let ssh_connection_id = persistence::DB
+ .get_or_create_ssh_connection(
connection_options.host.clone(),
connection_options.port,
- paths
- .iter()
- .map(|path| path.to_string_lossy().to_string())
- .collect::<Vec<_>>(),
connection_options.username.clone(),
)
.await?;
let serialized_workspace =
- persistence::DB.workspace_for_ssh_project(&serialized_ssh_project);
+ persistence::DB.ssh_workspace_for_roots(&paths, ssh_connection_id);
let workspace_id = if let Some(workspace_id) =
serialized_workspace.as_ref().map(|workspace| workspace.id)
@@ -7548,7 +7477,7 @@ fn serialize_ssh_project(
persistence::DB.next_id().await?
};
- Ok((serialized_ssh_project, workspace_id, serialized_workspace))
+ Ok((workspace_id, serialized_workspace))
})
}
@@ -8095,18 +8024,15 @@ pub fn ssh_workspace_position_from_db(
paths_to_open: &[PathBuf],
cx: &App,
) -> Task<Result<WorkspacePosition>> {
- let paths = paths_to_open
- .iter()
- .map(|path| path.to_string_lossy().to_string())
- .collect::<Vec<_>>();
+ let paths = paths_to_open.to_vec();
cx.background_spawn(async move {
- let serialized_ssh_project = persistence::DB
- .get_or_create_ssh_project(host, port, paths, user)
+ let ssh_connection_id = persistence::DB
+ .get_or_create_ssh_connection(host, port, user)
.await
.context("fetching serialized ssh project")?;
let serialized_workspace =
- persistence::DB.workspace_for_ssh_project(&serialized_ssh_project);
+ persistence::DB.ssh_workspace_for_roots(&paths, ssh_connection_id);
let (window_bounds, display) = if let Some(bounds) = window_bounds_env_override() {
(Some(WindowBounds::Windowed(bounds)), None)
@@ -47,8 +47,8 @@ use theme::{
use util::{ResultExt, TryFutureExt, maybe};
use uuid::Uuid;
use workspace::{
- AppState, SerializedWorkspaceLocation, Toast, Workspace, WorkspaceSettings, WorkspaceStore,
- notifications::NotificationId,
+ AppState, PathList, SerializedWorkspaceLocation, Toast, Workspace, WorkspaceSettings,
+ WorkspaceStore, notifications::NotificationId,
};
use zed::{
OpenListener, OpenRequest, RawOpenRequest, app_menus, build_window_options,
@@ -949,15 +949,14 @@ async fn restore_or_create_workspace(app_state: Arc<AppState>, cx: &mut AsyncApp
if let Some(locations) = restorable_workspace_locations(cx, &app_state).await {
let mut tasks = Vec::new();
- for location in locations {
+ for (location, paths) in locations {
match location {
- SerializedWorkspaceLocation::Local(location, _) => {
+ SerializedWorkspaceLocation::Local => {
let app_state = app_state.clone();
- let paths = location.paths().to_vec();
let task = cx.spawn(async move |cx| {
let open_task = cx.update(|cx| {
workspace::open_paths(
- &paths,
+ &paths.paths(),
app_state,
workspace::OpenOptions::default(),
cx,
@@ -979,7 +978,7 @@ async fn restore_or_create_workspace(app_state: Arc<AppState>, cx: &mut AsyncApp
match connection_options {
Ok(connection_options) => recent_projects::open_ssh_project(
connection_options,
- ssh.paths.into_iter().map(PathBuf::from).collect(),
+ paths.paths().into_iter().map(PathBuf::from).collect(),
app_state,
workspace::OpenOptions::default(),
cx,
@@ -1070,7 +1069,7 @@ async fn restore_or_create_workspace(app_state: Arc<AppState>, cx: &mut AsyncApp
pub(crate) async fn restorable_workspace_locations(
cx: &mut AsyncApp,
app_state: &Arc<AppState>,
-) -> Option<Vec<SerializedWorkspaceLocation>> {
+) -> Option<Vec<(SerializedWorkspaceLocation, PathList)>> {
let mut restore_behavior = cx
.update(|cx| WorkspaceSettings::get(None, cx).restore_on_startup)
.ok()?;
@@ -26,6 +26,7 @@ use std::thread;
use std::time::Duration;
use util::ResultExt;
use util::paths::PathWithPosition;
+use workspace::PathList;
use workspace::item::ItemHandle;
use workspace::{AppState, OpenOptions, SerializedWorkspaceLocation, Workspace};
@@ -361,12 +362,14 @@ async fn open_workspaces(
if open_new_workspace == Some(true) {
Vec::new()
} else {
- let locations = restorable_workspace_locations(cx, &app_state).await;
- locations.unwrap_or_default()
+ restorable_workspace_locations(cx, &app_state)
+ .await
+ .unwrap_or_default()
}
} else {
- vec![SerializedWorkspaceLocation::from_local_paths(
- paths.into_iter().map(PathBuf::from),
+ vec![(
+ SerializedWorkspaceLocation::Local,
+ PathList::new(&paths.into_iter().map(PathBuf::from).collect::<Vec<_>>()),
)]
};
@@ -394,9 +397,9 @@ async fn open_workspaces(
// If there are paths to open, open a workspace for each grouping of paths
let mut errored = false;
- for location in grouped_locations {
+ for (location, workspace_paths) in grouped_locations {
match location {
- SerializedWorkspaceLocation::Local(workspace_paths, _) => {
+ SerializedWorkspaceLocation::Local => {
let workspace_paths = workspace_paths
.paths()
.iter()
@@ -429,7 +432,7 @@ async fn open_workspaces(
cx.spawn(async move |cx| {
open_ssh_project(
connection_options,
- ssh.paths.into_iter().map(PathBuf::from).collect(),
+ workspace_paths.paths().to_vec(),
app_state,
OpenOptions::default(),
cx,