remote projects per user (#10594)
Release Notes: - Made remote projects per-user instead of per-channel. If you'd like to be part of the remote development alpha, please email hi@zed.dev. --------- Co-authored-by: Bennet Bo Fenner <53836821+bennetbo@users.noreply.github.com> Co-authored-by: Bennet <bennetbo@gmx.de> Co-authored-by: Nate Butler <1714999+iamnbutler@users.noreply.github.com> Co-authored-by: Nate Butler <iamnbutler@gmail.com>
This commit is contained in:
parent
8ae4c3277f
commit
e0c83a1d32
56 changed files with 2807 additions and 1625 deletions
|
@ -45,6 +45,7 @@ node_runtime.workspace = true
|
|||
parking_lot.workspace = true
|
||||
postage.workspace = true
|
||||
project.workspace = true
|
||||
remote_projects.workspace = true
|
||||
task.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
|
|
|
@ -513,8 +513,9 @@ impl<T: Item> ItemHandle for View<T> {
|
|||
}));
|
||||
}
|
||||
|
||||
let mut event_subscription =
|
||||
Some(cx.subscribe(self, move |workspace, item, event, cx| {
|
||||
let mut event_subscription = Some(cx.subscribe(
|
||||
self,
|
||||
move |workspace, item: View<T>, event, cx| {
|
||||
let pane = if let Some(pane) = workspace
|
||||
.panes_by_item
|
||||
.get(&item.item_id())
|
||||
|
@ -575,7 +576,8 @@ impl<T: Item> ItemHandle for View<T> {
|
|||
|
||||
_ => {}
|
||||
});
|
||||
}));
|
||||
},
|
||||
));
|
||||
|
||||
cx.on_blur(&self.focus_handle(cx), move |workspace, cx| {
|
||||
if WorkspaceSettings::get_global(cx).autosave == AutosaveSetting::OnFocusChange {
|
||||
|
|
|
@ -3,6 +3,7 @@ pub mod model;
|
|||
use std::path::Path;
|
||||
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use client::RemoteProjectId;
|
||||
use db::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql};
|
||||
use gpui::{point, size, Axis, Bounds};
|
||||
|
||||
|
@ -17,11 +18,11 @@ use uuid::Uuid;
|
|||
use crate::WorkspaceId;
|
||||
|
||||
use model::{
|
||||
GroupId, PaneId, SerializedItem, SerializedPane, SerializedPaneGroup, SerializedWorkspace,
|
||||
WorkspaceLocation,
|
||||
GroupId, LocalPaths, PaneId, SerializedItem, SerializedPane, SerializedPaneGroup,
|
||||
SerializedWorkspace,
|
||||
};
|
||||
|
||||
use self::model::DockStructure;
|
||||
use self::model::{DockStructure, SerializedRemoteProject, SerializedWorkspaceLocation};
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub(crate) struct SerializedAxis(pub(crate) gpui::Axis);
|
||||
|
@ -125,7 +126,7 @@ define_connection! {
|
|||
//
|
||||
// workspaces(
|
||||
// workspace_id: usize, // Primary key for workspaces
|
||||
// workspace_location: Bincode<Vec<PathBuf>>,
|
||||
// local_paths: Bincode<Vec<PathBuf>>,
|
||||
// dock_visible: bool, // Deprecated
|
||||
// dock_anchor: DockAnchor, // Deprecated
|
||||
// dock_pane: Option<usize>, // Deprecated
|
||||
|
@ -289,6 +290,15 @@ define_connection! {
|
|||
sql!(
|
||||
ALTER TABLE workspaces ADD COLUMN centered_layout INTEGER; //bool
|
||||
),
|
||||
sql!(
|
||||
CREATE TABLE remote_projects (
|
||||
remote_project_id INTEGER NOT NULL UNIQUE,
|
||||
path TEXT,
|
||||
dev_server_name TEXT
|
||||
);
|
||||
ALTER TABLE workspaces ADD COLUMN remote_project_id INTEGER;
|
||||
ALTER TABLE workspaces RENAME COLUMN workspace_location TO local_paths;
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -300,13 +310,23 @@ impl WorkspaceDb {
|
|||
&self,
|
||||
worktree_roots: &[P],
|
||||
) -> Option<SerializedWorkspace> {
|
||||
let workspace_location: WorkspaceLocation = worktree_roots.into();
|
||||
let local_paths = LocalPaths::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, workspace_location, bounds, display, fullscreen, centered_layout, docks): (
|
||||
let (
|
||||
workspace_id,
|
||||
local_paths,
|
||||
remote_project_id,
|
||||
bounds,
|
||||
display,
|
||||
fullscreen,
|
||||
centered_layout,
|
||||
docks,
|
||||
): (
|
||||
WorkspaceId,
|
||||
WorkspaceLocation,
|
||||
Option<LocalPaths>,
|
||||
Option<u64>,
|
||||
Option<SerializedWindowsBounds>,
|
||||
Option<Uuid>,
|
||||
Option<bool>,
|
||||
|
@ -316,7 +336,8 @@ impl WorkspaceDb {
|
|||
.select_row_bound(sql! {
|
||||
SELECT
|
||||
workspace_id,
|
||||
workspace_location,
|
||||
local_paths,
|
||||
remote_project_id,
|
||||
window_state,
|
||||
window_x,
|
||||
window_y,
|
||||
|
@ -335,16 +356,34 @@ impl WorkspaceDb {
|
|||
bottom_dock_active_panel,
|
||||
bottom_dock_zoom
|
||||
FROM workspaces
|
||||
WHERE workspace_location = ?
|
||||
WHERE local_paths = ?
|
||||
})
|
||||
.and_then(|mut prepared_statement| (prepared_statement)(&workspace_location))
|
||||
.and_then(|mut prepared_statement| (prepared_statement)(&local_paths))
|
||||
.context("No workspaces found")
|
||||
.warn_on_err()
|
||||
.flatten()?;
|
||||
|
||||
let location = if let Some(remote_project_id) = remote_project_id {
|
||||
let remote_project: SerializedRemoteProject = self
|
||||
.select_row_bound(sql! {
|
||||
SELECT remote_project_id, path, dev_server_name
|
||||
FROM remote_projects
|
||||
WHERE remote_project_id = ?
|
||||
})
|
||||
.and_then(|mut prepared_statement| (prepared_statement)(remote_project_id))
|
||||
.context("No remote project found")
|
||||
.warn_on_err()
|
||||
.flatten()?;
|
||||
SerializedWorkspaceLocation::Remote(remote_project)
|
||||
} else if let Some(local_paths) = local_paths {
|
||||
SerializedWorkspaceLocation::Local(local_paths)
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
Some(SerializedWorkspace {
|
||||
id: workspace_id,
|
||||
location: workspace_location.clone(),
|
||||
location,
|
||||
center_group: self
|
||||
.get_center_pane_group(workspace_id)
|
||||
.context("Getting center group")
|
||||
|
@ -368,43 +407,102 @@ impl WorkspaceDb {
|
|||
DELETE FROM panes WHERE workspace_id = ?1;))?(workspace.id)
|
||||
.context("Clearing old panes")?;
|
||||
|
||||
conn.exec_bound(sql!(
|
||||
DELETE FROM workspaces WHERE workspace_location = ? AND workspace_id != ?
|
||||
))?((&workspace.location, workspace.id))
|
||||
.context("clearing out old locations")?;
|
||||
match workspace.location {
|
||||
SerializedWorkspaceLocation::Local(local_paths) => {
|
||||
conn.exec_bound(sql!(
|
||||
DELETE FROM workspaces WHERE local_paths = ? AND workspace_id != ?
|
||||
))?((&local_paths, workspace.id))
|
||||
.context("clearing out old locations")?;
|
||||
|
||||
// Upsert
|
||||
conn.exec_bound(sql!(
|
||||
INSERT INTO workspaces(
|
||||
workspace_id,
|
||||
workspace_location,
|
||||
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,
|
||||
timestamp
|
||||
)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, CURRENT_TIMESTAMP)
|
||||
ON CONFLICT DO
|
||||
UPDATE SET
|
||||
workspace_location = ?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,
|
||||
timestamp = CURRENT_TIMESTAMP
|
||||
))?((workspace.id, &workspace.location, workspace.docks))
|
||||
.context("Updating workspace")?;
|
||||
// Upsert
|
||||
conn.exec_bound(sql!(
|
||||
INSERT INTO workspaces(
|
||||
workspace_id,
|
||||
local_paths,
|
||||
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,
|
||||
timestamp
|
||||
)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, CURRENT_TIMESTAMP)
|
||||
ON CONFLICT DO
|
||||
UPDATE SET
|
||||
local_paths = ?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,
|
||||
timestamp = CURRENT_TIMESTAMP
|
||||
))?((workspace.id, &local_paths, workspace.docks))
|
||||
.context("Updating workspace")?;
|
||||
}
|
||||
SerializedWorkspaceLocation::Remote(remote_project) => {
|
||||
conn.exec_bound(sql!(
|
||||
DELETE FROM workspaces WHERE remote_project_id = ? AND workspace_id != ?
|
||||
))?((remote_project.id.0, workspace.id))
|
||||
.context("clearing out old locations")?;
|
||||
|
||||
conn.exec_bound(sql!(
|
||||
INSERT INTO remote_projects(
|
||||
remote_project_id,
|
||||
path,
|
||||
dev_server_name
|
||||
) VALUES (?1, ?2, ?3)
|
||||
ON CONFLICT DO
|
||||
UPDATE SET
|
||||
path = ?2,
|
||||
dev_server_name = ?3
|
||||
))?(&remote_project)?;
|
||||
|
||||
// Upsert
|
||||
conn.exec_bound(sql!(
|
||||
INSERT INTO workspaces(
|
||||
workspace_id,
|
||||
remote_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,
|
||||
timestamp
|
||||
)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, CURRENT_TIMESTAMP)
|
||||
ON CONFLICT DO
|
||||
UPDATE SET
|
||||
remote_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,
|
||||
timestamp = CURRENT_TIMESTAMP
|
||||
))?((
|
||||
workspace.id,
|
||||
remote_project.id.0,
|
||||
workspace.docks,
|
||||
))
|
||||
.context("Updating workspace")?;
|
||||
}
|
||||
}
|
||||
|
||||
// Save center pane group
|
||||
Self::save_pane_group(conn, workspace.id, &workspace.center_group, None)
|
||||
|
@ -424,24 +522,43 @@ impl WorkspaceDb {
|
|||
}
|
||||
|
||||
query! {
|
||||
fn recent_workspaces() -> Result<Vec<(WorkspaceId, WorkspaceLocation)>> {
|
||||
SELECT workspace_id, workspace_location
|
||||
fn recent_workspaces() -> Result<Vec<(WorkspaceId, LocalPaths, Option<u64>)>> {
|
||||
SELECT workspace_id, local_paths, remote_project_id
|
||||
FROM workspaces
|
||||
WHERE workspace_location IS NOT NULL
|
||||
WHERE local_paths IS NOT NULL OR remote_project_id IS NOT NULL
|
||||
ORDER BY timestamp DESC
|
||||
}
|
||||
}
|
||||
|
||||
query! {
|
||||
pub fn last_window() -> Result<(Option<Uuid>, Option<SerializedWindowsBounds>, Option<bool>)> {
|
||||
SELECT display, window_state, window_x, window_y, window_width, window_height, fullscreen
|
||||
FROM workspaces
|
||||
WHERE workspace_location IS NOT NULL
|
||||
ORDER BY timestamp DESC
|
||||
LIMIT 1
|
||||
fn remote_projects() -> Result<Vec<SerializedRemoteProject>> {
|
||||
SELECT remote_project_id, path, dev_server_name
|
||||
FROM remote_projects
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn last_window(
|
||||
&self,
|
||||
) -> anyhow::Result<(Option<Uuid>, Option<SerializedWindowsBounds>, Option<bool>)> {
|
||||
let mut prepared_query =
|
||||
self.select::<(Option<Uuid>, Option<SerializedWindowsBounds>, Option<bool>)>(sql!(
|
||||
SELECT
|
||||
display,
|
||||
window_state, window_x, window_y, window_width, window_height,
|
||||
fullscreen
|
||||
FROM workspaces
|
||||
WHERE local_paths
|
||||
IS NOT NULL
|
||||
ORDER BY timestamp DESC
|
||||
LIMIT 1
|
||||
))?;
|
||||
let result = prepared_query()?;
|
||||
Ok(result
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap_or_else(|| (None, None, None)))
|
||||
}
|
||||
|
||||
query! {
|
||||
pub async fn delete_workspace_by_id(id: WorkspaceId) -> Result<()> {
|
||||
DELETE FROM workspaces
|
||||
|
@ -451,14 +568,29 @@ impl WorkspaceDb {
|
|||
|
||||
// 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, WorkspaceLocation)>> {
|
||||
pub async fn recent_workspaces_on_disk(
|
||||
&self,
|
||||
) -> Result<Vec<(WorkspaceId, SerializedWorkspaceLocation)>> {
|
||||
let mut result = Vec::new();
|
||||
let mut delete_tasks = Vec::new();
|
||||
for (id, location) in self.recent_workspaces()? {
|
||||
let remote_projects = self.remote_projects()?;
|
||||
|
||||
for (id, location, remote_project_id) in self.recent_workspaces()? {
|
||||
if let Some(remote_project_id) = remote_project_id.map(RemoteProjectId) {
|
||||
if let Some(remote_project) =
|
||||
remote_projects.iter().find(|rp| rp.id == remote_project_id)
|
||||
{
|
||||
result.push((id, remote_project.clone().into()));
|
||||
} 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())
|
||||
{
|
||||
result.push((id, location));
|
||||
result.push((id, location.into()));
|
||||
} else {
|
||||
delete_tasks.push(self.delete_workspace_by_id(id));
|
||||
}
|
||||
|
@ -468,13 +600,16 @@ impl WorkspaceDb {
|
|||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn last_workspace(&self) -> Result<Option<WorkspaceLocation>> {
|
||||
pub async fn last_workspace(&self) -> Result<Option<LocalPaths>> {
|
||||
Ok(self
|
||||
.recent_workspaces_on_disk()
|
||||
.await?
|
||||
.into_iter()
|
||||
.next()
|
||||
.map(|(_, location)| location))
|
||||
.filter_map(|(_, location)| match location {
|
||||
SerializedWorkspaceLocation::Local(local_paths) => Some(local_paths),
|
||||
SerializedWorkspaceLocation::Remote(_) => None,
|
||||
})
|
||||
.next())
|
||||
}
|
||||
|
||||
fn get_center_pane_group(&self, workspace_id: WorkspaceId) -> Result<SerializedPaneGroup> {
|
||||
|
@ -774,7 +909,7 @@ mod tests {
|
|||
|
||||
let mut workspace_1 = SerializedWorkspace {
|
||||
id: WorkspaceId(1),
|
||||
location: (["/tmp", "/tmp2"]).into(),
|
||||
location: LocalPaths::new(["/tmp", "/tmp2"]).into(),
|
||||
center_group: Default::default(),
|
||||
bounds: Default::default(),
|
||||
display: Default::default(),
|
||||
|
@ -785,7 +920,7 @@ mod tests {
|
|||
|
||||
let workspace_2 = SerializedWorkspace {
|
||||
id: WorkspaceId(2),
|
||||
location: (["/tmp"]).into(),
|
||||
location: LocalPaths::new(["/tmp"]).into(),
|
||||
center_group: Default::default(),
|
||||
bounds: Default::default(),
|
||||
display: Default::default(),
|
||||
|
@ -812,7 +947,7 @@ mod tests {
|
|||
})
|
||||
.await;
|
||||
|
||||
workspace_1.location = (["/tmp", "/tmp3"]).into();
|
||||
workspace_1.location = LocalPaths::new(["/tmp", "/tmp3"]).into();
|
||||
db.save_workspace(workspace_1.clone()).await;
|
||||
db.save_workspace(workspace_1).await;
|
||||
db.save_workspace(workspace_2).await;
|
||||
|
@ -885,7 +1020,7 @@ mod tests {
|
|||
|
||||
let workspace = SerializedWorkspace {
|
||||
id: WorkspaceId(5),
|
||||
location: (["/tmp", "/tmp2"]).into(),
|
||||
location: LocalPaths::new(["/tmp", "/tmp2"]).into(),
|
||||
center_group,
|
||||
bounds: Default::default(),
|
||||
display: Default::default(),
|
||||
|
@ -915,7 +1050,7 @@ mod tests {
|
|||
|
||||
let workspace_1 = SerializedWorkspace {
|
||||
id: WorkspaceId(1),
|
||||
location: (["/tmp", "/tmp2"]).into(),
|
||||
location: LocalPaths::new(["/tmp", "/tmp2"]).into(),
|
||||
center_group: Default::default(),
|
||||
bounds: Default::default(),
|
||||
display: Default::default(),
|
||||
|
@ -926,7 +1061,7 @@ mod tests {
|
|||
|
||||
let mut workspace_2 = SerializedWorkspace {
|
||||
id: WorkspaceId(2),
|
||||
location: (["/tmp"]).into(),
|
||||
location: LocalPaths::new(["/tmp"]).into(),
|
||||
center_group: Default::default(),
|
||||
bounds: Default::default(),
|
||||
display: Default::default(),
|
||||
|
@ -953,7 +1088,7 @@ mod tests {
|
|||
assert_eq!(db.workspace_for_roots(&["/tmp3", "/tmp2", "/tmp4"]), None);
|
||||
|
||||
// Test 'mutate' case of updating a pre-existing id
|
||||
workspace_2.location = (["/tmp", "/tmp2"]).into();
|
||||
workspace_2.location = LocalPaths::new(["/tmp", "/tmp2"]).into();
|
||||
|
||||
db.save_workspace(workspace_2.clone()).await;
|
||||
assert_eq!(
|
||||
|
@ -964,7 +1099,7 @@ mod tests {
|
|||
// Test other mechanism for mutating
|
||||
let mut workspace_3 = SerializedWorkspace {
|
||||
id: WorkspaceId(3),
|
||||
location: (&["/tmp", "/tmp2"]).into(),
|
||||
location: LocalPaths::new(&["/tmp", "/tmp2"]).into(),
|
||||
center_group: Default::default(),
|
||||
bounds: Default::default(),
|
||||
display: Default::default(),
|
||||
|
@ -980,7 +1115,7 @@ mod tests {
|
|||
);
|
||||
|
||||
// Make sure that updating paths differently also works
|
||||
workspace_3.location = (["/tmp3", "/tmp4", "/tmp2"]).into();
|
||||
workspace_3.location = LocalPaths::new(["/tmp3", "/tmp4", "/tmp2"]).into();
|
||||
db.save_workspace(workspace_3.clone()).await;
|
||||
assert_eq!(db.workspace_for_roots(&["/tmp2", "tmp"]), None);
|
||||
assert_eq!(
|
||||
|
@ -999,7 +1134,7 @@ mod tests {
|
|||
) -> SerializedWorkspace {
|
||||
SerializedWorkspace {
|
||||
id: WorkspaceId(4),
|
||||
location: workspace_id.into(),
|
||||
location: LocalPaths::new(workspace_id).into(),
|
||||
center_group: center_group.clone(),
|
||||
bounds: Default::default(),
|
||||
display: Default::default(),
|
||||
|
|
|
@ -2,12 +2,14 @@ use super::SerializedAxis;
|
|||
use crate::{item::ItemHandle, ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId};
|
||||
use anyhow::{Context, Result};
|
||||
use async_recursion::async_recursion;
|
||||
use client::RemoteProjectId;
|
||||
use db::sqlez::{
|
||||
bindable::{Bind, Column, StaticColumnCount},
|
||||
statement::Statement,
|
||||
};
|
||||
use gpui::{AsyncWindowContext, Bounds, DevicePixels, Model, Task, View, WeakView};
|
||||
use project::Project;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
|
@ -15,59 +17,98 @@ use std::{
|
|||
use util::ResultExt;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct WorkspaceLocation(Arc<Vec<PathBuf>>);
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct SerializedRemoteProject {
|
||||
pub id: RemoteProjectId,
|
||||
pub dev_server_name: String,
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
#[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| p.as_ref().to_path_buf())
|
||||
.collect();
|
||||
paths.sort();
|
||||
Self(Arc::new(paths))
|
||||
}
|
||||
|
||||
impl WorkspaceLocation {
|
||||
pub fn paths(&self) -> Arc<Vec<PathBuf>> {
|
||||
self.0.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn new<P: AsRef<Path>>(paths: Vec<P>) -> Self {
|
||||
Self(Arc::new(
|
||||
paths
|
||||
.into_iter()
|
||||
.map(|p| p.as_ref().to_path_buf())
|
||||
.collect(),
|
||||
))
|
||||
impl From<LocalPaths> for SerializedWorkspaceLocation {
|
||||
fn from(local_paths: LocalPaths) -> Self {
|
||||
Self::Local(local_paths)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: AsRef<Path>, T: IntoIterator<Item = P>> From<T> for WorkspaceLocation {
|
||||
fn from(iterator: T) -> Self {
|
||||
let mut roots = iterator
|
||||
.into_iter()
|
||||
.map(|p| p.as_ref().to_path_buf())
|
||||
.collect::<Vec<_>>();
|
||||
roots.sort();
|
||||
Self(Arc::new(roots))
|
||||
}
|
||||
}
|
||||
|
||||
impl StaticColumnCount for WorkspaceLocation {}
|
||||
impl Bind for &WorkspaceLocation {
|
||||
impl StaticColumnCount for LocalPaths {}
|
||||
impl Bind for &LocalPaths {
|
||||
fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
|
||||
bincode::serialize(&self.0)
|
||||
.expect("Bincode serialization of paths should not fail")
|
||||
.bind(statement, start_index)
|
||||
statement.bind(&bincode::serialize(&self.0)?, start_index)
|
||||
}
|
||||
}
|
||||
|
||||
impl Column for WorkspaceLocation {
|
||||
impl Column for LocalPaths {
|
||||
fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
|
||||
let blob = statement.column_blob(start_index)?;
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SerializedRemoteProject> for SerializedWorkspaceLocation {
|
||||
fn from(remote_project: SerializedRemoteProject) -> Self {
|
||||
Self::Remote(remote_project)
|
||||
}
|
||||
}
|
||||
|
||||
impl StaticColumnCount for SerializedRemoteProject {}
|
||||
impl Bind for &SerializedRemoteProject {
|
||||
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.dev_server_name, next_index)?;
|
||||
statement.bind(&self.path, next_index)
|
||||
}
|
||||
}
|
||||
|
||||
impl Column for SerializedRemoteProject {
|
||||
fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
|
||||
let id = statement.column_int64(start_index)?;
|
||||
let dev_server_name = statement.column_text(start_index + 1)?.to_string();
|
||||
let path = statement.column_text(start_index + 2)?.to_string();
|
||||
Ok((
|
||||
WorkspaceLocation(bincode::deserialize(blob).context("Bincode failed")?),
|
||||
start_index + 1,
|
||||
Self {
|
||||
id: RemoteProjectId(id as u64),
|
||||
dev_server_name,
|
||||
path,
|
||||
},
|
||||
start_index + 3,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum SerializedWorkspaceLocation {
|
||||
Local(LocalPaths),
|
||||
Remote(SerializedRemoteProject),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub(crate) struct SerializedWorkspace {
|
||||
pub(crate) id: WorkspaceId,
|
||||
pub(crate) location: WorkspaceLocation,
|
||||
pub(crate) location: SerializedWorkspaceLocation,
|
||||
pub(crate) center_group: SerializedPaneGroup,
|
||||
pub(crate) bounds: Option<Bounds<DevicePixels>>,
|
||||
pub(crate) fullscreen: bool,
|
||||
|
|
|
@ -46,7 +46,7 @@ pub use pane::*;
|
|||
pub use pane_group::*;
|
||||
use persistence::{model::SerializedWorkspace, SerializedWindowsBounds, DB};
|
||||
pub use persistence::{
|
||||
model::{ItemId, WorkspaceLocation},
|
||||
model::{ItemId, LocalPaths, SerializedRemoteProject, SerializedWorkspaceLocation},
|
||||
WorkspaceDb, DB as WORKSPACE_DB,
|
||||
};
|
||||
use postage::stream::Stream;
|
||||
|
@ -82,7 +82,7 @@ use ui::{
|
|||
InteractiveElement as _, IntoElement, Label, ParentElement as _, Pixels, SharedString,
|
||||
Styled as _, ViewContext, VisualContext as _, WindowContext,
|
||||
};
|
||||
use util::ResultExt;
|
||||
use util::{maybe, ResultExt};
|
||||
use uuid::Uuid;
|
||||
pub use workspace_settings::{
|
||||
AutosaveSetting, RestoreOnStartupBehaviour, TabBarSettings, WorkspaceSettings,
|
||||
|
@ -3392,17 +3392,16 @@ impl Workspace {
|
|||
self.database_id
|
||||
}
|
||||
|
||||
fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
|
||||
fn local_paths(&self, cx: &AppContext) -> Option<LocalPaths> {
|
||||
let project = self.project().read(cx);
|
||||
|
||||
if project.is_local() {
|
||||
Some(
|
||||
Some(LocalPaths::new(
|
||||
project
|
||||
.visible_worktrees(cx)
|
||||
.map(|worktree| worktree.read(cx).abs_path())
|
||||
.collect::<Vec<_>>()
|
||||
.into(),
|
||||
)
|
||||
.collect::<Vec<_>>(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -3540,25 +3539,44 @@ impl Workspace {
|
|||
}
|
||||
}
|
||||
|
||||
if let Some(location) = self.location(cx) {
|
||||
// Load bearing special case:
|
||||
// - with_local_workspace() relies on this to not have other stuff open
|
||||
// when you open your log
|
||||
if !location.paths().is_empty() {
|
||||
let center_group = build_serialized_pane_group(&self.center.root, cx);
|
||||
let docks = build_serialized_docks(self, cx);
|
||||
let serialized_workspace = SerializedWorkspace {
|
||||
id: self.database_id,
|
||||
location,
|
||||
center_group,
|
||||
bounds: Default::default(),
|
||||
display: Default::default(),
|
||||
docks,
|
||||
fullscreen: cx.is_fullscreen(),
|
||||
centered_layout: self.centered_layout,
|
||||
};
|
||||
return cx.spawn(|_| persistence::DB.save_workspace(serialized_workspace));
|
||||
let location = if let Some(local_paths) = self.local_paths(cx) {
|
||||
if !local_paths.paths().is_empty() {
|
||||
Some(SerializedWorkspaceLocation::Local(local_paths))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if let Some(remote_project_id) = self.project().read(cx).remote_project_id() {
|
||||
let store = remote_projects::Store::global(cx).read(cx);
|
||||
maybe!({
|
||||
let project = store.remote_project(remote_project_id)?;
|
||||
let dev_server = store.dev_server(project.dev_server_id)?;
|
||||
|
||||
let remote_project = SerializedRemoteProject {
|
||||
id: remote_project_id,
|
||||
dev_server_name: dev_server.name.to_string(),
|
||||
path: project.path.to_string(),
|
||||
};
|
||||
Some(SerializedWorkspaceLocation::Remote(remote_project))
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// don't save workspace state for the empty workspace.
|
||||
if let Some(location) = location {
|
||||
let center_group = build_serialized_pane_group(&self.center.root, cx);
|
||||
let docks = build_serialized_docks(self, cx);
|
||||
let serialized_workspace = SerializedWorkspace {
|
||||
id: self.database_id,
|
||||
location,
|
||||
center_group,
|
||||
bounds: Default::default(),
|
||||
display: Default::default(),
|
||||
docks,
|
||||
fullscreen: cx.is_fullscreen(),
|
||||
centered_layout: self.centered_layout,
|
||||
};
|
||||
return cx.spawn(|_| persistence::DB.save_workspace(serialized_workspace));
|
||||
}
|
||||
Task::ready(())
|
||||
}
|
||||
|
@ -4303,7 +4321,7 @@ pub fn activate_workspace_for_project(
|
|||
None
|
||||
}
|
||||
|
||||
pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
|
||||
pub async fn last_opened_workspace_paths() -> Option<LocalPaths> {
|
||||
DB.last_workspace().await.log_err().flatten()
|
||||
}
|
||||
|
||||
|
@ -4410,7 +4428,6 @@ async fn join_channel_internal(
|
|||
if let Some((project, host)) = room.most_active_project(cx) {
|
||||
return Some(join_in_room_project(project, host, app_state.clone(), cx));
|
||||
}
|
||||
|
||||
// if you are the first to join a channel, share your project
|
||||
if room.remote_participants().len() == 0 && !room.local_participant_is_guest() {
|
||||
if let Some(workspace) = requesting_window {
|
||||
|
@ -4419,7 +4436,7 @@ async fn join_channel_internal(
|
|||
return None;
|
||||
}
|
||||
let project = workspace.project.read(cx);
|
||||
if project.is_local()
|
||||
if (project.is_local() || project.remote_project_id().is_some())
|
||||
&& project.visible_worktrees(cx).any(|tree| {
|
||||
tree.read(cx)
|
||||
.root_entry()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue