Added UUID based, stable workspace ID for caching on item startup. Completed first sketch of terminal persistence. Still need to debug it though....

This commit is contained in:
Mikayla Maki 2022-11-20 22:41:10 -08:00
parent e659823e6c
commit a47f2ca445
20 changed files with 501 additions and 364 deletions

2
Cargo.lock generated
View file

@ -1572,6 +1572,7 @@ dependencies = [
"sqlez", "sqlez",
"tempdir", "tempdir",
"util", "util",
"uuid 1.2.2",
] ]
[[package]] [[package]]
@ -6834,6 +6835,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c"
dependencies = [ dependencies = [
"getrandom 0.2.8", "getrandom 0.2.8",
"rand 0.8.5",
] ]
[[package]] [[package]]

View file

@ -22,6 +22,7 @@ lazy_static = "1.4.0"
log = { version = "0.4.16", features = ["kv_unstable_serde"] } log = { version = "0.4.16", features = ["kv_unstable_serde"] }
parking_lot = "0.11.1" parking_lot = "0.11.1"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
uuid = { version = "1.2.2", features = ["v4", "fast-rng"] }
[dev-dependencies] [dev-dependencies]
gpui = { path = "../gpui", features = ["test-support"] } gpui = { path = "../gpui", features = ["test-support"] }

View file

@ -1,21 +1,26 @@
pub mod kvp; 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 indoc::indoc;
pub use lazy_static; pub use lazy_static;
pub use sqlez; pub use sqlez;
use sqlez::bindable::{Bind, Column};
use std::fs::{create_dir_all, remove_dir_all};
use std::path::Path;
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
use anyhow::Result; use anyhow::Result;
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
use sqlez::connection::Connection; 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 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::channel::{ReleaseChannel, RELEASE_CHANNEL, RELEASE_CHANNEL_NAME};
use util::paths::DB_DIR; use util::paths::DB_DIR;
use uuid::Uuid as RealUuid;
const INITIALIZE_QUERY: &'static str = indoc! {" const INITIALIZE_QUERY: &'static str = indoc! {"
PRAGMA journal_mode=WAL; PRAGMA journal_mode=WAL;
@ -25,6 +30,47 @@ const INITIALIZE_QUERY: &'static str = indoc! {"
PRAGMA case_sensitive_like=TRUE; 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<i32> {
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<Self> {
Ok(Uuid(RealUuid::from_bytes(bytes.try_into()?)))
}
}
/// Open or create a database at the given directory path. /// Open or create a database at the given directory path.
pub fn open_file_db<M: Migrator>() -> ThreadSafeConnection<M> { pub fn open_file_db<M: Migrator>() -> ThreadSafeConnection<M> {
// Use 0 for now. Will implement incrementing and clearing of old db files soon TM // 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<Vec<$return_type>> {
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<Vec<$return_type>> {
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<Option<$return_type>> {
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<Option<$return_type>> {
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),
))
}
};
}

View file

@ -584,7 +584,11 @@ impl Item for ProjectDiagnosticsEditor {
}); });
} }
fn clone_on_split(&self, cx: &mut ViewContext<Self>) -> Option<Self> fn clone_on_split(
&self,
_workspace_id: workspace::WorkspaceId,
cx: &mut ViewContext<Self>,
) -> Option<Self>
where where
Self: Sized, Self: Sized,
{ {

View file

@ -83,7 +83,7 @@ use theme::{DiagnosticStyle, Theme};
use util::{post_inc, ResultExt, TryFutureExt}; use util::{post_inc, ResultExt, TryFutureExt};
use workspace::{ItemNavHistory, Workspace}; 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 CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1); const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
@ -1137,30 +1137,30 @@ impl Editor {
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Self { ) -> Self {
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
if let Some(project) = project.as_ref() { // if let Some(project) = project.as_ref() {
if let Some(file) = buffer // if let Some(file) = buffer
.read(cx) // .read(cx)
.as_singleton() // .as_singleton()
.and_then(|buffer| buffer.read(cx).file()) // .and_then(|buffer| buffer.read(cx).file())
.and_then(|file| file.as_local()) // .and_then(|file| file.as_local())
{ // {
let item_id = cx.weak_handle().id(); // // let item_id = cx.weak_handle().id();
let workspace_id = project // // let workspace_id = project
.read(cx) // // .read(cx)
.visible_worktrees(cx) // // .visible_worktrees(cx)
.map(|worktree| worktree.read(cx).abs_path()) // // .map(|worktree| worktree.read(cx).abs_path())
.collect::<Vec<_>>() // // .collect::<Vec<_>>()
.into(); // // .into();
let path = file.abs_path(cx); // let path = file.abs_path(cx);
dbg!(&path); // dbg!(&path);
cx.background() // // cx.background()
.spawn(async move { // // .spawn(async move {
DB.save_path(item_id, workspace_id, path).log_err(); // // DB.save_path(item_id, workspace_id, path).log_err();
}) // // })
.detach(); // // .detach();
} // }
} // }
Self::new(EditorMode::Full, buffer, project, None, cx) Self::new(EditorMode::Full, buffer, project, None, cx)
} }

View file

@ -368,7 +368,7 @@ impl Item for Editor {
self.buffer.read(cx).is_singleton() self.buffer.read(cx).is_singleton()
} }
fn clone_on_split(&self, cx: &mut ViewContext<Self>) -> Option<Self> fn clone_on_split(&self, _workspace_id: WorkspaceId, cx: &mut ViewContext<Self>) -> Option<Self>
where where
Self: Sized, Self: Sized,
{ {
@ -561,14 +561,13 @@ impl Item for Editor {
fn deserialize( fn deserialize(
project: ModelHandle<Project>, project: ModelHandle<Project>,
_workspace: WeakViewHandle<Workspace>, _workspace: WeakViewHandle<Workspace>,
workspace_id: WorkspaceId, workspace_id: workspace::WorkspaceId,
item_id: ItemId, item_id: ItemId,
cx: &mut ViewContext<Pane>, cx: &mut ViewContext<Pane>,
) -> Task<Result<ViewHandle<Self>>> { ) -> Task<Result<ViewHandle<Self>>> {
if let Some(project_item) = project.update(cx, |project, cx| { if let Some(project_item) = project.update(cx, |project, cx| {
// Look up the path with this key associated, create a self with that path // Look up the path with this key associated, create a self with that path
let path = DB.get_path(item_id, workspace_id).ok()?; let path = DB.get_path(item_id, workspace_id).ok()?;
dbg!(&path);
let (worktree, path) = project.find_local_worktree(&path, cx)?; let (worktree, path) = project.find_local_worktree(&path, cx)?;
let project_path = ProjectPath { let project_path = ProjectPath {
worktree_id: worktree.read(cx).id(), worktree_id: worktree.read(cx).id(),

View file

@ -1,7 +1,7 @@
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use db::connection; use db::{connection, exec_method};
use indoc::indoc; use indoc::indoc;
use sqlez::domain::Domain; use sqlez::domain::Domain;
use workspace::{ItemId, Workspace, WorkspaceId}; use workspace::{ItemId, Workspace, WorkspaceId};
@ -35,18 +35,12 @@ impl EditorDb {
pub fn get_path(&self, item_id: ItemId, workspace_id: WorkspaceId) -> Result<PathBuf> { pub fn get_path(&self, item_id: ItemId, workspace_id: WorkspaceId) -> Result<PathBuf> {
self.select_row_bound(indoc! {" self.select_row_bound(indoc! {"
SELECT path FROM editors 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") .context("Path not found for serialized editor")
} }
pub fn save_path( exec_method!(save_path(item_id: ItemId, workspace_id: WorkspaceId, path: &Path):
&self, "INSERT OR REPLACE INTO editors(item_id, workspace_id, path)
item_id: ItemId, VALUES (?, ?, ?)"
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))
}
} }

View file

@ -26,7 +26,7 @@ use util::ResultExt as _;
use workspace::{ use workspace::{
item::{Item, ItemEvent, ItemHandle}, item::{Item, ItemEvent, ItemHandle},
searchable::{Direction, SearchableItem, SearchableItemHandle}, searchable::{Direction, SearchableItem, SearchableItemHandle},
ItemNavHistory, Pane, ToolbarItemLocation, ToolbarItemView, Workspace, ItemNavHistory, Pane, ToolbarItemLocation, ToolbarItemView, Workspace, WorkspaceId,
}; };
actions!(project_search, [SearchInNew, ToggleFocus]); actions!(project_search, [SearchInNew, ToggleFocus]);
@ -315,7 +315,7 @@ impl Item for ProjectSearchView {
.update(cx, |editor, cx| editor.reload(project, cx)) .update(cx, |editor, cx| editor.reload(project, cx))
} }
fn clone_on_split(&self, cx: &mut ViewContext<Self>) -> Option<Self> fn clone_on_split(&self, _workspace_id: WorkspaceId, cx: &mut ViewContext<Self>) -> Option<Self>
where where
Self: Sized, Self: Sized,
{ {

View file

@ -36,6 +36,13 @@ impl Bind for &[u8] {
} }
} }
impl<const C: usize> Bind for &[u8; C] {
fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
statement.bind_blob(start_index, self.as_slice())?;
Ok(start_index + 1)
}
}
impl Bind for Vec<u8> { impl Bind for Vec<u8> {
fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> { fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
statement.bind_blob(start_index, self)?; statement.bind_blob(start_index, self)?;

View file

@ -52,57 +52,3 @@ impl Connection {
Ok(move |bindings| statement.with_bindings(bindings)?.maybe_row::<C>()) Ok(move |bindings| statement.with_bindings(bindings)?.maybe_row::<C>())
} }
} }
#[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<Vec<$return_type>> {
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<Vec<$return_type>> {
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<Option<$return_type>> {
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<Option<$return_type>> {
iife!({
self.select_row_bound::<($($arg_type),+), $return_type>($sql)?(($($arg),+))
})
}
};
}

View file

@ -1,10 +1,7 @@
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use db::{ use db::{connection, exec_method, indoc, select_row_method, sqlez::domain::Domain};
connection, indoc,
sqlez::{domain::Domain, exec_method, select_row_method},
};
use util::iife;
use workspace::{ItemId, Workspace, WorkspaceId}; use workspace::{ItemId, Workspace, WorkspaceId};
use crate::Terminal; use crate::Terminal;
@ -19,13 +16,12 @@ impl Domain for Terminal {
fn migrations() -> &'static [&'static str] { fn migrations() -> &'static [&'static str] {
&[indoc! {" &[indoc! {"
CREATE TABLE terminals ( CREATE TABLE terminals (
item_id INTEGER,
workspace_id BLOB, workspace_id BLOB,
item_id INTEGER,
working_directory BLOB, working_directory BLOB,
PRIMARY KEY(item_id, workspace_id), PRIMARY KEY(workspace_id, item_id),
FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
ON DELETE CASCADE ON DELETE CASCADE
ON UPDATE CASCADE
) STRICT; ) STRICT;
"}] "}]
} }
@ -33,15 +29,15 @@ impl Domain for Terminal {
impl TerminalDb { impl TerminalDb {
exec_method!( 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) "INSERT OR REPLACE INTO terminals(item_id, workspace_id, working_directory)
VALUES (?, ?, ?)" VALUES (?1, ?2, ?3)"
); );
select_row_method!( 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 "SELECT working_directory
FROM terminals FROM terminals
WHERE item_id = ? workspace_id = ?" WHERE item_id = ? AND workspace_id = ?"
); );
} }

View file

@ -33,9 +33,11 @@ use mappings::mouse::{
alt_scroll, grid_point, mouse_button_report, mouse_moved_report, mouse_side, scroll_report, alt_scroll, grid_point, mouse_button_report, mouse_moved_report, mouse_side, scroll_report,
}; };
use persistence::TERMINAL_CONNECTION;
use procinfo::LocalProcessInfo; use procinfo::LocalProcessInfo;
use settings::{AlternateScroll, Settings, Shell, TerminalBlink}; use settings::{AlternateScroll, Settings, Shell, TerminalBlink};
use util::ResultExt; use util::ResultExt;
use workspace::{ItemId, WorkspaceId};
use std::{ use std::{
cmp::min, cmp::min,
@ -282,6 +284,8 @@ impl TerminalBuilder {
blink_settings: Option<TerminalBlink>, blink_settings: Option<TerminalBlink>,
alternate_scroll: &AlternateScroll, alternate_scroll: &AlternateScroll,
window_id: usize, window_id: usize,
item_id: ItemId,
workspace_id: WorkspaceId,
) -> Result<TerminalBuilder> { ) -> Result<TerminalBuilder> {
let pty_config = { let pty_config = {
let alac_shell = shell.clone().and_then(|shell| match shell { let alac_shell = shell.clone().and_then(|shell| match shell {
@ -386,6 +390,8 @@ impl TerminalBuilder {
last_mouse_position: None, last_mouse_position: None,
next_link_id: 0, next_link_id: 0,
selection_phase: SelectionPhase::Ended, selection_phase: SelectionPhase::Ended,
workspace_id,
item_id,
}; };
Ok(TerminalBuilder { Ok(TerminalBuilder {
@ -529,6 +535,8 @@ pub struct Terminal {
scroll_px: f32, scroll_px: f32,
next_link_id: usize, next_link_id: usize,
selection_phase: SelectionPhase, selection_phase: SelectionPhase,
workspace_id: WorkspaceId,
item_id: ItemId,
} }
impl Terminal { impl Terminal {
@ -566,20 +574,6 @@ impl Terminal {
} }
AlacTermEvent::Wakeup => { AlacTermEvent::Wakeup => {
cx.emit(Event::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) => { AlacTermEvent::ColorRequest(idx, fun_ptr) => {
self.events self.events
@ -888,6 +882,19 @@ impl Terminal {
if self.update_process_info() { if self.update_process_info() {
cx.emit(Event::TitleChanged); 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 //Note that the ordering of events matters for event processing

View file

@ -1,6 +1,6 @@
use crate::persistence::TERMINAL_CONNECTION; use crate::persistence::TERMINAL_CONNECTION;
use crate::terminal_view::TerminalView; use crate::terminal_view::TerminalView;
use crate::{Event, Terminal, TerminalBuilder, TerminalError}; use crate::{Event, TerminalBuilder, TerminalError};
use alacritty_terminal::index::Point; use alacritty_terminal::index::Point;
use dirs::home_dir; use dirs::home_dir;
@ -14,7 +14,7 @@ use workspace::{
item::{Item, ItemEvent}, item::{Item, ItemEvent},
ToolbarItemLocation, Workspace, ToolbarItemLocation, Workspace,
}; };
use workspace::{register_deserializable_item, Pane}; use workspace::{register_deserializable_item, Pane, WorkspaceId};
use project::{LocalWorktree, Project, ProjectPath}; use project::{LocalWorktree, Project, ProjectPath};
use settings::{AlternateScroll, Settings, WorkingDirectory}; use settings::{AlternateScroll, Settings, WorkingDirectory};
@ -82,7 +82,9 @@ impl TerminalContainer {
.unwrap_or(WorkingDirectory::CurrentProjectDirectory); .unwrap_or(WorkingDirectory::CurrentProjectDirectory);
let working_directory = get_working_directory(workspace, cx, strategy); 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); workspace.add_item(Box::new(view), cx);
} }
@ -90,6 +92,7 @@ impl TerminalContainer {
pub fn new( pub fn new(
working_directory: Option<PathBuf>, working_directory: Option<PathBuf>,
modal: bool, modal: bool,
workspace_id: WorkspaceId,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Self { ) -> Self {
let settings = cx.global::<Settings>(); let settings = cx.global::<Settings>();
@ -116,10 +119,13 @@ impl TerminalContainer {
settings.terminal_overrides.blinking.clone(), settings.terminal_overrides.blinking.clone(),
scroll, scroll,
cx.window_id(), cx.window_id(),
cx.view_id(),
workspace_id,
) { ) {
Ok(terminal) => { Ok(terminal) => {
let terminal = cx.add_model(|cx| terminal.subscribe(cx)); let terminal = cx.add_model(|cx| terminal.subscribe(cx));
let view = cx.add_view(|cx| TerminalView::from_terminal(terminal, modal, cx)); let view = cx.add_view(|cx| TerminalView::from_terminal(terminal, modal, cx));
cx.subscribe(&view, |_this, _content, event, cx| cx.emit(*event)) cx.subscribe(&view, |_this, _content, event, cx| cx.emit(*event))
.detach(); .detach();
TerminalContainerContent::Connected(view) TerminalContainerContent::Connected(view)
@ -139,18 +145,6 @@ impl TerminalContainer {
} }
} }
pub fn from_terminal(
terminal: ModelHandle<Terminal>,
modal: bool,
cx: &mut ViewContext<Self>,
) -> 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<ViewHandle<TerminalView>> { fn connected(&self) -> Option<ViewHandle<TerminalView>> {
match &self.content { match &self.content {
TerminalContainerContent::Connected(vh) => Some(vh.clone()), TerminalContainerContent::Connected(vh) => Some(vh.clone()),
@ -278,13 +272,18 @@ impl Item for TerminalContainer {
.boxed() .boxed()
} }
fn clone_on_split(&self, cx: &mut ViewContext<Self>) -> Option<Self> { fn clone_on_split(
&self,
workspace_id: WorkspaceId,
cx: &mut ViewContext<Self>,
) -> Option<Self> {
//From what I can tell, there's no way to tell the current working //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 //Directory of the terminal from outside the shell. There might be
//solutions to this, but they are non-trivial and require more IPC //solutions to this, but they are non-trivial and require more IPC
Some(TerminalContainer::new( Some(TerminalContainer::new(
self.associated_directory.clone(), self.associated_directory.clone(),
false, false,
workspace_id,
cx, cx,
)) ))
} }
@ -391,9 +390,14 @@ impl Item for TerminalContainer {
item_id: workspace::ItemId, item_id: workspace::ItemId,
cx: &mut ViewContext<Pane>, cx: &mut ViewContext<Pane>,
) -> Task<anyhow::Result<ViewHandle<Self>>> { ) -> Task<anyhow::Result<ViewHandle<Self>>> {
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| { 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,
)
}))) })))
} }
} }

View file

@ -206,7 +206,7 @@ impl Dock {
cx.focus(last_active_center_pane); cx.focus(last_active_center_pane);
} }
cx.emit(crate::Event::DockAnchorChanged); cx.emit(crate::Event::DockAnchorChanged);
workspace.serialize_workspace(None, cx); workspace.serialize_workspace(cx);
cx.notify(); cx.notify();
} }

View file

@ -22,11 +22,8 @@ use theme::Theme;
use util::ResultExt; use util::ResultExt;
use crate::{ use crate::{
pane, pane, persistence::model::ItemId, searchable::SearchableItemHandle, DelayedDebouncedEditAction,
persistence::model::{ItemId, WorkspaceId}, FollowableItemBuilders, ItemNavHistory, Pane, ToolbarItemLocation, Workspace, WorkspaceId,
searchable::SearchableItemHandle,
DelayedDebouncedEditAction, FollowableItemBuilders, ItemNavHistory, Pane, ToolbarItemLocation,
Workspace,
}; };
#[derive(Eq, PartialEq, Hash)] #[derive(Eq, PartialEq, Hash)]
@ -52,7 +49,7 @@ pub trait Item: View {
fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>; fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>;
fn is_singleton(&self, cx: &AppContext) -> bool; fn is_singleton(&self, cx: &AppContext) -> bool;
fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext<Self>); fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext<Self>);
fn clone_on_split(&self, _: &mut ViewContext<Self>) -> Option<Self> fn clone_on_split(&self, _workspace_id: WorkspaceId, _: &mut ViewContext<Self>) -> Option<Self>
where where
Self: Sized, Self: Sized,
{ {
@ -121,7 +118,9 @@ pub trait Item: View {
fn breadcrumbs(&self, _theme: &Theme, _cx: &AppContext) -> Option<Vec<ElementBox>> { fn breadcrumbs(&self, _theme: &Theme, _cx: &AppContext) -> Option<Vec<ElementBox>> {
None None
} }
fn serialized_item_kind() -> Option<&'static str>; fn serialized_item_kind() -> Option<&'static str>;
fn deserialize( fn deserialize(
project: ModelHandle<Project>, project: ModelHandle<Project>,
workspace: WeakViewHandle<Workspace>, workspace: WeakViewHandle<Workspace>,
@ -144,7 +143,11 @@ pub trait ItemHandle: 'static + fmt::Debug {
fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>; fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>;
fn is_singleton(&self, cx: &AppContext) -> bool; fn is_singleton(&self, cx: &AppContext) -> bool;
fn boxed_clone(&self) -> Box<dyn ItemHandle>; fn boxed_clone(&self) -> Box<dyn ItemHandle>;
fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option<Box<dyn ItemHandle>>; fn clone_on_split(
&self,
workspace_id: WorkspaceId,
cx: &mut MutableAppContext,
) -> Option<Box<dyn ItemHandle>>;
fn added_to_pane( fn added_to_pane(
&self, &self,
workspace: &mut Workspace, workspace: &mut Workspace,
@ -246,9 +249,13 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
Box::new(self.clone()) Box::new(self.clone())
} }
fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option<Box<dyn ItemHandle>> { fn clone_on_split(
&self,
workspace_id: WorkspaceId,
cx: &mut MutableAppContext,
) -> Option<Box<dyn ItemHandle>> {
self.update(cx, |item, cx| { 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<dyn ItemHandle>) .map(|handle| Box::new(handle) as Box<dyn ItemHandle>)
} }
@ -812,7 +819,11 @@ pub(crate) mod test {
self.push_to_nav_history(cx); self.push_to_nav_history(cx);
} }
fn clone_on_split(&self, _: &mut ViewContext<Self>) -> Option<Self> fn clone_on_split(
&self,
_workspace_id: WorkspaceId,
_: &mut ViewContext<Self>,
) -> Option<Self>
where where
Self: Sized, Self: Sized,
{ {

View file

@ -2,39 +2,38 @@
pub mod model; pub mod model;
use std::path::{Path, PathBuf}; use std::path::Path;
use std::sync::Arc;
use anyhow::{anyhow, bail, Result, Context}; use anyhow::{anyhow, bail, Context, Result};
use db::connection; use db::connection;
use gpui::Axis; use gpui::Axis;
use indoc::indoc; use indoc::indoc;
use db::sqlez::domain::Domain; use db::sqlez::domain::Domain;
use util::{iife, unzip_option, ResultExt}; use util::{iife, unzip_option, ResultExt};
use crate::dock::DockPosition; use crate::dock::DockPosition;
use crate::WorkspaceId;
use super::Workspace; use super::Workspace;
use model::{ use model::{
GroupId, PaneId, SerializedItem, SerializedPane, SerializedPaneGroup, GroupId, PaneId, SerializedItem, SerializedPane, SerializedPaneGroup, SerializedWorkspace,
SerializedWorkspace, WorkspaceId, WorkspaceLocation,
}; };
connection!(DB: WorkspaceDb<Workspace>); connection!(DB: WorkspaceDb<Workspace>);
impl Domain for Workspace { impl Domain for Workspace {
fn name() -> &'static str { fn name() -> &'static str {
"workspace" "workspace"
} }
fn migrations() -> &'static [&'static str] { fn migrations() -> &'static [&'static str] {
&[indoc! {" &[indoc! {"
CREATE TABLE workspaces( CREATE TABLE workspaces(
workspace_id BLOB PRIMARY KEY, workspace_id BLOB PRIMARY KEY,
workspace_location BLOB NOT NULL UNIQUE,
dock_visible INTEGER, -- Boolean dock_visible INTEGER, -- Boolean
dock_anchor TEXT, -- Enum: 'Bottom' / 'Right' / 'Expanded' dock_anchor TEXT, -- Enum: 'Bottom' / 'Right' / 'Expanded'
dock_pane INTEGER, -- NULL indicates that we don't have a dock pane yet dock_pane INTEGER, -- NULL indicates that we don't have a dock pane yet
@ -97,21 +96,25 @@ impl WorkspaceDb {
&self, &self,
worktree_roots: &[P], worktree_roots: &[P],
) -> Option<SerializedWorkspace> { ) -> Option<SerializedWorkspace> {
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 // Note that we re-assign the workspace_id here in case it's empty
// and we've grabbed the most recent workspace // 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 { if worktree_roots.len() == 0 {
self.select_row(indoc! {" self.select_row(indoc! {"
SELECT workspace_id, dock_visible, dock_anchor SELECT workspace_id, workspace_location, dock_visible, dock_anchor
FROM workspaces FROM workspaces
ORDER BY timestamp DESC LIMIT 1"})?()? ORDER BY timestamp DESC LIMIT 1"})?()?
} else { } else {
self.select_row_bound(indoc! {" self.select_row_bound(indoc! {"
SELECT workspace_id, dock_visible, dock_anchor SELECT workspace_id, workspace_location, dock_visible, dock_anchor
FROM workspaces FROM workspaces
WHERE workspace_id = ?"})?(&workspace_id)? WHERE workspace_location = ?"})?(&workspace_location)?
} }
.context("No workspaces found") .context("No workspaces found")
}) })
@ -119,13 +122,14 @@ impl WorkspaceDb {
.flatten()?; .flatten()?;
Some(SerializedWorkspace { Some(SerializedWorkspace {
workspace_id: workspace_id.clone(), id: workspace_id,
location: workspace_location.clone(),
dock_pane: self dock_pane: self
.get_dock_pane(&workspace_id) .get_dock_pane(workspace_id)
.context("Getting dock pane") .context("Getting dock pane")
.log_err()?, .log_err()?,
center_group: self center_group: self
.get_center_pane_group(&workspace_id) .get_center_pane_group(workspace_id)
.context("Getting center group") .context("Getting center group")
.log_err()?, .log_err()?,
dock_position, dock_position,
@ -134,72 +138,61 @@ impl WorkspaceDb {
/// Saves a workspace using the worktree roots. Will garbage collect any workspaces /// Saves a workspace using the worktree roots. Will garbage collect any workspaces
/// that used this workspace previously /// that used this workspace previously
pub fn save_workspace( pub fn save_workspace(&self, workspace: &SerializedWorkspace) {
&self,
old_id: Option<WorkspaceId>,
workspace: &SerializedWorkspace,
) {
self.with_savepoint("update_worktrees", || { self.with_savepoint("update_worktrees", || {
// Clear out panes and pane_groups
self.exec_bound(indoc! {" self.exec_bound(indoc! {"
UPDATE workspaces SET dock_pane = NULL WHERE workspace_id = ?1; UPDATE workspaces SET dock_pane = NULL WHERE workspace_id = ?1;
DELETE FROM pane_groups WHERE workspace_id = ?1; DELETE FROM pane_groups WHERE workspace_id = ?1;
DELETE FROM panes WHERE workspace_id = ?1;"})? DELETE FROM panes WHERE workspace_id = ?1;"})?(workspace.id)
(old_id.as_ref().unwrap_or(&workspace.workspace_id)).context("Clearing old panes")?; .context("Clearing old panes")?;
if let Some(old_id) = old_id { // Update or insert
self.exec_bound(indoc! {" self.exec_bound(indoc! {
UPDATE OR REPLACE workspaces "INSERT OR REPLACE INTO
SET workspace_id = ?, workspaces(workspace_id, workspace_location, dock_visible, dock_anchor, timestamp)
dock_visible = ?, VALUES
dock_anchor = ?, (?1, ?2, ?3, ?4, CURRENT_TIMESTAMP)"
timestamp = CURRENT_TIMESTAMP })?((workspace.id, &workspace.location, workspace.dock_position))
WHERE workspace_id = ?"})?(( .context("Updating workspace")?;
&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")?;
}
// Save center pane group and dock pane // 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")?; self.save_pane_group(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")?;
let dock_id = self
.save_pane(workspace.id, &workspace.dock_pane, None, true)
.context("save pane in save workspace")?;
// Complete workspace initialization // Complete workspace initialization
self.exec_bound(indoc! {" self.exec_bound(indoc! {"
UPDATE workspaces UPDATE workspaces
SET dock_pane = ? SET dock_pane = ?
WHERE workspace_id = ?"})?(( WHERE workspace_id = ?"})?((dock_id, workspace.id))
dock_id, .context("Finishing initialization with dock pane")?;
&workspace.workspace_id,
)).context("Finishing initialization with dock pane")?;
Ok(()) Ok(())
}) })
.with_context(|| { .with_context(|| {
format!( format!(
"Update workspace with roots {:?} failed.", "Update workspace with roots {:?} and id {:?} failed.",
workspace.workspace_id.paths() workspace.location.paths(),
workspace.id
) )
}) })
.log_err(); .log_err();
} }
/// Returns the previous workspace ids sorted by last modified along with their opened worktree roots /// Returns the previous workspace ids sorted by last modified along with their opened worktree roots
pub fn recent_workspaces(&self, limit: usize) -> Vec<Arc<Vec<PathBuf>>> { pub fn recent_workspaces(&self, limit: usize) -> Vec<(WorkspaceId, WorkspaceLocation)> {
iife!({ iife!({
// TODO, upgrade anyhow: https://docs.rs/anyhow/1.0.66/anyhow/fn.Ok.html // TODO, upgrade anyhow: https://docs.rs/anyhow/1.0.66/anyhow/fn.Ok.html
Ok::<_, anyhow::Error>( Ok::<_, anyhow::Error>(
self.select_bound::<usize, WorkspaceId>( self.select_bound::<usize, (WorkspaceId, WorkspaceLocation)>(
"SELECT workspace_id FROM workspaces ORDER BY timestamp DESC LIMIT ?", "SELECT workspace_id, workspace_location FROM workspaces ORDER BY timestamp DESC LIMIT ?",
)?(limit)? )?(limit)?
.into_iter() .into_iter()
.map(|id| id.paths()) .collect::<Vec<(WorkspaceId, WorkspaceLocation)>>(),
.collect::<Vec<Arc<Vec<PathBuf>>>>(),
) )
}) })
.log_err() .log_err()
@ -208,7 +201,7 @@ impl WorkspaceDb {
pub(crate) fn get_center_pane_group( pub(crate) fn get_center_pane_group(
&self, &self,
workspace_id: &WorkspaceId, workspace_id: WorkspaceId,
) -> Result<SerializedPaneGroup> { ) -> Result<SerializedPaneGroup> {
self.get_pane_group(workspace_id, None)? self.get_pane_group(workspace_id, None)?
.into_iter() .into_iter()
@ -218,10 +211,10 @@ impl WorkspaceDb {
fn get_pane_group( fn get_pane_group(
&self, &self,
workspace_id: &WorkspaceId, workspace_id: WorkspaceId,
group_id: Option<GroupId>, group_id: Option<GroupId>,
) -> Result<Vec<SerializedPaneGroup>> { ) -> Result<Vec<SerializedPaneGroup>> {
type GroupKey<'a> = (Option<GroupId>, &'a WorkspaceId); type GroupKey = (Option<GroupId>, WorkspaceId);
type GroupOrPane = (Option<GroupId>, Option<Axis>, Option<PaneId>, Option<bool>); type GroupOrPane = (Option<GroupId>, Option<Axis>, Option<PaneId>, Option<bool>);
self.select_bound::<GroupKey, GroupOrPane>(indoc! {" self.select_bound::<GroupKey, GroupOrPane>(indoc! {"
SELECT group_id, axis, pane_id, active SELECT group_id, axis, pane_id, active
@ -253,31 +246,29 @@ impl WorkspaceDb {
if let Some((group_id, axis)) = group_id.zip(axis) { if let Some((group_id, axis)) = group_id.zip(axis) {
Ok(SerializedPaneGroup::Group { Ok(SerializedPaneGroup::Group {
axis, axis,
children: self.get_pane_group( children: self.get_pane_group(workspace_id, Some(group_id))?,
workspace_id,
Some(group_id),
)?,
}) })
} else if let Some((pane_id, active)) = pane_id.zip(active) { } 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 { } else {
bail!("Pane Group Child was neither a pane group or a pane"); 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 out panes and pane groups which don't have any children or items
.filter(|pane_group| { .filter(|pane_group| match pane_group {
match pane_group { Ok(SerializedPaneGroup::Group { children, .. }) => !children.is_empty(),
Ok(SerializedPaneGroup::Group { children, .. }) => !children.is_empty(), Ok(SerializedPaneGroup::Pane(pane)) => !pane.children.is_empty(),
Ok(SerializedPaneGroup::Pane(pane)) => !pane.children.is_empty(), _ => true,
_ => true,
}
}) })
.collect::<Result<_>>() .collect::<Result<_>>()
} }
pub(crate) fn save_pane_group( pub(crate) fn save_pane_group(
&self, &self,
workspace_id: &WorkspaceId, workspace_id: WorkspaceId,
pane_group: &SerializedPaneGroup, pane_group: &SerializedPaneGroup,
parent: Option<(GroupId, usize)>, parent: Option<(GroupId, usize)>,
) -> Result<()> { ) -> Result<()> {
@ -285,26 +276,31 @@ impl WorkspaceDb {
SerializedPaneGroup::Group { axis, children } => { SerializedPaneGroup::Group { axis, children } => {
let (parent_id, position) = unzip_option(parent); 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) INSERT INTO pane_groups(workspace_id, parent_group_id, position, axis)
VALUES (?, ?, ?, ?) VALUES (?, ?, ?, ?)
RETURNING group_id"})? RETURNING group_id"})?((
((workspace_id, parent_id, position, *axis))? workspace_id,
.ok_or_else(|| anyhow!("Couldn't retrieve group_id from inserted pane_group"))?; parent_id,
position,
*axis,
))?
.ok_or_else(|| anyhow!("Couldn't retrieve group_id from inserted pane_group"))?;
for (position, group) in children.iter().enumerate() { for (position, group) in children.iter().enumerate() {
self.save_pane_group(workspace_id, group, Some((group_id, position)))? self.save_pane_group(workspace_id, group, Some((group_id, position)))?
} }
Ok(()) Ok(())
} }
SerializedPaneGroup::Pane(pane) => { SerializedPaneGroup::Pane(pane) => {
self.save_pane(workspace_id, &pane, parent, false)?; self.save_pane(workspace_id, &pane, parent, false)?;
Ok(()) Ok(())
}, }
} }
} }
pub(crate) fn get_dock_pane(&self, workspace_id: &WorkspaceId) -> Result<SerializedPane> { pub(crate) fn get_dock_pane(&self, workspace_id: WorkspaceId) -> Result<SerializedPane> {
let (pane_id, active) = self.select_row_bound(indoc! {" let (pane_id, active) = self.select_row_bound(indoc! {"
SELECT pane_id, active SELECT pane_id, active
FROM panes FROM panes
@ -315,40 +311,35 @@ impl WorkspaceDb {
Ok(SerializedPane::new( Ok(SerializedPane::new(
self.get_items(pane_id).context("Reading items")?, self.get_items(pane_id).context("Reading items")?,
active active,
)) ))
} }
pub(crate) fn save_pane( pub(crate) fn save_pane(
&self, &self,
workspace_id: &WorkspaceId, workspace_id: WorkspaceId,
pane: &SerializedPane, pane: &SerializedPane,
parent: Option<(GroupId, usize)>, // None indicates BOTH dock pane AND center_pane parent: Option<(GroupId, usize)>, // None indicates BOTH dock pane AND center_pane
dock: bool, dock: bool,
) -> Result<PaneId> { ) -> Result<PaneId> {
let pane_id = self.select_row_bound::<_, i64>(indoc!{" let pane_id = self.select_row_bound::<_, i64>(indoc! {"
INSERT INTO panes(workspace_id, active) INSERT INTO panes(workspace_id, active)
VALUES (?, ?) VALUES (?, ?)
RETURNING pane_id"}, RETURNING pane_id"})?((workspace_id, pane.active))?
)?((workspace_id, pane.active))?
.ok_or_else(|| anyhow!("Could not retrieve inserted pane_id"))?; .ok_or_else(|| anyhow!("Could not retrieve inserted pane_id"))?;
if !dock { if !dock {
let (parent_id, order) = unzip_option(parent); let (parent_id, order) = unzip_option(parent);
self.exec_bound(indoc! {" self.exec_bound(indoc! {"
INSERT INTO center_panes(pane_id, parent_group_id, position) INSERT INTO center_panes(pane_id, parent_group_id, position)
VALUES (?, ?, ?)"})?(( VALUES (?, ?, ?)"})?((pane_id, parent_id, order))?;
pane_id, parent_id, order
))?;
} }
self.save_items(workspace_id, pane_id, &pane.children) self.save_items(workspace_id, pane_id, &pane.children)
.context("Saving items")?; .context("Saving items")?;
Ok(pane_id) Ok(pane_id)
} }
pub(crate) fn get_items(&self, pane_id: PaneId) -> Result<Vec<SerializedItem>> { pub(crate) fn get_items(&self, pane_id: PaneId) -> Result<Vec<SerializedItem>> {
Ok(self.select_bound(indoc! {" Ok(self.select_bound(indoc! {"
@ -359,7 +350,7 @@ impl WorkspaceDb {
pub(crate) fn save_items( pub(crate) fn save_items(
&self, &self,
workspace_id: &WorkspaceId, workspace_id: WorkspaceId,
pane_id: PaneId, pane_id: PaneId,
items: &[SerializedItem], items: &[SerializedItem],
) -> Result<()> { ) -> Result<()> {
@ -376,7 +367,8 @@ impl WorkspaceDb {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use db::{open_memory_db};
use db::{open_memory_db, Uuid};
use settings::DockAnchor; use settings::DockAnchor;
use super::*; use super::*;
@ -388,15 +380,13 @@ mod tests {
let db = WorkspaceDb(open_memory_db(Some("test_full_workspace_serialization"))); let db = WorkspaceDb(open_memory_db(Some("test_full_workspace_serialization")));
let dock_pane = crate::persistence::model::SerializedPane { let dock_pane = crate::persistence::model::SerializedPane {
children: vec![ children: vec![
SerializedItem::new("Terminal", 1), SerializedItem::new("Terminal", 1),
SerializedItem::new("Terminal", 2), SerializedItem::new("Terminal", 2),
SerializedItem::new("Terminal", 3), SerializedItem::new("Terminal", 3),
SerializedItem::new("Terminal", 4), SerializedItem::new("Terminal", 4),
], ],
active: false active: false,
}; };
// ----------------- // -----------------
@ -415,8 +405,8 @@ mod tests {
SerializedItem::new("Terminal", 5), SerializedItem::new("Terminal", 5),
SerializedItem::new("Terminal", 6), SerializedItem::new("Terminal", 6),
], ],
false) false,
), )),
SerializedPaneGroup::Pane(SerializedPane::new( SerializedPaneGroup::Pane(SerializedPane::new(
vec![ vec![
SerializedItem::new("Terminal", 7), SerializedItem::new("Terminal", 7),
@ -430,7 +420,6 @@ mod tests {
vec![ vec![
SerializedItem::new("Terminal", 9), SerializedItem::new("Terminal", 9),
SerializedItem::new("Terminal", 10), SerializedItem::new("Terminal", 10),
], ],
false, false,
)), )),
@ -438,25 +427,24 @@ mod tests {
}; };
let workspace = SerializedWorkspace { let workspace = SerializedWorkspace {
workspace_id: (["/tmp", "/tmp2"]).into(), id: Uuid::new(),
dock_position: DockPosition::Shown(DockAnchor::Bottom), location: (["/tmp", "/tmp2"]).into(),
dock_position: DockPosition::Shown(DockAnchor::Bottom),
center_group, center_group,
dock_pane, dock_pane,
}; };
db.save_workspace(None, &workspace); db.save_workspace(&workspace);
let round_trip_workspace = db.workspace_for_roots(&["/tmp2", "/tmp"]); let round_trip_workspace = db.workspace_for_roots(&["/tmp2", "/tmp"]);
assert_eq!(workspace, round_trip_workspace.unwrap()); assert_eq!(workspace, round_trip_workspace.unwrap());
// Test guaranteed duplicate IDs // Test guaranteed duplicate IDs
db.save_workspace(None, &workspace); db.save_workspace(&workspace);
db.save_workspace(None, &workspace); db.save_workspace(&workspace);
let round_trip_workspace = db.workspace_for_roots(&["/tmp", "/tmp2"]); let round_trip_workspace = db.workspace_for_roots(&["/tmp", "/tmp2"]);
assert_eq!(workspace, round_trip_workspace.unwrap()); assert_eq!(workspace, round_trip_workspace.unwrap());
} }
#[test] #[test]
@ -466,21 +454,23 @@ mod tests {
let db = WorkspaceDb(open_memory_db(Some("test_basic_functionality"))); let db = WorkspaceDb(open_memory_db(Some("test_basic_functionality")));
let workspace_1 = SerializedWorkspace { let workspace_1 = SerializedWorkspace {
workspace_id: (["/tmp", "/tmp2"]).into(), id: WorkspaceId::new(),
location: (["/tmp", "/tmp2"]).into(),
dock_position: crate::dock::DockPosition::Shown(DockAnchor::Bottom), dock_position: crate::dock::DockPosition::Shown(DockAnchor::Bottom),
center_group: Default::default(), center_group: Default::default(),
dock_pane: Default::default(), dock_pane: Default::default(),
}; };
let mut workspace_2 = SerializedWorkspace { let mut workspace_2 = SerializedWorkspace {
workspace_id: (["/tmp"]).into(), id: WorkspaceId::new(),
location: (["/tmp"]).into(),
dock_position: crate::dock::DockPosition::Hidden(DockAnchor::Expanded), dock_position: crate::dock::DockPosition::Hidden(DockAnchor::Expanded),
center_group: Default::default(), center_group: Default::default(),
dock_pane: Default::default(), dock_pane: Default::default(),
}; };
db.save_workspace(None, &workspace_1); db.save_workspace(&workspace_1);
db.save_workspace(None, &workspace_2); db.save_workspace(&workspace_2);
// Test that paths are treated as a set // Test that paths are treated as a set
assert_eq!( assert_eq!(
@ -497,8 +487,9 @@ mod tests {
assert_eq!(db.workspace_for_roots(&["/tmp3", "/tmp2", "/tmp4"]), None); assert_eq!(db.workspace_for_roots(&["/tmp3", "/tmp2", "/tmp4"]), None);
// Test 'mutate' case of updating a pre-existing id // Test 'mutate' case of updating a pre-existing id
workspace_2.workspace_id = (["/tmp", "/tmp2"]).into(); workspace_2.location = (["/tmp", "/tmp2"]).into();
db.save_workspace(Some((&["/tmp"]).into()), &workspace_2);
db.save_workspace(&workspace_2);
assert_eq!( assert_eq!(
db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(), db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(),
workspace_2 workspace_2
@ -506,33 +497,28 @@ mod tests {
// Test other mechanism for mutating // Test other mechanism for mutating
let mut workspace_3 = SerializedWorkspace { let mut workspace_3 = SerializedWorkspace {
workspace_id: (&["/tmp", "/tmp2"]).into(), id: WorkspaceId::new(),
location: (&["/tmp", "/tmp2"]).into(),
dock_position: DockPosition::Shown(DockAnchor::Right), dock_position: DockPosition::Shown(DockAnchor::Right),
center_group: Default::default(), center_group: Default::default(),
dock_pane: Default::default(), dock_pane: Default::default(),
}; };
db.save_workspace(&workspace_3);
db.save_workspace(None, &workspace_3);
assert_eq!( assert_eq!(
db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(), db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(),
workspace_3 workspace_3
); );
// Make sure that updating paths differently also works // Make sure that updating paths differently also works
workspace_3.workspace_id = (["/tmp3", "/tmp4", "/tmp2"]).into(); workspace_3.location = (["/tmp3", "/tmp4", "/tmp2"]).into();
db.save_workspace( db.save_workspace(&workspace_3);
Some((&["/tmp", "/tmp2"]).into()),
&workspace_3,
);
assert_eq!(db.workspace_for_roots(&["/tmp2", "tmp"]), None); assert_eq!(db.workspace_for_roots(&["/tmp2", "tmp"]), None);
assert_eq!( assert_eq!(
db.workspace_for_roots(&["/tmp2", "/tmp3", "/tmp4"]) db.workspace_for_roots(&["/tmp2", "/tmp3", "/tmp4"])
.unwrap(), .unwrap(),
workspace_3 workspace_3
); );
} }
use crate::dock::DockPosition; use crate::dock::DockPosition;
@ -545,7 +531,8 @@ mod tests {
center_group: &SerializedPaneGroup, center_group: &SerializedPaneGroup,
) -> SerializedWorkspace { ) -> SerializedWorkspace {
SerializedWorkspace { SerializedWorkspace {
workspace_id: workspace_id.into(), id: WorkspaceId::new(),
location: workspace_id.into(),
dock_position: crate::dock::DockPosition::Hidden(DockAnchor::Right), dock_position: crate::dock::DockPosition::Hidden(DockAnchor::Right),
center_group: center_group.clone(), center_group: center_group.clone(),
dock_pane, dock_pane,
@ -564,12 +551,13 @@ mod tests {
SerializedItem::new("Terminal", 4), SerializedItem::new("Terminal", 4),
SerializedItem::new("Terminal", 2), SerializedItem::new("Terminal", 2),
SerializedItem::new("Terminal", 3), SerializedItem::new("Terminal", 3),
], false ],
false,
); );
let workspace = default_workspace(&["/tmp"], dock_pane, &Default::default()); 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(); let new_workspace = db.workspace_for_roots(&["/tmp"]).unwrap();
@ -593,16 +581,20 @@ mod tests {
SerializedPaneGroup::Group { SerializedPaneGroup::Group {
axis: gpui::Axis::Vertical, axis: gpui::Axis::Vertical,
children: vec![ children: vec![
SerializedPaneGroup::Pane(SerializedPane::new( SerializedPaneGroup::Pane(SerializedPane::new(
vec![ vec![
SerializedItem::new("Terminal", 1), SerializedItem::new("Terminal", 1),
SerializedItem::new("Terminal", 2), SerializedItem::new("Terminal", 2),
], ],
false)), false,
SerializedPaneGroup::Pane(SerializedPane::new(vec![ )),
SerializedItem::new("Terminal", 4), SerializedPaneGroup::Pane(SerializedPane::new(
SerializedItem::new("Terminal", 3), vec![
], true)), SerializedItem::new("Terminal", 4),
SerializedItem::new("Terminal", 3),
],
true,
)),
], ],
}, },
SerializedPaneGroup::Pane(SerializedPane::new( SerializedPaneGroup::Pane(SerializedPane::new(
@ -610,41 +602,46 @@ mod tests {
SerializedItem::new("Terminal", 5), SerializedItem::new("Terminal", 5),
SerializedItem::new("Terminal", 6), SerializedItem::new("Terminal", 6),
], ],
false)), false,
)),
], ],
}; };
let workspace = default_workspace(&["/tmp"], Default::default(), &center_pane); let workspace = default_workspace(&["/tmp"], Default::default(), &center_pane);
db.save_workspace(None, &workspace); db.save_workspace(&workspace);
let new_workspace = db.workspace_for_roots(&["/tmp"]).unwrap(); let new_workspace = db.workspace_for_roots(&["/tmp"]).unwrap();
assert_eq!(workspace.center_group, new_workspace.center_group); assert_eq!(workspace.center_group, new_workspace.center_group);
} }
#[test] #[test]
fn test_cleanup_panes() { fn test_cleanup_panes() {
env_logger::try_init().ok(); env_logger::try_init().ok();
let db = WorkspaceDb(open_memory_db(Some("test_cleanup_panes"))); let db = WorkspaceDb(open_memory_db(Some("test_cleanup_panes")));
let center_pane = SerializedPaneGroup::Group { let center_pane = SerializedPaneGroup::Group {
axis: gpui::Axis::Horizontal, axis: gpui::Axis::Horizontal,
children: vec![ children: vec![
SerializedPaneGroup::Group { SerializedPaneGroup::Group {
axis: gpui::Axis::Vertical, axis: gpui::Axis::Vertical,
children: vec![ children: vec![
SerializedPaneGroup::Pane(SerializedPane::new( SerializedPaneGroup::Pane(SerializedPane::new(
vec![ vec![
SerializedItem::new("Terminal", 1), SerializedItem::new("Terminal", 1),
SerializedItem::new("Terminal", 2), SerializedItem::new("Terminal", 2),
], ],
false)), false,
SerializedPaneGroup::Pane(SerializedPane::new(vec![ )),
SerializedItem::new("Terminal", 4), SerializedPaneGroup::Pane(SerializedPane::new(
SerializedItem::new("Terminal", 3), vec![
], true)), SerializedItem::new("Terminal", 4),
SerializedItem::new("Terminal", 3),
],
true,
)),
], ],
}, },
SerializedPaneGroup::Pane(SerializedPane::new( SerializedPaneGroup::Pane(SerializedPane::new(
@ -652,37 +649,41 @@ mod tests {
SerializedItem::new("Terminal", 5), SerializedItem::new("Terminal", 5),
SerializedItem::new("Terminal", 6), SerializedItem::new("Terminal", 6),
], ],
false)), false,
)),
], ],
}; };
let id = &["/tmp"]; let id = &["/tmp"];
let mut workspace = default_workspace(id, Default::default(), &center_pane); let mut workspace = default_workspace(id, Default::default(), &center_pane);
db.save_workspace(None, &workspace); db.save_workspace(&workspace);
workspace.center_group = SerializedPaneGroup::Group { workspace.center_group = SerializedPaneGroup::Group {
axis: gpui::Axis::Vertical, axis: gpui::Axis::Vertical,
children: vec![ children: vec![
SerializedPaneGroup::Pane(SerializedPane::new( SerializedPaneGroup::Pane(SerializedPane::new(
vec![ vec![
SerializedItem::new("Terminal", 1), SerializedItem::new("Terminal", 1),
SerializedItem::new("Terminal", 2), SerializedItem::new("Terminal", 2),
], ],
false)), false,
SerializedPaneGroup::Pane(SerializedPane::new(vec![ )),
SerializedItem::new("Terminal", 4), SerializedPaneGroup::Pane(SerializedPane::new(
SerializedItem::new("Terminal", 3), vec![
], true)), 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(); let new_workspace = db.workspace_for_roots(id).unwrap();
assert_eq!(workspace.center_group, new_workspace.center_group); assert_eq!(workspace.center_group, new_workspace.center_group);
} }
} }

View file

@ -16,18 +16,20 @@ use project::Project;
use settings::DockAnchor; use settings::DockAnchor;
use util::ResultExt; 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)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct WorkspaceId(Arc<Vec<PathBuf>>); pub struct WorkspaceLocation(Arc<Vec<PathBuf>>);
impl WorkspaceId { impl WorkspaceLocation {
pub fn paths(&self) -> Arc<Vec<PathBuf>> { pub fn paths(&self) -> Arc<Vec<PathBuf>> {
self.0.clone() self.0.clone()
} }
} }
impl<P: AsRef<Path>, T: IntoIterator<Item = P>> From<T> for WorkspaceId { impl<P: AsRef<Path>, T: IntoIterator<Item = P>> From<T> for WorkspaceLocation {
fn from(iterator: T) -> Self { fn from(iterator: T) -> Self {
let mut roots = iterator let mut roots = iterator
.into_iter() .into_iter()
@ -38,7 +40,7 @@ impl<P: AsRef<Path>, T: IntoIterator<Item = P>> From<T> for WorkspaceId {
} }
} }
impl Bind for &WorkspaceId { impl Bind for &WorkspaceLocation {
fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> { fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
bincode::serialize(&self.0) bincode::serialize(&self.0)
.expect("Bincode serialization of paths should not fail") .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)> { fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
let blob = statement.column_blob(start_index)?; 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)] #[derive(Debug, PartialEq, Eq)]
pub struct SerializedWorkspace { pub struct SerializedWorkspace {
pub workspace_id: WorkspaceId, pub id: WorkspaceId,
pub location: WorkspaceLocation,
pub dock_position: DockPosition, pub dock_position: DockPosition,
pub center_group: SerializedPaneGroup, pub center_group: SerializedPaneGroup,
pub dock_pane: SerializedPane, pub dock_pane: SerializedPane,
@ -70,10 +76,11 @@ pub enum SerializedPaneGroup {
Pane(SerializedPane), Pane(SerializedPane),
} }
#[cfg(test)]
impl Default for SerializedPaneGroup { impl Default for SerializedPaneGroup {
fn default() -> Self { fn default() -> Self {
Self::Pane(SerializedPane { Self::Pane(SerializedPane {
children: Vec::new(), children: vec![SerializedItem::default()],
active: false, active: false,
}) })
} }
@ -84,7 +91,7 @@ impl SerializedPaneGroup {
pub(crate) async fn deserialize( pub(crate) async fn deserialize(
&self, &self,
project: &ModelHandle<Project>, project: &ModelHandle<Project>,
workspace_id: &WorkspaceId, workspace_id: WorkspaceId,
workspace: &ViewHandle<Workspace>, workspace: &ViewHandle<Workspace>,
cx: &mut AsyncAppContext, cx: &mut AsyncAppContext,
) -> (Member, Option<ViewHandle<Pane>>) { ) -> (Member, Option<ViewHandle<Pane>>) {
@ -136,13 +143,12 @@ impl SerializedPane {
&self, &self,
project: &ModelHandle<Project>, project: &ModelHandle<Project>,
pane_handle: &ViewHandle<Pane>, pane_handle: &ViewHandle<Pane>,
workspace_id: &WorkspaceId, workspace_id: WorkspaceId,
workspace: &ViewHandle<Workspace>, workspace: &ViewHandle<Workspace>,
cx: &mut AsyncAppContext, cx: &mut AsyncAppContext,
) { ) {
for item in self.children.iter() { for item in self.children.iter() {
let project = project.clone(); let project = project.clone();
let workspace_id = workspace_id.clone();
let item_handle = pane_handle let item_handle = pane_handle
.update(cx, |_, cx| { .update(cx, |_, cx| {
if let Some(deserializer) = cx.global::<ItemDeserializers>().get(&item.kind) { if let Some(deserializer) = cx.global::<ItemDeserializers>().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 { impl Bind for &SerializedItem {
fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> { fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
let next_index = statement.bind(self.kind.clone(), start_index)?; let next_index = statement.bind(self.kind.clone(), start_index)?;
@ -231,7 +247,7 @@ mod tests {
use db::sqlez::connection::Connection; use db::sqlez::connection::Connection;
use settings::DockAnchor; use settings::DockAnchor;
use super::WorkspaceId; use super::WorkspaceLocation;
#[test] #[test]
fn test_workspace_round_trips() { fn test_workspace_round_trips() {
@ -245,7 +261,7 @@ mod tests {
.unwrap()() .unwrap()()
.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 (?,?)") db.exec_bound("INSERT INTO workspace_id_test(workspace_id, dock_anchor) VALUES (?,?)")
.unwrap()((&workspace_id, DockAnchor::Bottom)) .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") db.select_row("SELECT workspace_id, dock_anchor FROM workspace_id_test LIMIT 1")
.unwrap()() .unwrap()()
.unwrap(), .unwrap(),
Some((WorkspaceId::from(&["\test1", "\test2"]), DockAnchor::Bottom)) Some((
WorkspaceLocation::from(&["\test1", "\test2"]),
DockAnchor::Bottom
))
); );
} }
} }

View file

@ -1,7 +1,5 @@
use crate::{ use crate::{
item::ItemEvent, item::ItemEvent, persistence::model::ItemId, Item, ItemNavHistory, Pane, Workspace, WorkspaceId,
persistence::model::{ItemId, WorkspaceId},
Item, ItemNavHistory, Pane, Workspace,
}; };
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use call::participant::{Frame, RemoteVideoTrack}; use call::participant::{Frame, RemoteVideoTrack};
@ -148,7 +146,11 @@ impl Item for SharedScreen {
self.nav_history = Some(history); self.nav_history = Some(history);
} }
fn clone_on_split(&self, cx: &mut ViewContext<Self>) -> Option<Self> { fn clone_on_split(
&self,
_workspace_id: WorkspaceId,
cx: &mut ViewContext<Self>,
) -> Option<Self> {
let track = self.track.upgrade()?; let track = self.track.upgrade()?;
Some(Self::new(&track, self.peer_id, self.user.clone(), cx)) Some(Self::new(&track, self.peer_id, self.user.clone(), cx))
} }

View file

@ -26,6 +26,7 @@ use anyhow::{anyhow, Context, Result};
use call::ActiveCall; use call::ActiveCall;
use client::{proto, Client, PeerId, TypedEnvelope, UserStore}; use client::{proto, Client, PeerId, TypedEnvelope, UserStore};
use collections::{hash_map, HashMap, HashSet}; use collections::{hash_map, HashMap, HashSet};
use db::Uuid;
use dock::{DefaultItemFactory, Dock, ToggleDockButton}; use dock::{DefaultItemFactory, Dock, ToggleDockButton};
use drag_and_drop::DragAndDrop; use drag_and_drop::DragAndDrop;
use fs::{self, Fs}; use fs::{self, Fs};
@ -45,7 +46,7 @@ use log::{error, warn};
pub use pane::*; pub use pane::*;
pub use pane_group::*; pub use pane_group::*;
use persistence::model::SerializedItem; use persistence::model::SerializedItem;
pub use persistence::model::{ItemId, WorkspaceId}; pub use persistence::model::{ItemId, WorkspaceLocation};
use postage::prelude::Stream; use postage::prelude::Stream;
use project::{Project, ProjectEntryId, ProjectPath, ProjectStore, Worktree, WorktreeId}; use project::{Project, ProjectEntryId, ProjectPath, ProjectStore, Worktree, WorktreeId};
use serde::Deserialize; use serde::Deserialize;
@ -128,6 +129,8 @@ pub struct OpenProjectEntryInPane {
project_entry: ProjectEntryId, project_entry: ProjectEntryId,
} }
pub type WorkspaceId = Uuid;
impl_internal_actions!( impl_internal_actions!(
workspace, workspace,
[ [
@ -530,6 +533,7 @@ pub struct Workspace {
last_leaders_by_pane: HashMap<WeakViewHandle<Pane>, PeerId>, last_leaders_by_pane: HashMap<WeakViewHandle<Pane>, PeerId>,
window_edited: bool, window_edited: bool,
active_call: Option<(ModelHandle<ActiveCall>, Vec<gpui::Subscription>)>, active_call: Option<(ModelHandle<ActiveCall>, Vec<gpui::Subscription>)>,
database_id: WorkspaceId,
_observe_current_user: Task<()>, _observe_current_user: Task<()>,
} }
@ -556,7 +560,7 @@ impl Workspace {
project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => { project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
this.update_window_title(cx); this.update_window_title(cx);
// TODO: Cache workspace_id on workspace and read from it here // TODO: Cache workspace_id on workspace and read from it here
this.serialize_workspace(None, cx); this.serialize_workspace(cx);
} }
project::Event::DisconnectedFromHost => { project::Event::DisconnectedFromHost => {
this.update_window_edited(cx); this.update_window_edited(cx);
@ -630,6 +634,12 @@ impl Workspace {
active_call = Some((call, subscriptions)); 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 { let mut this = Workspace {
modal: None, modal: None,
weak_self: weak_handle.clone(), weak_self: weak_handle.clone(),
@ -657,6 +667,7 @@ impl Workspace {
last_leaders_by_pane: Default::default(), last_leaders_by_pane: Default::default(),
window_edited: false, window_edited: false,
active_call, active_call,
database_id: id,
_observe_current_user, _observe_current_user,
}; };
this.project_remote_id_changed(project.read(cx).remote_id(), cx); 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<dyn ItemHandle>, cx: &mut ViewContext<Self>) { pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
let active_pane = self.active_pane().clone(); let active_pane = self.active_pane().clone();
Pane::add_item(self, &active_pane, item, true, true, None, cx); Pane::add_item(self, &active_pane, item, true, true, None, cx);
self.serialize_workspace(None, cx); self.serialize_workspace(cx);
} }
pub fn open_path( pub fn open_path(
@ -1522,7 +1533,7 @@ impl Workspace {
entry.remove(); entry.remove();
} }
} }
self.serialize_workspace(None, cx); self.serialize_workspace(cx);
} }
_ => {} _ => {}
} }
@ -1544,7 +1555,7 @@ impl Workspace {
pane.read(cx).active_item().map(|item| { pane.read(cx).active_item().map(|item| {
let new_pane = self.add_pane(cx); 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); Pane::add_item(self, &new_pane, clone, true, true, None, cx);
} }
self.center.split(&pane, &new_pane, direction).unwrap(); 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() self.project()
.read(cx) .read(cx)
.visible_worktrees(cx) .visible_worktrees(cx)
@ -2275,7 +2290,7 @@ impl Workspace {
} }
} }
fn serialize_workspace(&self, old_id: Option<WorkspaceId>, cx: &AppContext) { fn serialize_workspace(&self, cx: &AppContext) {
fn serialize_pane_handle( fn serialize_pane_handle(
pane_handle: &ViewHandle<Pane>, pane_handle: &ViewHandle<Pane>,
cx: &AppContext, cx: &AppContext,
@ -2320,7 +2335,8 @@ impl Workspace {
let center_group = build_serialized_pane_group(&self.center.root, cx); let center_group = build_serialized_pane_group(&self.center.root, cx);
let serialized_workspace = SerializedWorkspace { let serialized_workspace = SerializedWorkspace {
workspace_id: self.workspace_id(cx), id: self.database_id,
location: self.location(cx),
dock_position: self.dock.position(), dock_position: self.dock.position(),
dock_pane, dock_pane,
center_group, center_group,
@ -2328,7 +2344,7 @@ impl Workspace {
cx.background() cx.background()
.spawn(async move { .spawn(async move {
persistence::DB.save_workspace(old_id, &serialized_workspace); persistence::DB.save_workspace(&serialized_workspace);
}) })
.detach(); .detach();
} }
@ -2349,7 +2365,7 @@ impl Workspace {
.deserialize_to( .deserialize_to(
&project, &project,
&dock_pane_handle, &dock_pane_handle,
&serialized_workspace.workspace_id, serialized_workspace.id,
&workspace, &workspace,
&mut cx, &mut cx,
) )
@ -2359,12 +2375,7 @@ impl Workspace {
let (root, active_pane) = serialized_workspace let (root, active_pane) = serialized_workspace
.center_group .center_group
.deserialize( .deserialize(&project, serialized_workspace.id, &workspace, &mut cx)
&project,
&serialized_workspace.workspace_id,
&workspace,
&mut cx,
)
.await; .await;
// Remove old panes from workspace panes list // Remove old panes from workspace panes list

View file

@ -597,6 +597,8 @@ pub fn default_item_factory(
let working_directory = get_working_directory(workspace, cx, strategy); 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) Box::new(terminal_handle)
} }