Restructure persistence of remote workspaces to make room for WSL and other non-ssh remote projects (#36714)
This is another pure refactor, to prepare for adding direct WSL support. ### Todo * [x] Represent `paths` in the same way for all workspaces, instead of having a completely separate SSH representation * [x] Adjust sqlite tables * [x] `ssh_projects` -> `ssh_connections` (drop paths) * [x] `workspaces.local_paths` -> `paths` * [x] remove duplicate path columns on `workspaces` * [x] Add migrations for backward-compatibility Release Notes: - N/A --------- Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
This commit is contained in:
parent
639417c2bc
commit
f649c31bf9
12 changed files with 784 additions and 1080 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -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]>>();
|
||||
|
|
121
crates/workspace/src/path_list.rs
Normal file
121
crates/workspace/src/path_list.rs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -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>>> {
|
||||
pub fn root_paths(&self, cx: &App) -> 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) {
|
||||
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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue