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
|
@ -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(),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue