diff --git a/Cargo.lock b/Cargo.lock index e887dfee66..f4998f235a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1572,6 +1572,7 @@ dependencies = [ "sqlez", "tempdir", "util", + "uuid 1.2.2", ] [[package]] @@ -6834,6 +6835,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" dependencies = [ "getrandom 0.2.8", + "rand 0.8.5", ] [[package]] diff --git a/crates/db/Cargo.toml b/crates/db/Cargo.toml index 70721c310c..7e58b2e9bf 100644 --- a/crates/db/Cargo.toml +++ b/crates/db/Cargo.toml @@ -22,6 +22,7 @@ lazy_static = "1.4.0" log = { version = "0.4.16", features = ["kv_unstable_serde"] } parking_lot = "0.11.1" serde = { version = "1.0", features = ["derive"] } +uuid = { version = "1.2.2", features = ["v4", "fast-rng"] } [dev-dependencies] gpui = { path = "../gpui", features = ["test-support"] } diff --git a/crates/db/src/db.rs b/crates/db/src/db.rs index 6e4e6e0619..aa09dc812d 100644 --- a/crates/db/src/db.rs +++ b/crates/db/src/db.rs @@ -1,21 +1,26 @@ pub mod kvp; -// Re-export indoc and sqlez so clients only need to include us +// Re-export +pub use anyhow; pub use indoc::indoc; pub use lazy_static; pub use sqlez; - -use std::fs::{create_dir_all, remove_dir_all}; -use std::path::Path; +use sqlez::bindable::{Bind, Column}; #[cfg(any(test, feature = "test-support"))] use anyhow::Result; #[cfg(any(test, feature = "test-support"))] use sqlez::connection::Connection; -use sqlez::domain::{Domain, Migrator}; +#[cfg(any(test, feature = "test-support"))] +use sqlez::domain::Domain; + +use sqlez::domain::Migrator; use sqlez::thread_safe_connection::ThreadSafeConnection; +use std::fs::{create_dir_all, remove_dir_all}; +use std::path::Path; use util::channel::{ReleaseChannel, RELEASE_CHANNEL, RELEASE_CHANNEL_NAME}; use util::paths::DB_DIR; +use uuid::Uuid as RealUuid; const INITIALIZE_QUERY: &'static str = indoc! {" PRAGMA journal_mode=WAL; @@ -25,6 +30,47 @@ const INITIALIZE_QUERY: &'static str = indoc! {" PRAGMA case_sensitive_like=TRUE; "}; +#[derive(Debug, Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct Uuid(RealUuid); + +impl std::ops::Deref for Uuid { + type Target = RealUuid; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Bind for Uuid { + fn bind( + &self, + statement: &sqlez::statement::Statement, + start_index: i32, + ) -> anyhow::Result { + statement.bind(self.as_bytes(), start_index) + } +} + +impl Column for Uuid { + fn column( + statement: &mut sqlez::statement::Statement, + start_index: i32, + ) -> anyhow::Result<(Self, i32)> { + let blob = statement.column_blob(start_index)?; + Ok((Uuid::from_bytes(blob)?, start_index + 1)) + } +} + +impl Uuid { + pub fn new() -> Self { + Uuid(RealUuid::new_v4()) + } + + fn from_bytes(bytes: &[u8]) -> anyhow::Result { + Ok(Uuid(RealUuid::from_bytes(bytes.try_into()?))) + } +} + /// Open or create a database at the given directory path. pub fn open_file_db() -> ThreadSafeConnection { // Use 0 for now. Will implement incrementing and clearing of old db files soon TM @@ -77,3 +123,88 @@ macro_rules! connection { } }; } + +#[macro_export] +macro_rules! exec_method { + ($id:ident(): $sql:literal) => { + pub fn $id(&self) -> $crate::sqlez::anyhow::Result<()> { + use $crate::anyhow::Context; + + self.exec($sql)?() + .context(::std::format!( + "Error in {}, exec failed to execute or parse for: {}", + ::std::stringify!($id), + ::std::stringify!($sql), + )) + } + }; + ($id:ident($($arg:ident: $arg_type:ty),+): $sql:literal) => { + pub fn $id(&self, $($arg: $arg_type),+) -> $crate::sqlez::anyhow::Result<()> { + use $crate::anyhow::Context; + + self.exec_bound::<($($arg_type),+)>($sql)?(($($arg),+)) + .context(::std::format!( + "Error in {}, exec_bound failed to execute or parse for: {}", + ::std::stringify!($id), + ::std::stringify!($sql), + )) + } + }; +} + +#[macro_export] +macro_rules! select_method { + ($id:ident() -> $return_type:ty: $sql:literal) => { + pub fn $id(&self) -> $crate::sqlez::anyhow::Result> { + use $crate::anyhow::Context; + + self.select::<$return_type>($sql)?(()) + .context(::std::format!( + "Error in {}, select_row failed to execute or parse for: {}", + ::std::stringify!($id), + ::std::stringify!($sql), + )) + } + }; + ($id:ident($($arg:ident: $arg_type:ty),+) -> $return_type:ty: $sql:literal) => { + pub fn $id(&self, $($arg: $arg_type),+) -> $crate::sqlez::anyhow::Result> { + use $crate::anyhow::Context; + + self.select_bound::<($($arg_type),+), $return_type>($sql)?(($($arg),+)) + .context(::std::format!( + "Error in {}, exec_bound failed to execute or parse for: {}", + ::std::stringify!($id), + ::std::stringify!($sql), + )) + } + }; +} + +#[macro_export] +macro_rules! select_row_method { + ($id:ident() -> $return_type:ty: $sql:literal) => { + pub fn $id(&self) -> $crate::sqlez::anyhow::Result> { + use $crate::anyhow::Context; + + self.select_row::<$return_type>($sql)?(()) + .context(::std::format!( + "Error in {}, select_row failed to execute or parse for: {}", + ::std::stringify!($id), + ::std::stringify!($sql), + )) + } + }; + ($id:ident($($arg:ident: $arg_type:ty),+) -> $return_type:ty: $sql:literal) => { + pub fn $id(&self, $($arg: $arg_type),+) -> $crate::sqlez::anyhow::Result> { + use $crate::anyhow::Context; + + self.select_row_bound::<($($arg_type),+), $return_type>($sql)?(($($arg),+)) + .context(::std::format!( + "Error in {}, select_row_bound failed to execute or parse for: {}", + ::std::stringify!($id), + ::std::stringify!($sql), + )) + + } + }; +} diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 639a108724..ef8b81ac66 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -584,7 +584,11 @@ impl Item for ProjectDiagnosticsEditor { }); } - fn clone_on_split(&self, cx: &mut ViewContext) -> Option + fn clone_on_split( + &self, + _workspace_id: workspace::WorkspaceId, + cx: &mut ViewContext, + ) -> Option where Self: Sized, { diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 81cf7a9211..d66fc3e28c 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -83,7 +83,7 @@ use theme::{DiagnosticStyle, Theme}; use util::{post_inc, ResultExt, TryFutureExt}; use workspace::{ItemNavHistory, Workspace}; -use crate::{git::diff_hunk_to_display, persistence::DB}; +use crate::git::diff_hunk_to_display; const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500); const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1); @@ -1137,30 +1137,30 @@ impl Editor { cx: &mut ViewContext, ) -> Self { let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); - if let Some(project) = project.as_ref() { - if let Some(file) = buffer - .read(cx) - .as_singleton() - .and_then(|buffer| buffer.read(cx).file()) - .and_then(|file| file.as_local()) - { - let item_id = cx.weak_handle().id(); - let workspace_id = project - .read(cx) - .visible_worktrees(cx) - .map(|worktree| worktree.read(cx).abs_path()) - .collect::>() - .into(); - let path = file.abs_path(cx); - dbg!(&path); + // if let Some(project) = project.as_ref() { + // if let Some(file) = buffer + // .read(cx) + // .as_singleton() + // .and_then(|buffer| buffer.read(cx).file()) + // .and_then(|file| file.as_local()) + // { + // // let item_id = cx.weak_handle().id(); + // // let workspace_id = project + // // .read(cx) + // // .visible_worktrees(cx) + // // .map(|worktree| worktree.read(cx).abs_path()) + // // .collect::>() + // // .into(); + // let path = file.abs_path(cx); + // dbg!(&path); - cx.background() - .spawn(async move { - DB.save_path(item_id, workspace_id, path).log_err(); - }) - .detach(); - } - } + // // cx.background() + // // .spawn(async move { + // // DB.save_path(item_id, workspace_id, path).log_err(); + // // }) + // // .detach(); + // } + // } Self::new(EditorMode::Full, buffer, project, None, cx) } diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index f7dcd57f42..1e695d2364 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -368,7 +368,7 @@ impl Item for Editor { self.buffer.read(cx).is_singleton() } - fn clone_on_split(&self, cx: &mut ViewContext) -> Option + fn clone_on_split(&self, _workspace_id: WorkspaceId, cx: &mut ViewContext) -> Option where Self: Sized, { @@ -561,14 +561,13 @@ impl Item for Editor { fn deserialize( project: ModelHandle, _workspace: WeakViewHandle, - workspace_id: WorkspaceId, + workspace_id: workspace::WorkspaceId, item_id: ItemId, cx: &mut ViewContext, ) -> Task>> { if let Some(project_item) = project.update(cx, |project, cx| { // Look up the path with this key associated, create a self with that path let path = DB.get_path(item_id, workspace_id).ok()?; - dbg!(&path); let (worktree, path) = project.find_local_worktree(&path, cx)?; let project_path = ProjectPath { worktree_id: worktree.read(cx).id(), diff --git a/crates/editor/src/persistence.rs b/crates/editor/src/persistence.rs index 5870bc71e5..4a44a32447 100644 --- a/crates/editor/src/persistence.rs +++ b/crates/editor/src/persistence.rs @@ -1,7 +1,7 @@ use std::path::{Path, PathBuf}; use anyhow::{Context, Result}; -use db::connection; +use db::{connection, exec_method}; use indoc::indoc; use sqlez::domain::Domain; use workspace::{ItemId, Workspace, WorkspaceId}; @@ -35,18 +35,12 @@ impl EditorDb { pub fn get_path(&self, item_id: ItemId, workspace_id: WorkspaceId) -> Result { self.select_row_bound(indoc! {" SELECT path FROM editors - WHERE item_id = ? AND workspace_id = ?"})?((item_id, &workspace_id))? + WHERE item_id = ? AND workspace_id = ?"})?((item_id, workspace_id))? .context("Path not found for serialized editor") } - pub fn save_path( - &self, - item_id: ItemId, - workspace_id: WorkspaceId, - path: PathBuf, - ) -> Result<()> { - self.exec_bound::<(ItemId, &WorkspaceId, &Path)>(indoc! {" - INSERT OR REPLACE INTO editors(item_id, workspace_id, path) - VALUES (?, ?, ?)"})?((item_id, &workspace_id, &path)) - } + exec_method!(save_path(item_id: ItemId, workspace_id: WorkspaceId, path: &Path): + "INSERT OR REPLACE INTO editors(item_id, workspace_id, path) + VALUES (?, ?, ?)" + ); } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 322d035870..6fa7d07d6f 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -26,7 +26,7 @@ use util::ResultExt as _; use workspace::{ item::{Item, ItemEvent, ItemHandle}, searchable::{Direction, SearchableItem, SearchableItemHandle}, - ItemNavHistory, Pane, ToolbarItemLocation, ToolbarItemView, Workspace, + ItemNavHistory, Pane, ToolbarItemLocation, ToolbarItemView, Workspace, WorkspaceId, }; actions!(project_search, [SearchInNew, ToggleFocus]); @@ -315,7 +315,7 @@ impl Item for ProjectSearchView { .update(cx, |editor, cx| editor.reload(project, cx)) } - fn clone_on_split(&self, cx: &mut ViewContext) -> Option + fn clone_on_split(&self, _workspace_id: WorkspaceId, cx: &mut ViewContext) -> Option where Self: Sized, { diff --git a/crates/sqlez/src/bindable.rs b/crates/sqlez/src/bindable.rs index 1e4f0df33f..18c4acedad 100644 --- a/crates/sqlez/src/bindable.rs +++ b/crates/sqlez/src/bindable.rs @@ -36,6 +36,13 @@ impl Bind for &[u8] { } } +impl Bind for &[u8; C] { + fn bind(&self, statement: &Statement, start_index: i32) -> Result { + statement.bind_blob(start_index, self.as_slice())?; + Ok(start_index + 1) + } +} + impl Bind for Vec { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement.bind_blob(start_index, self)?; diff --git a/crates/sqlez/src/typed_statements.rs b/crates/sqlez/src/typed_statements.rs index ce289437c2..98f51b970a 100644 --- a/crates/sqlez/src/typed_statements.rs +++ b/crates/sqlez/src/typed_statements.rs @@ -52,57 +52,3 @@ impl Connection { Ok(move |bindings| statement.with_bindings(bindings)?.maybe_row::()) } } - -#[macro_export] -macro_rules! exec_method { - ($id:ident(): $sql:literal) => { - pub fn $id(&self) -> $crate::anyhow::Result<()> { - iife!({ - self.exec($sql)?() - }) - } - }; - ($id:ident($($arg:ident: $arg_type:ty),+): $sql:literal) => { - pub fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<()> { - iife!({ - self.exec_bound::<($($arg_type),+)>($sql)?(($($arg),+)) - }) - } - }; -} - -#[macro_export] -macro_rules! select_method { - ($id:ident() -> $return_type:ty: $sql:literal) => { - pub fn $id(&self) -> $crate::anyhow::Result> { - iife!({ - self.select::<$return_type>($sql)?(()) - }) - } - }; - ($id:ident($($arg:ident: $arg_type:ty),+) -> $return_type:ty: $sql:literal) => { - pub fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result> { - iife!({ - self.exec_bound::<($($arg_type),+), $return_type>($sql)?(($($arg),+)) - }) - } - }; -} - -#[macro_export] -macro_rules! select_row_method { - ($id:ident() -> $return_type:ty: $sql:literal) => { - pub fn $id(&self) -> $crate::anyhow::Result> { - iife!({ - self.select_row::<$return_type>($sql)?(()) - }) - } - }; - ($id:ident($($arg:ident: $arg_type:ty),+) -> $return_type:ty: $sql:literal) => { - pub fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result> { - iife!({ - self.select_row_bound::<($($arg_type),+), $return_type>($sql)?(($($arg),+)) - }) - } - }; -} diff --git a/crates/terminal/src/persistence.rs b/crates/terminal/src/persistence.rs index 9c72105530..8928164676 100644 --- a/crates/terminal/src/persistence.rs +++ b/crates/terminal/src/persistence.rs @@ -1,10 +1,7 @@ use std::path::{Path, PathBuf}; -use db::{ - connection, indoc, - sqlez::{domain::Domain, exec_method, select_row_method}, -}; -use util::iife; +use db::{connection, exec_method, indoc, select_row_method, sqlez::domain::Domain}; + use workspace::{ItemId, Workspace, WorkspaceId}; use crate::Terminal; @@ -19,13 +16,12 @@ impl Domain for Terminal { fn migrations() -> &'static [&'static str] { &[indoc! {" CREATE TABLE terminals ( - item_id INTEGER, workspace_id BLOB, + item_id INTEGER, working_directory BLOB, - PRIMARY KEY(item_id, workspace_id), + PRIMARY KEY(workspace_id, item_id), FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) ON DELETE CASCADE - ON UPDATE CASCADE ) STRICT; "}] } @@ -33,15 +29,15 @@ impl Domain for Terminal { impl TerminalDb { exec_method!( - save_working_directory(item_id: ItemId, workspace_id: &WorkspaceId, working_directory: &Path): + save_working_directory(model_id: ItemId, workspace_id: WorkspaceId, working_directory: &Path): "INSERT OR REPLACE INTO terminals(item_id, workspace_id, working_directory) - VALUES (?, ?, ?)" + VALUES (?1, ?2, ?3)" ); select_row_method!( - get_working_directory(item_id: ItemId, workspace_id: &WorkspaceId) -> PathBuf: + get_working_directory(item_id: ItemId, workspace_id: WorkspaceId) -> PathBuf: "SELECT working_directory FROM terminals - WHERE item_id = ? workspace_id = ?" + WHERE item_id = ? AND workspace_id = ?" ); } diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 1c564afc63..fdf16b7825 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -33,9 +33,11 @@ use mappings::mouse::{ alt_scroll, grid_point, mouse_button_report, mouse_moved_report, mouse_side, scroll_report, }; +use persistence::TERMINAL_CONNECTION; use procinfo::LocalProcessInfo; use settings::{AlternateScroll, Settings, Shell, TerminalBlink}; use util::ResultExt; +use workspace::{ItemId, WorkspaceId}; use std::{ cmp::min, @@ -282,6 +284,8 @@ impl TerminalBuilder { blink_settings: Option, alternate_scroll: &AlternateScroll, window_id: usize, + item_id: ItemId, + workspace_id: WorkspaceId, ) -> Result { let pty_config = { let alac_shell = shell.clone().and_then(|shell| match shell { @@ -386,6 +390,8 @@ impl TerminalBuilder { last_mouse_position: None, next_link_id: 0, selection_phase: SelectionPhase::Ended, + workspace_id, + item_id, }; Ok(TerminalBuilder { @@ -529,6 +535,8 @@ pub struct Terminal { scroll_px: f32, next_link_id: usize, selection_phase: SelectionPhase, + workspace_id: WorkspaceId, + item_id: ItemId, } impl Terminal { @@ -566,20 +574,6 @@ impl Terminal { } AlacTermEvent::Wakeup => { cx.emit(Event::Wakeup); - - if self.update_process_info() { - cx.emit(Event::TitleChanged); - - // if let Some(foreground_info) = self.foreground_process_info { - // cx.background().spawn(async move { - // TERMINAL_CONNECTION.save_working_directory( - // self.item_id, - // &self.workspace_id, - // &foreground_info.cwd, - // ); - // }); - // } - } } AlacTermEvent::ColorRequest(idx, fun_ptr) => { self.events @@ -888,6 +882,19 @@ impl Terminal { if self.update_process_info() { cx.emit(Event::TitleChanged); + + if let Some(foreground_info) = &self.foreground_process_info { + let cwd = foreground_info.cwd.clone(); + let item_id = self.item_id; + let workspace_id = self.workspace_id; + cx.background() + .spawn(async move { + TERMINAL_CONNECTION + .save_working_directory(item_id, workspace_id, cwd.as_path()) + .log_err(); + }) + .detach(); + } } //Note that the ordering of events matters for event processing diff --git a/crates/terminal/src/terminal_container_view.rs b/crates/terminal/src/terminal_container_view.rs index 88d4862bdc..fdda388642 100644 --- a/crates/terminal/src/terminal_container_view.rs +++ b/crates/terminal/src/terminal_container_view.rs @@ -1,6 +1,6 @@ use crate::persistence::TERMINAL_CONNECTION; use crate::terminal_view::TerminalView; -use crate::{Event, Terminal, TerminalBuilder, TerminalError}; +use crate::{Event, TerminalBuilder, TerminalError}; use alacritty_terminal::index::Point; use dirs::home_dir; @@ -14,7 +14,7 @@ use workspace::{ item::{Item, ItemEvent}, ToolbarItemLocation, Workspace, }; -use workspace::{register_deserializable_item, Pane}; +use workspace::{register_deserializable_item, Pane, WorkspaceId}; use project::{LocalWorktree, Project, ProjectPath}; use settings::{AlternateScroll, Settings, WorkingDirectory}; @@ -82,7 +82,9 @@ impl TerminalContainer { .unwrap_or(WorkingDirectory::CurrentProjectDirectory); let working_directory = get_working_directory(workspace, cx, strategy); - let view = cx.add_view(|cx| TerminalContainer::new(working_directory, false, cx)); + let view = cx.add_view(|cx| { + TerminalContainer::new(working_directory, false, workspace.database_id(), cx) + }); workspace.add_item(Box::new(view), cx); } @@ -90,6 +92,7 @@ impl TerminalContainer { pub fn new( working_directory: Option, modal: bool, + workspace_id: WorkspaceId, cx: &mut ViewContext, ) -> Self { let settings = cx.global::(); @@ -116,10 +119,13 @@ impl TerminalContainer { settings.terminal_overrides.blinking.clone(), scroll, cx.window_id(), + cx.view_id(), + workspace_id, ) { Ok(terminal) => { let terminal = cx.add_model(|cx| terminal.subscribe(cx)); let view = cx.add_view(|cx| TerminalView::from_terminal(terminal, modal, cx)); + cx.subscribe(&view, |_this, _content, event, cx| cx.emit(*event)) .detach(); TerminalContainerContent::Connected(view) @@ -139,18 +145,6 @@ impl TerminalContainer { } } - pub fn from_terminal( - terminal: ModelHandle, - modal: bool, - cx: &mut ViewContext, - ) -> Self { - let connected_view = cx.add_view(|cx| TerminalView::from_terminal(terminal, modal, cx)); - TerminalContainer { - content: TerminalContainerContent::Connected(connected_view), - associated_directory: None, - } - } - fn connected(&self) -> Option> { match &self.content { TerminalContainerContent::Connected(vh) => Some(vh.clone()), @@ -278,13 +272,18 @@ impl Item for TerminalContainer { .boxed() } - fn clone_on_split(&self, cx: &mut ViewContext) -> Option { + fn clone_on_split( + &self, + workspace_id: WorkspaceId, + cx: &mut ViewContext, + ) -> Option { //From what I can tell, there's no way to tell the current working //Directory of the terminal from outside the shell. There might be //solutions to this, but they are non-trivial and require more IPC Some(TerminalContainer::new( self.associated_directory.clone(), false, + workspace_id, cx, )) } @@ -391,9 +390,14 @@ impl Item for TerminalContainer { item_id: workspace::ItemId, cx: &mut ViewContext, ) -> Task>> { - let working_directory = TERMINAL_CONNECTION.get_working_directory(item_id, &workspace_id); + let working_directory = TERMINAL_CONNECTION.get_working_directory(item_id, workspace_id); Task::ready(Ok(cx.add_view(|cx| { - TerminalContainer::new(working_directory.log_err().flatten(), false, cx) + TerminalContainer::new( + working_directory.log_err().flatten(), + false, + workspace_id, + cx, + ) }))) } } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 2e4fbcad6f..fb28571172 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -206,7 +206,7 @@ impl Dock { cx.focus(last_active_center_pane); } cx.emit(crate::Event::DockAnchorChanged); - workspace.serialize_workspace(None, cx); + workspace.serialize_workspace(cx); cx.notify(); } diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index d006f2fe15..b990ba20a2 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -22,11 +22,8 @@ use theme::Theme; use util::ResultExt; use crate::{ - pane, - persistence::model::{ItemId, WorkspaceId}, - searchable::SearchableItemHandle, - DelayedDebouncedEditAction, FollowableItemBuilders, ItemNavHistory, Pane, ToolbarItemLocation, - Workspace, + pane, persistence::model::ItemId, searchable::SearchableItemHandle, DelayedDebouncedEditAction, + FollowableItemBuilders, ItemNavHistory, Pane, ToolbarItemLocation, Workspace, WorkspaceId, }; #[derive(Eq, PartialEq, Hash)] @@ -52,7 +49,7 @@ pub trait Item: View { fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>; fn is_singleton(&self, cx: &AppContext) -> bool; fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext); - fn clone_on_split(&self, _: &mut ViewContext) -> Option + fn clone_on_split(&self, _workspace_id: WorkspaceId, _: &mut ViewContext) -> Option where Self: Sized, { @@ -121,7 +118,9 @@ pub trait Item: View { fn breadcrumbs(&self, _theme: &Theme, _cx: &AppContext) -> Option> { None } + fn serialized_item_kind() -> Option<&'static str>; + fn deserialize( project: ModelHandle, workspace: WeakViewHandle, @@ -144,7 +143,11 @@ pub trait ItemHandle: 'static + fmt::Debug { fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>; fn is_singleton(&self, cx: &AppContext) -> bool; fn boxed_clone(&self) -> Box; - fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option>; + fn clone_on_split( + &self, + workspace_id: WorkspaceId, + cx: &mut MutableAppContext, + ) -> Option>; fn added_to_pane( &self, workspace: &mut Workspace, @@ -246,9 +249,13 @@ impl ItemHandle for ViewHandle { Box::new(self.clone()) } - fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option> { + fn clone_on_split( + &self, + workspace_id: WorkspaceId, + cx: &mut MutableAppContext, + ) -> Option> { self.update(cx, |item, cx| { - cx.add_option_view(|cx| item.clone_on_split(cx)) + cx.add_option_view(|cx| item.clone_on_split(workspace_id, cx)) }) .map(|handle| Box::new(handle) as Box) } @@ -812,7 +819,11 @@ pub(crate) mod test { self.push_to_nav_history(cx); } - fn clone_on_split(&self, _: &mut ViewContext) -> Option + fn clone_on_split( + &self, + _workspace_id: WorkspaceId, + _: &mut ViewContext, + ) -> Option where Self: Sized, { diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index 372c4cafce..bd59afd497 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -2,39 +2,38 @@ pub mod model; -use std::path::{Path, PathBuf}; -use std::sync::Arc; +use std::path::Path; -use anyhow::{anyhow, bail, Result, Context}; +use anyhow::{anyhow, bail, Context, Result}; use db::connection; use gpui::Axis; use indoc::indoc; - use db::sqlez::domain::Domain; use util::{iife, unzip_option, ResultExt}; use crate::dock::DockPosition; +use crate::WorkspaceId; use super::Workspace; use model::{ - GroupId, PaneId, SerializedItem, SerializedPane, SerializedPaneGroup, - SerializedWorkspace, WorkspaceId, + GroupId, PaneId, SerializedItem, SerializedPane, SerializedPaneGroup, SerializedWorkspace, + WorkspaceLocation, }; connection!(DB: WorkspaceDb); - impl Domain for Workspace { fn name() -> &'static str { "workspace" } - + fn migrations() -> &'static [&'static str] { &[indoc! {" CREATE TABLE workspaces( workspace_id BLOB PRIMARY KEY, + workspace_location BLOB NOT NULL UNIQUE, dock_visible INTEGER, -- Boolean dock_anchor TEXT, -- Enum: 'Bottom' / 'Right' / 'Expanded' dock_pane INTEGER, -- NULL indicates that we don't have a dock pane yet @@ -97,21 +96,25 @@ impl WorkspaceDb { &self, worktree_roots: &[P], ) -> Option { - let workspace_id: WorkspaceId = worktree_roots.into(); + let workspace_location: WorkspaceLocation = worktree_roots.into(); // 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, dock_position): (WorkspaceId, DockPosition) = iife!({ + let (workspace_id, workspace_location, dock_position): ( + WorkspaceId, + WorkspaceLocation, + DockPosition, + ) = iife!({ if worktree_roots.len() == 0 { self.select_row(indoc! {" - SELECT workspace_id, dock_visible, dock_anchor + SELECT workspace_id, workspace_location, dock_visible, dock_anchor FROM workspaces ORDER BY timestamp DESC LIMIT 1"})?()? } else { self.select_row_bound(indoc! {" - SELECT workspace_id, dock_visible, dock_anchor + SELECT workspace_id, workspace_location, dock_visible, dock_anchor FROM workspaces - WHERE workspace_id = ?"})?(&workspace_id)? + WHERE workspace_location = ?"})?(&workspace_location)? } .context("No workspaces found") }) @@ -119,13 +122,14 @@ impl WorkspaceDb { .flatten()?; Some(SerializedWorkspace { - workspace_id: workspace_id.clone(), + id: workspace_id, + location: workspace_location.clone(), dock_pane: self - .get_dock_pane(&workspace_id) + .get_dock_pane(workspace_id) .context("Getting dock pane") .log_err()?, center_group: self - .get_center_pane_group(&workspace_id) + .get_center_pane_group(workspace_id) .context("Getting center group") .log_err()?, dock_position, @@ -134,72 +138,61 @@ impl WorkspaceDb { /// Saves a workspace using the worktree roots. Will garbage collect any workspaces /// that used this workspace previously - pub fn save_workspace( - &self, - old_id: Option, - workspace: &SerializedWorkspace, - ) { + pub fn save_workspace(&self, workspace: &SerializedWorkspace) { self.with_savepoint("update_worktrees", || { + // Clear out panes and pane_groups self.exec_bound(indoc! {" UPDATE workspaces SET dock_pane = NULL WHERE workspace_id = ?1; DELETE FROM pane_groups WHERE workspace_id = ?1; - DELETE FROM panes WHERE workspace_id = ?1;"})? - (old_id.as_ref().unwrap_or(&workspace.workspace_id)).context("Clearing old panes")?; - - if let Some(old_id) = old_id { - self.exec_bound(indoc! {" - UPDATE OR REPLACE workspaces - SET workspace_id = ?, - dock_visible = ?, - dock_anchor = ?, - timestamp = CURRENT_TIMESTAMP - WHERE workspace_id = ?"})?(( - &workspace.workspace_id, - workspace.dock_position, - &old_id, - )).context("Updating workspace with new worktree roots")?; - } else { - self.exec_bound( - "INSERT OR REPLACE INTO workspaces(workspace_id, dock_visible, dock_anchor) VALUES (?, ?, ?)", - )?((&workspace.workspace_id, workspace.dock_position)).context("Uodating workspace")?; - } - + DELETE FROM panes WHERE workspace_id = ?1;"})?(workspace.id) + .context("Clearing old panes")?; + + // Update or insert + self.exec_bound(indoc! { + "INSERT OR REPLACE INTO + workspaces(workspace_id, workspace_location, dock_visible, dock_anchor, timestamp) + VALUES + (?1, ?2, ?3, ?4, CURRENT_TIMESTAMP)" + })?((workspace.id, &workspace.location, workspace.dock_position)) + .context("Updating workspace")?; + // Save center pane group and dock pane - self.save_pane_group(&workspace.workspace_id, &workspace.center_group, None).context("save pane group in save workspace")?; - - let dock_id = self.save_pane(&workspace.workspace_id, &workspace.dock_pane, None, true).context("save pane in save workspace")?; - + self.save_pane_group(workspace.id, &workspace.center_group, None) + .context("save pane group in save workspace")?; + + let dock_id = self + .save_pane(workspace.id, &workspace.dock_pane, None, true) + .context("save pane in save workspace")?; + // Complete workspace initialization self.exec_bound(indoc! {" UPDATE workspaces SET dock_pane = ? - WHERE workspace_id = ?"})?(( - dock_id, - &workspace.workspace_id, - )).context("Finishing initialization with dock pane")?; + WHERE workspace_id = ?"})?((dock_id, workspace.id)) + .context("Finishing initialization with dock pane")?; Ok(()) }) .with_context(|| { format!( - "Update workspace with roots {:?} failed.", - workspace.workspace_id.paths() + "Update workspace with roots {:?} and id {:?} failed.", + workspace.location.paths(), + workspace.id ) }) .log_err(); } /// Returns the previous workspace ids sorted by last modified along with their opened worktree roots - pub fn recent_workspaces(&self, limit: usize) -> Vec>> { + pub fn recent_workspaces(&self, limit: usize) -> Vec<(WorkspaceId, WorkspaceLocation)> { iife!({ // TODO, upgrade anyhow: https://docs.rs/anyhow/1.0.66/anyhow/fn.Ok.html Ok::<_, anyhow::Error>( - self.select_bound::( - "SELECT workspace_id FROM workspaces ORDER BY timestamp DESC LIMIT ?", + self.select_bound::( + "SELECT workspace_id, workspace_location FROM workspaces ORDER BY timestamp DESC LIMIT ?", )?(limit)? .into_iter() - .map(|id| id.paths()) - .collect::>>>(), + .collect::>(), ) }) .log_err() @@ -208,7 +201,7 @@ impl WorkspaceDb { pub(crate) fn get_center_pane_group( &self, - workspace_id: &WorkspaceId, + workspace_id: WorkspaceId, ) -> Result { self.get_pane_group(workspace_id, None)? .into_iter() @@ -218,10 +211,10 @@ impl WorkspaceDb { fn get_pane_group( &self, - workspace_id: &WorkspaceId, + workspace_id: WorkspaceId, group_id: Option, ) -> Result> { - type GroupKey<'a> = (Option, &'a WorkspaceId); + type GroupKey = (Option, WorkspaceId); type GroupOrPane = (Option, Option, Option, Option); self.select_bound::(indoc! {" SELECT group_id, axis, pane_id, active @@ -253,31 +246,29 @@ impl WorkspaceDb { if let Some((group_id, axis)) = group_id.zip(axis) { Ok(SerializedPaneGroup::Group { axis, - children: self.get_pane_group( - workspace_id, - Some(group_id), - )?, + children: self.get_pane_group(workspace_id, Some(group_id))?, }) } else if let Some((pane_id, active)) = pane_id.zip(active) { - Ok(SerializedPaneGroup::Pane(SerializedPane::new(self.get_items( pane_id)?, active))) + Ok(SerializedPaneGroup::Pane(SerializedPane::new( + self.get_items(pane_id)?, + active, + ))) } else { bail!("Pane Group Child was neither a pane group or a pane"); } }) // Filter out panes and pane groups which don't have any children or items - .filter(|pane_group| { - match pane_group { - Ok(SerializedPaneGroup::Group { children, .. }) => !children.is_empty(), - Ok(SerializedPaneGroup::Pane(pane)) => !pane.children.is_empty(), - _ => true, - } + .filter(|pane_group| match pane_group { + Ok(SerializedPaneGroup::Group { children, .. }) => !children.is_empty(), + Ok(SerializedPaneGroup::Pane(pane)) => !pane.children.is_empty(), + _ => true, }) .collect::>() } pub(crate) fn save_pane_group( &self, - workspace_id: &WorkspaceId, + workspace_id: WorkspaceId, pane_group: &SerializedPaneGroup, parent: Option<(GroupId, usize)>, ) -> Result<()> { @@ -285,26 +276,31 @@ impl WorkspaceDb { SerializedPaneGroup::Group { axis, children } => { let (parent_id, position) = unzip_option(parent); - let group_id = self.select_row_bound::<_, i64>(indoc!{" + let group_id = self.select_row_bound::<_, i64>(indoc! {" INSERT INTO pane_groups(workspace_id, parent_group_id, position, axis) VALUES (?, ?, ?, ?) - RETURNING group_id"})? - ((workspace_id, parent_id, position, *axis))? - .ok_or_else(|| anyhow!("Couldn't retrieve group_id from inserted pane_group"))?; - + RETURNING group_id"})?(( + workspace_id, + parent_id, + position, + *axis, + ))? + .ok_or_else(|| anyhow!("Couldn't retrieve group_id from inserted pane_group"))?; + for (position, group) in children.iter().enumerate() { self.save_pane_group(workspace_id, group, Some((group_id, position)))? } + Ok(()) } SerializedPaneGroup::Pane(pane) => { self.save_pane(workspace_id, &pane, parent, false)?; Ok(()) - }, + } } } - pub(crate) fn get_dock_pane(&self, workspace_id: &WorkspaceId) -> Result { + pub(crate) fn get_dock_pane(&self, workspace_id: WorkspaceId) -> Result { let (pane_id, active) = self.select_row_bound(indoc! {" SELECT pane_id, active FROM panes @@ -315,40 +311,35 @@ impl WorkspaceDb { Ok(SerializedPane::new( self.get_items(pane_id).context("Reading items")?, - active + active, )) } pub(crate) fn save_pane( &self, - workspace_id: &WorkspaceId, + workspace_id: WorkspaceId, pane: &SerializedPane, parent: Option<(GroupId, usize)>, // None indicates BOTH dock pane AND center_pane dock: bool, ) -> Result { - let pane_id = self.select_row_bound::<_, i64>(indoc!{" + let pane_id = self.select_row_bound::<_, i64>(indoc! {" INSERT INTO panes(workspace_id, active) VALUES (?, ?) - RETURNING pane_id"}, - )?((workspace_id, pane.active))? + RETURNING pane_id"})?((workspace_id, pane.active))? .ok_or_else(|| anyhow!("Could not retrieve inserted pane_id"))?; - + if !dock { let (parent_id, order) = unzip_option(parent); self.exec_bound(indoc! {" INSERT INTO center_panes(pane_id, parent_group_id, position) - VALUES (?, ?, ?)"})?(( - pane_id, parent_id, order - ))?; + VALUES (?, ?, ?)"})?((pane_id, parent_id, order))?; } self.save_items(workspace_id, pane_id, &pane.children) .context("Saving items")?; - + Ok(pane_id) } - - pub(crate) fn get_items(&self, pane_id: PaneId) -> Result> { Ok(self.select_bound(indoc! {" @@ -359,7 +350,7 @@ impl WorkspaceDb { pub(crate) fn save_items( &self, - workspace_id: &WorkspaceId, + workspace_id: WorkspaceId, pane_id: PaneId, items: &[SerializedItem], ) -> Result<()> { @@ -376,7 +367,8 @@ impl WorkspaceDb { #[cfg(test)] mod tests { - use db::{open_memory_db}; + + use db::{open_memory_db, Uuid}; use settings::DockAnchor; use super::*; @@ -388,15 +380,13 @@ mod tests { let db = WorkspaceDb(open_memory_db(Some("test_full_workspace_serialization"))); let dock_pane = crate::persistence::model::SerializedPane { - children: vec![ SerializedItem::new("Terminal", 1), SerializedItem::new("Terminal", 2), SerializedItem::new("Terminal", 3), SerializedItem::new("Terminal", 4), - ], - active: false + active: false, }; // ----------------- @@ -415,8 +405,8 @@ mod tests { SerializedItem::new("Terminal", 5), SerializedItem::new("Terminal", 6), ], - false) - ), + false, + )), SerializedPaneGroup::Pane(SerializedPane::new( vec![ SerializedItem::new("Terminal", 7), @@ -430,7 +420,6 @@ mod tests { vec![ SerializedItem::new("Terminal", 9), SerializedItem::new("Terminal", 10), - ], false, )), @@ -438,25 +427,24 @@ mod tests { }; let workspace = SerializedWorkspace { - workspace_id: (["/tmp", "/tmp2"]).into(), - dock_position: DockPosition::Shown(DockAnchor::Bottom), + id: Uuid::new(), + location: (["/tmp", "/tmp2"]).into(), + dock_position: DockPosition::Shown(DockAnchor::Bottom), center_group, dock_pane, }; - - db.save_workspace(None, &workspace); + + db.save_workspace(&workspace); let round_trip_workspace = db.workspace_for_roots(&["/tmp2", "/tmp"]); - + assert_eq!(workspace, round_trip_workspace.unwrap()); // Test guaranteed duplicate IDs - db.save_workspace(None, &workspace); - db.save_workspace(None, &workspace); - + db.save_workspace(&workspace); + db.save_workspace(&workspace); + let round_trip_workspace = db.workspace_for_roots(&["/tmp", "/tmp2"]); assert_eq!(workspace, round_trip_workspace.unwrap()); - - } #[test] @@ -466,21 +454,23 @@ mod tests { let db = WorkspaceDb(open_memory_db(Some("test_basic_functionality"))); let workspace_1 = SerializedWorkspace { - workspace_id: (["/tmp", "/tmp2"]).into(), + id: WorkspaceId::new(), + location: (["/tmp", "/tmp2"]).into(), dock_position: crate::dock::DockPosition::Shown(DockAnchor::Bottom), center_group: Default::default(), dock_pane: Default::default(), }; let mut workspace_2 = SerializedWorkspace { - workspace_id: (["/tmp"]).into(), + id: WorkspaceId::new(), + location: (["/tmp"]).into(), dock_position: crate::dock::DockPosition::Hidden(DockAnchor::Expanded), center_group: Default::default(), dock_pane: Default::default(), }; - db.save_workspace(None, &workspace_1); - db.save_workspace(None, &workspace_2); + db.save_workspace(&workspace_1); + db.save_workspace(&workspace_2); // Test that paths are treated as a set assert_eq!( @@ -497,8 +487,9 @@ mod tests { assert_eq!(db.workspace_for_roots(&["/tmp3", "/tmp2", "/tmp4"]), None); // Test 'mutate' case of updating a pre-existing id - workspace_2.workspace_id = (["/tmp", "/tmp2"]).into(); - db.save_workspace(Some((&["/tmp"]).into()), &workspace_2); + workspace_2.location = (["/tmp", "/tmp2"]).into(); + + db.save_workspace(&workspace_2); assert_eq!( db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(), workspace_2 @@ -506,33 +497,28 @@ mod tests { // Test other mechanism for mutating let mut workspace_3 = SerializedWorkspace { - workspace_id: (&["/tmp", "/tmp2"]).into(), + id: WorkspaceId::new(), + location: (&["/tmp", "/tmp2"]).into(), dock_position: DockPosition::Shown(DockAnchor::Right), center_group: Default::default(), dock_pane: Default::default(), }; - - db.save_workspace(None, &workspace_3); + db.save_workspace(&workspace_3); assert_eq!( db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(), workspace_3 ); // Make sure that updating paths differently also works - workspace_3.workspace_id = (["/tmp3", "/tmp4", "/tmp2"]).into(); - db.save_workspace( - Some((&["/tmp", "/tmp2"]).into()), - &workspace_3, - ); + workspace_3.location = (["/tmp3", "/tmp4", "/tmp2"]).into(); + db.save_workspace(&workspace_3); assert_eq!(db.workspace_for_roots(&["/tmp2", "tmp"]), None); assert_eq!( db.workspace_for_roots(&["/tmp2", "/tmp3", "/tmp4"]) .unwrap(), workspace_3 ); - - } use crate::dock::DockPosition; @@ -545,7 +531,8 @@ mod tests { center_group: &SerializedPaneGroup, ) -> SerializedWorkspace { SerializedWorkspace { - workspace_id: workspace_id.into(), + id: WorkspaceId::new(), + location: workspace_id.into(), dock_position: crate::dock::DockPosition::Hidden(DockAnchor::Right), center_group: center_group.clone(), dock_pane, @@ -564,12 +551,13 @@ mod tests { SerializedItem::new("Terminal", 4), SerializedItem::new("Terminal", 2), SerializedItem::new("Terminal", 3), - ], false + ], + false, ); let workspace = default_workspace(&["/tmp"], dock_pane, &Default::default()); - db.save_workspace(None, &workspace); + db.save_workspace(&workspace); let new_workspace = db.workspace_for_roots(&["/tmp"]).unwrap(); @@ -593,16 +581,20 @@ mod tests { SerializedPaneGroup::Group { axis: gpui::Axis::Vertical, children: vec![ - SerializedPaneGroup::Pane(SerializedPane::new( - vec![ - SerializedItem::new("Terminal", 1), - SerializedItem::new("Terminal", 2), - ], - false)), - SerializedPaneGroup::Pane(SerializedPane::new(vec![ - SerializedItem::new("Terminal", 4), - SerializedItem::new("Terminal", 3), - ], true)), + SerializedPaneGroup::Pane(SerializedPane::new( + vec![ + SerializedItem::new("Terminal", 1), + SerializedItem::new("Terminal", 2), + ], + false, + )), + SerializedPaneGroup::Pane(SerializedPane::new( + vec![ + SerializedItem::new("Terminal", 4), + SerializedItem::new("Terminal", 3), + ], + true, + )), ], }, SerializedPaneGroup::Pane(SerializedPane::new( @@ -610,41 +602,46 @@ mod tests { SerializedItem::new("Terminal", 5), SerializedItem::new("Terminal", 6), ], - false)), + false, + )), ], }; let workspace = default_workspace(&["/tmp"], Default::default(), ¢er_pane); - db.save_workspace(None, &workspace); - + db.save_workspace(&workspace); + let new_workspace = db.workspace_for_roots(&["/tmp"]).unwrap(); assert_eq!(workspace.center_group, new_workspace.center_group); } - + #[test] fn test_cleanup_panes() { env_logger::try_init().ok(); - + let db = WorkspaceDb(open_memory_db(Some("test_cleanup_panes"))); - + let center_pane = SerializedPaneGroup::Group { axis: gpui::Axis::Horizontal, children: vec![ SerializedPaneGroup::Group { axis: gpui::Axis::Vertical, children: vec![ - SerializedPaneGroup::Pane(SerializedPane::new( - vec![ - SerializedItem::new("Terminal", 1), - SerializedItem::new("Terminal", 2), - ], - false)), - SerializedPaneGroup::Pane(SerializedPane::new(vec![ - SerializedItem::new("Terminal", 4), - SerializedItem::new("Terminal", 3), - ], true)), + SerializedPaneGroup::Pane(SerializedPane::new( + vec![ + SerializedItem::new("Terminal", 1), + SerializedItem::new("Terminal", 2), + ], + false, + )), + SerializedPaneGroup::Pane(SerializedPane::new( + vec![ + SerializedItem::new("Terminal", 4), + SerializedItem::new("Terminal", 3), + ], + true, + )), ], }, SerializedPaneGroup::Pane(SerializedPane::new( @@ -652,37 +649,41 @@ mod tests { SerializedItem::new("Terminal", 5), SerializedItem::new("Terminal", 6), ], - false)), + false, + )), ], }; let id = &["/tmp"]; - + let mut workspace = default_workspace(id, Default::default(), ¢er_pane); - db.save_workspace(None, &workspace); - + db.save_workspace(&workspace); + workspace.center_group = SerializedPaneGroup::Group { axis: gpui::Axis::Vertical, children: vec![ - SerializedPaneGroup::Pane(SerializedPane::new( - vec![ - SerializedItem::new("Terminal", 1), - SerializedItem::new("Terminal", 2), - ], - false)), - SerializedPaneGroup::Pane(SerializedPane::new(vec![ - SerializedItem::new("Terminal", 4), - SerializedItem::new("Terminal", 3), - ], true)), + SerializedPaneGroup::Pane(SerializedPane::new( + vec![ + SerializedItem::new("Terminal", 1), + SerializedItem::new("Terminal", 2), + ], + false, + )), + SerializedPaneGroup::Pane(SerializedPane::new( + vec![ + SerializedItem::new("Terminal", 4), + SerializedItem::new("Terminal", 3), + ], + true, + )), ], }; - - db.save_workspace(None, &workspace); - + + db.save_workspace(&workspace); + let new_workspace = db.workspace_for_roots(id).unwrap(); assert_eq!(workspace.center_group, new_workspace.center_group); - } } diff --git a/crates/workspace/src/persistence/model.rs b/crates/workspace/src/persistence/model.rs index 5f046d76ee..ff8be51406 100644 --- a/crates/workspace/src/persistence/model.rs +++ b/crates/workspace/src/persistence/model.rs @@ -16,18 +16,20 @@ use project::Project; use settings::DockAnchor; use util::ResultExt; -use crate::{dock::DockPosition, ItemDeserializers, Member, Pane, PaneAxis, Workspace}; +use crate::{ + dock::DockPosition, ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId, +}; #[derive(Debug, Clone, PartialEq, Eq)] -pub struct WorkspaceId(Arc>); +pub struct WorkspaceLocation(Arc>); -impl WorkspaceId { +impl WorkspaceLocation { pub fn paths(&self) -> Arc> { self.0.clone() } } -impl, T: IntoIterator> From for WorkspaceId { +impl, T: IntoIterator> From for WorkspaceLocation { fn from(iterator: T) -> Self { let mut roots = iterator .into_iter() @@ -38,7 +40,7 @@ impl, T: IntoIterator> From for WorkspaceId { } } -impl Bind for &WorkspaceId { +impl Bind for &WorkspaceLocation { fn bind(&self, statement: &Statement, start_index: i32) -> Result { bincode::serialize(&self.0) .expect("Bincode serialization of paths should not fail") @@ -46,16 +48,20 @@ impl Bind for &WorkspaceId { } } -impl Column for WorkspaceId { +impl Column for WorkspaceLocation { fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { let blob = statement.column_blob(start_index)?; - Ok((WorkspaceId(bincode::deserialize(blob)?), start_index + 1)) + Ok(( + WorkspaceLocation(bincode::deserialize(blob)?), + start_index + 1, + )) } } #[derive(Debug, PartialEq, Eq)] pub struct SerializedWorkspace { - pub workspace_id: WorkspaceId, + pub id: WorkspaceId, + pub location: WorkspaceLocation, pub dock_position: DockPosition, pub center_group: SerializedPaneGroup, pub dock_pane: SerializedPane, @@ -70,10 +76,11 @@ pub enum SerializedPaneGroup { Pane(SerializedPane), } +#[cfg(test)] impl Default for SerializedPaneGroup { fn default() -> Self { Self::Pane(SerializedPane { - children: Vec::new(), + children: vec![SerializedItem::default()], active: false, }) } @@ -84,7 +91,7 @@ impl SerializedPaneGroup { pub(crate) async fn deserialize( &self, project: &ModelHandle, - workspace_id: &WorkspaceId, + workspace_id: WorkspaceId, workspace: &ViewHandle, cx: &mut AsyncAppContext, ) -> (Member, Option>) { @@ -136,13 +143,12 @@ impl SerializedPane { &self, project: &ModelHandle, pane_handle: &ViewHandle, - workspace_id: &WorkspaceId, + workspace_id: WorkspaceId, workspace: &ViewHandle, cx: &mut AsyncAppContext, ) { for item in self.children.iter() { let project = project.clone(); - let workspace_id = workspace_id.clone(); let item_handle = pane_handle .update(cx, |_, cx| { if let Some(deserializer) = cx.global::().get(&item.kind) { @@ -191,6 +197,16 @@ impl SerializedItem { } } +#[cfg(test)] +impl Default for SerializedItem { + fn default() -> Self { + SerializedItem { + kind: Arc::from("Terminal"), + item_id: 100000, + } + } +} + impl Bind for &SerializedItem { fn bind(&self, statement: &Statement, start_index: i32) -> Result { let next_index = statement.bind(self.kind.clone(), start_index)?; @@ -231,7 +247,7 @@ mod tests { use db::sqlez::connection::Connection; use settings::DockAnchor; - use super::WorkspaceId; + use super::WorkspaceLocation; #[test] fn test_workspace_round_trips() { @@ -245,7 +261,7 @@ mod tests { .unwrap()() .unwrap(); - let workspace_id: WorkspaceId = WorkspaceId::from(&["\test2", "\test1"]); + let workspace_id: WorkspaceLocation = WorkspaceLocation::from(&["\test2", "\test1"]); db.exec_bound("INSERT INTO workspace_id_test(workspace_id, dock_anchor) VALUES (?,?)") .unwrap()((&workspace_id, DockAnchor::Bottom)) @@ -255,7 +271,10 @@ mod tests { db.select_row("SELECT workspace_id, dock_anchor FROM workspace_id_test LIMIT 1") .unwrap()() .unwrap(), - Some((WorkspaceId::from(&["\test1", "\test2"]), DockAnchor::Bottom)) + Some(( + WorkspaceLocation::from(&["\test1", "\test2"]), + DockAnchor::Bottom + )) ); } } diff --git a/crates/workspace/src/shared_screen.rs b/crates/workspace/src/shared_screen.rs index 28623950df..7dee642423 100644 --- a/crates/workspace/src/shared_screen.rs +++ b/crates/workspace/src/shared_screen.rs @@ -1,7 +1,5 @@ use crate::{ - item::ItemEvent, - persistence::model::{ItemId, WorkspaceId}, - Item, ItemNavHistory, Pane, Workspace, + item::ItemEvent, persistence::model::ItemId, Item, ItemNavHistory, Pane, Workspace, WorkspaceId, }; use anyhow::{anyhow, Result}; use call::participant::{Frame, RemoteVideoTrack}; @@ -148,7 +146,11 @@ impl Item for SharedScreen { self.nav_history = Some(history); } - fn clone_on_split(&self, cx: &mut ViewContext) -> Option { + fn clone_on_split( + &self, + _workspace_id: WorkspaceId, + cx: &mut ViewContext, + ) -> Option { let track = self.track.upgrade()?; Some(Self::new(&track, self.peer_id, self.user.clone(), cx)) } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 66fdd19c70..4b02522496 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -26,6 +26,7 @@ use anyhow::{anyhow, Context, Result}; use call::ActiveCall; use client::{proto, Client, PeerId, TypedEnvelope, UserStore}; use collections::{hash_map, HashMap, HashSet}; +use db::Uuid; use dock::{DefaultItemFactory, Dock, ToggleDockButton}; use drag_and_drop::DragAndDrop; use fs::{self, Fs}; @@ -45,7 +46,7 @@ use log::{error, warn}; pub use pane::*; pub use pane_group::*; use persistence::model::SerializedItem; -pub use persistence::model::{ItemId, WorkspaceId}; +pub use persistence::model::{ItemId, WorkspaceLocation}; use postage::prelude::Stream; use project::{Project, ProjectEntryId, ProjectPath, ProjectStore, Worktree, WorktreeId}; use serde::Deserialize; @@ -128,6 +129,8 @@ pub struct OpenProjectEntryInPane { project_entry: ProjectEntryId, } +pub type WorkspaceId = Uuid; + impl_internal_actions!( workspace, [ @@ -530,6 +533,7 @@ pub struct Workspace { last_leaders_by_pane: HashMap, PeerId>, window_edited: bool, active_call: Option<(ModelHandle, Vec)>, + database_id: WorkspaceId, _observe_current_user: Task<()>, } @@ -556,7 +560,7 @@ impl Workspace { project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => { this.update_window_title(cx); // TODO: Cache workspace_id on workspace and read from it here - this.serialize_workspace(None, cx); + this.serialize_workspace(cx); } project::Event::DisconnectedFromHost => { this.update_window_edited(cx); @@ -630,6 +634,12 @@ impl Workspace { active_call = Some((call, subscriptions)); } + let id = if let Some(id) = serialized_workspace.as_ref().map(|ws| ws.id) { + id + } else { + WorkspaceId::new() + }; + let mut this = Workspace { modal: None, weak_self: weak_handle.clone(), @@ -657,6 +667,7 @@ impl Workspace { last_leaders_by_pane: Default::default(), window_edited: false, active_call, + database_id: id, _observe_current_user, }; this.project_remote_id_changed(project.read(cx).remote_id(), cx); @@ -1317,7 +1328,7 @@ impl Workspace { pub fn add_item(&mut self, item: Box, cx: &mut ViewContext) { let active_pane = self.active_pane().clone(); Pane::add_item(self, &active_pane, item, true, true, None, cx); - self.serialize_workspace(None, cx); + self.serialize_workspace(cx); } pub fn open_path( @@ -1522,7 +1533,7 @@ impl Workspace { entry.remove(); } } - self.serialize_workspace(None, cx); + self.serialize_workspace(cx); } _ => {} } @@ -1544,7 +1555,7 @@ impl Workspace { pane.read(cx).active_item().map(|item| { let new_pane = self.add_pane(cx); - if let Some(clone) = item.clone_on_split(cx.as_mut()) { + if let Some(clone) = item.clone_on_split(self.database_id(), cx.as_mut()) { Pane::add_item(self, &new_pane, clone, true, true, None, cx); } self.center.split(&pane, &new_pane, direction).unwrap(); @@ -2255,7 +2266,11 @@ impl Workspace { } } - fn workspace_id(&self, cx: &AppContext) -> WorkspaceId { + pub fn database_id(&self) -> WorkspaceId { + self.database_id + } + + fn location(&self, cx: &AppContext) -> WorkspaceLocation { self.project() .read(cx) .visible_worktrees(cx) @@ -2275,7 +2290,7 @@ impl Workspace { } } - fn serialize_workspace(&self, old_id: Option, cx: &AppContext) { + fn serialize_workspace(&self, cx: &AppContext) { fn serialize_pane_handle( pane_handle: &ViewHandle, cx: &AppContext, @@ -2320,7 +2335,8 @@ impl Workspace { let center_group = build_serialized_pane_group(&self.center.root, cx); let serialized_workspace = SerializedWorkspace { - workspace_id: self.workspace_id(cx), + id: self.database_id, + location: self.location(cx), dock_position: self.dock.position(), dock_pane, center_group, @@ -2328,7 +2344,7 @@ impl Workspace { cx.background() .spawn(async move { - persistence::DB.save_workspace(old_id, &serialized_workspace); + persistence::DB.save_workspace(&serialized_workspace); }) .detach(); } @@ -2349,7 +2365,7 @@ impl Workspace { .deserialize_to( &project, &dock_pane_handle, - &serialized_workspace.workspace_id, + serialized_workspace.id, &workspace, &mut cx, ) @@ -2359,12 +2375,7 @@ impl Workspace { let (root, active_pane) = serialized_workspace .center_group - .deserialize( - &project, - &serialized_workspace.workspace_id, - &workspace, - &mut cx, - ) + .deserialize(&project, serialized_workspace.id, &workspace, &mut cx) .await; // Remove old panes from workspace panes list diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 53273b45d8..c95b7c4071 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -597,6 +597,8 @@ pub fn default_item_factory( let working_directory = get_working_directory(workspace, cx, strategy); - let terminal_handle = cx.add_view(|cx| TerminalContainer::new(working_directory, false, cx)); + let terminal_handle = cx.add_view(|cx| { + TerminalContainer::new(working_directory, false, workspace.database_id(), cx) + }); Box::new(terminal_handle) }