Detailed changes
@@ -1,15 +1,20 @@
use crate::TerminalView;
+use db::kvp::KEY_VALUE_STORE;
use gpui::{
- actions, anyhow, elements::*, AppContext, Entity, Subscription, View, ViewContext, ViewHandle,
- WeakViewHandle, WindowContext,
+ actions, anyhow::Result, elements::*, serde_json, AppContext, AsyncAppContext, Entity,
+ Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext,
};
+use serde::{Deserialize, Serialize};
use settings::{settings_file::SettingsFile, Settings, TerminalDockPosition, WorkingDirectory};
-use util::ResultExt;
+use util::{ResultExt, TryFutureExt};
use workspace::{
dock::{DockPosition, Panel},
+ item::Item,
pane, DraggedItem, Pane, Workspace,
};
+const TERMINAL_PANEL_KEY: &'static str = "TerminalPanel";
+
actions!(terminal_panel, [ToggleFocus]);
pub fn init(cx: &mut AppContext) {
@@ -27,6 +32,7 @@ pub enum Event {
pub struct TerminalPanel {
pane: ViewHandle<Pane>,
workspace: WeakViewHandle<Workspace>,
+ pending_serialization: Task<Option<()>>,
_subscriptions: Vec<Subscription>,
}
@@ -86,10 +92,79 @@ impl TerminalPanel {
Self {
pane,
workspace: workspace.weak_handle(),
+ pending_serialization: Task::ready(None),
_subscriptions: subscriptions,
}
}
+ pub fn load(
+ workspace: WeakViewHandle<Workspace>,
+ cx: AsyncAppContext,
+ ) -> Task<Result<ViewHandle<Self>>> {
+ cx.spawn(|mut cx| async move {
+ let serialized_panel = if let Some(panel) = cx
+ .background()
+ .spawn(async move { KEY_VALUE_STORE.read_kvp(TERMINAL_PANEL_KEY) })
+ .await?
+ {
+ Some(serde_json::from_str::<SerializedTerminalPanel>(&panel)?)
+ } else {
+ None
+ };
+ let (panel, pane, items) = workspace.update(&mut cx, |workspace, cx| {
+ let panel = cx.add_view(|cx| TerminalPanel::new(workspace, cx));
+ let items = if let Some(serialized_panel) = serialized_panel.as_ref() {
+ panel.update(cx, |panel, cx| {
+ panel.pane.update(cx, |_, cx| {
+ serialized_panel
+ .items
+ .iter()
+ .map(|item_id| {
+ TerminalView::deserialize(
+ workspace.project().clone(),
+ workspace.weak_handle(),
+ workspace.database_id(),
+ *item_id,
+ cx,
+ )
+ })
+ .collect::<Vec<_>>()
+ })
+ })
+ } else {
+ Default::default()
+ };
+ let pane = panel.read(cx).pane.clone();
+ (panel, pane, items)
+ })?;
+
+ let items = futures::future::join_all(items).await;
+ workspace.update(&mut cx, |workspace, cx| {
+ let active_item_id = serialized_panel
+ .as_ref()
+ .and_then(|panel| panel.active_item_id);
+ let mut active_ix = None;
+ for item in items {
+ if let Some(item) = item.log_err() {
+ let item_id = item.id();
+ Pane::add_item(workspace, &pane, Box::new(item), false, false, None, cx);
+ if Some(item_id) == active_item_id {
+ active_ix = Some(pane.read(cx).items_len() - 1);
+ }
+ }
+ }
+
+ if let Some(active_ix) = active_ix {
+ pane.update(cx, |pane, cx| {
+ pane.activate_item(active_ix, false, false, cx)
+ });
+ }
+ })?;
+
+ Ok(panel)
+ })
+ }
+
fn handle_pane_event(
&mut self,
_pane: ViewHandle<Pane>,
@@ -97,6 +172,8 @@ impl TerminalPanel {
cx: &mut ViewContext<Self>,
) {
match event {
+ pane::Event::ActivateItem { .. } => self.serialize(cx),
+ pane::Event::RemoveItem { .. } => self.serialize(cx),
pane::Event::Remove => cx.emit(Event::Close),
pane::Event::ZoomIn => cx.emit(Event::ZoomIn),
pane::Event::ZoomOut => cx.emit(Event::ZoomOut),
@@ -131,10 +208,36 @@ impl TerminalPanel {
Pane::add_item(workspace, &pane, terminal, true, true, None, cx);
}
})?;
+ this.update(&mut cx, |this, cx| this.serialize(cx))?;
anyhow::Ok(())
})
.detach_and_log_err(cx);
}
+
+ fn serialize(&mut self, cx: &mut ViewContext<Self>) {
+ let items = self
+ .pane
+ .read(cx)
+ .items()
+ .map(|item| item.id())
+ .collect::<Vec<_>>();
+ let active_item_id = self.pane.read(cx).active_item().map(|item| item.id());
+ self.pending_serialization = cx.background().spawn(
+ async move {
+ KEY_VALUE_STORE
+ .write_kvp(
+ TERMINAL_PANEL_KEY.into(),
+ serde_json::to_string(&SerializedTerminalPanel {
+ items,
+ active_item_id,
+ })?,
+ )
+ .await?;
+ anyhow::Ok(())
+ }
+ .log_err(),
+ );
+ }
}
impl Entity for TerminalPanel {
@@ -255,3 +358,9 @@ impl Panel for TerminalPanel {
matches!(event, Event::Focus)
}
}
+
+#[derive(Serialize, Deserialize)]
+struct SerializedTerminalPanel {
+ items: Vec<usize>,
+ active_item_id: Option<usize>,
+}
@@ -361,7 +361,8 @@ pub struct AppState {
pub fs: Arc<dyn fs::Fs>,
pub build_window_options:
fn(Option<WindowBounds>, Option<uuid::Uuid>, &dyn Platform) -> WindowOptions<'static>,
- pub initialize_workspace: fn(&mut Workspace, bool, &Arc<AppState>, &mut ViewContext<Workspace>),
+ pub initialize_workspace:
+ fn(WeakViewHandle<Workspace>, bool, Arc<AppState>, AsyncAppContext) -> Task<Result<()>>,
pub background_actions: BackgroundActions,
}
@@ -383,7 +384,7 @@ impl AppState {
fs,
languages,
user_store,
- initialize_workspace: |_, _, _, _| {},
+ initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
build_window_options: |_, _, _| Default::default(),
background_actions: || &[],
})
@@ -730,31 +731,19 @@ impl Workspace {
))
});
- let build_workspace =
- |cx: &mut ViewContext<Workspace>,
- serialized_workspace: Option<SerializedWorkspace>| {
- let was_deserialized = serialized_workspace.is_some();
- let mut workspace = Workspace::new(
- serialized_workspace,
- workspace_id,
- project_handle.clone(),
- app_state.clone(),
- cx,
- );
- (app_state.initialize_workspace)(
- &mut workspace,
- was_deserialized,
- &app_state,
- cx,
- );
- workspace
- };
+ let was_deserialized = serialized_workspace.is_some();
let workspace = requesting_window_id
.and_then(|window_id| {
cx.update(|cx| {
cx.replace_root_view(window_id, |cx| {
- build_workspace(cx, serialized_workspace.take())
+ Workspace::new(
+ serialized_workspace.take(),
+ workspace_id,
+ project_handle.clone(),
+ app_state.clone(),
+ cx,
+ )
})
})
})
@@ -794,11 +783,28 @@ impl Workspace {
// Use the serialized workspace to construct the new window
cx.add_window(
(app_state.build_window_options)(bounds, display, cx.platform().as_ref()),
- |cx| build_workspace(cx, serialized_workspace),
+ |cx| {
+ Workspace::new(
+ serialized_workspace,
+ workspace_id,
+ project_handle.clone(),
+ app_state.clone(),
+ cx,
+ )
+ },
)
.1
});
+ (app_state.initialize_workspace)(
+ workspace.downgrade(),
+ was_deserialized,
+ app_state.clone(),
+ cx.clone(),
+ )
+ .await
+ .log_err();
+
let workspace = workspace.downgrade();
notify_if_database_failed(&workspace, &mut cx);
@@ -2740,7 +2746,7 @@ impl Workspace {
user_store: project.read(cx).user_store(),
fs: project.read(cx).fs().clone(),
build_window_options: |_, _, _| Default::default(),
- initialize_workspace: |_, _, _, _| {},
+ initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
background_actions: || &[],
});
Self::new(None, 0, project, app_state, cx)
@@ -3097,12 +3103,17 @@ pub fn join_remote_project(
let (_, workspace) = cx.add_window(
(app_state.build_window_options)(None, None, cx.platform().as_ref()),
- |cx| {
- let mut workspace = Workspace::new(None, 0, project, app_state.clone(), cx);
- (app_state.initialize_workspace)(&mut workspace, false, &app_state, cx);
- workspace
- },
+ |cx| Workspace::new(None, 0, project, app_state.clone(), cx),
);
+ (app_state.initialize_workspace)(
+ workspace.downgrade(),
+ false,
+ app_state.clone(),
+ cx.clone(),
+ )
+ .await
+ .log_err();
+
workspace.downgrade()
};
@@ -18,10 +18,11 @@ use feedback::{
use futures::StreamExt;
use gpui::{
actions,
+ anyhow::{self, Result},
geometry::vector::vec2f,
impl_actions,
platform::{Platform, PromptLevel, TitlebarOptions, WindowBounds, WindowKind, WindowOptions},
- AppContext, ViewContext,
+ AppContext, AsyncAppContext, Task, ViewContext, WeakViewHandle,
};
pub use lsp;
pub use project;
@@ -281,93 +282,105 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::AppContext) {
}
pub fn initialize_workspace(
- workspace: &mut Workspace,
+ workspace_handle: WeakViewHandle<Workspace>,
was_deserialized: bool,
- app_state: &Arc<AppState>,
- cx: &mut ViewContext<Workspace>,
-) {
- let workspace_handle = cx.handle();
- cx.subscribe(&workspace_handle, {
- move |workspace, _, event, cx| {
- if let workspace::Event::PaneAdded(pane) = event {
- pane.update(cx, |pane, cx| {
- pane.toolbar().update(cx, |toolbar, cx| {
- let breadcrumbs = cx.add_view(|_| Breadcrumbs::new(workspace));
- toolbar.add_item(breadcrumbs, cx);
- let buffer_search_bar = cx.add_view(BufferSearchBar::new);
- toolbar.add_item(buffer_search_bar, cx);
- let project_search_bar = cx.add_view(|_| ProjectSearchBar::new());
- toolbar.add_item(project_search_bar, cx);
- let submit_feedback_button = cx.add_view(|_| SubmitFeedbackButton::new());
- toolbar.add_item(submit_feedback_button, cx);
- let feedback_info_text = cx.add_view(|_| FeedbackInfoText::new());
- toolbar.add_item(feedback_info_text, cx);
- let lsp_log_item = cx.add_view(|_| {
- lsp_log::LspLogToolbarItemView::new(workspace.project().clone())
+ app_state: Arc<AppState>,
+ cx: AsyncAppContext,
+) -> Task<Result<()>> {
+ cx.spawn(|mut cx| async move {
+ workspace_handle.update(&mut cx, |workspace, cx| {
+ let workspace_handle = cx.handle();
+ cx.subscribe(&workspace_handle, {
+ move |workspace, _, event, cx| {
+ if let workspace::Event::PaneAdded(pane) = event {
+ pane.update(cx, |pane, cx| {
+ pane.toolbar().update(cx, |toolbar, cx| {
+ let breadcrumbs = cx.add_view(|_| Breadcrumbs::new(workspace));
+ toolbar.add_item(breadcrumbs, cx);
+ let buffer_search_bar = cx.add_view(BufferSearchBar::new);
+ toolbar.add_item(buffer_search_bar, cx);
+ let project_search_bar = cx.add_view(|_| ProjectSearchBar::new());
+ toolbar.add_item(project_search_bar, cx);
+ let submit_feedback_button =
+ cx.add_view(|_| SubmitFeedbackButton::new());
+ toolbar.add_item(submit_feedback_button, cx);
+ let feedback_info_text = cx.add_view(|_| FeedbackInfoText::new());
+ toolbar.add_item(feedback_info_text, cx);
+ let lsp_log_item = cx.add_view(|_| {
+ lsp_log::LspLogToolbarItemView::new(workspace.project().clone())
+ });
+ toolbar.add_item(lsp_log_item, cx);
+ })
});
- toolbar.add_item(lsp_log_item, cx);
- })
- });
- }
- }
- })
- .detach();
-
- cx.emit(workspace::Event::PaneAdded(workspace.active_pane().clone()));
-
- let collab_titlebar_item =
- cx.add_view(|cx| CollabTitlebarItem::new(workspace, &workspace_handle, cx));
- workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx);
-
- let project_panel = ProjectPanel::new(workspace, cx);
- let project_panel_position = project_panel.position(cx);
- workspace.add_panel(project_panel, cx);
- if !was_deserialized
- && workspace
- .project()
- .read(cx)
- .visible_worktrees(cx)
- .any(|tree| {
- tree.read(cx)
- .root_entry()
- .map_or(false, |entry| entry.is_dir())
+ }
+ }
})
- {
- workspace.toggle_dock(project_panel_position, cx);
- }
+ .detach();
- let terminal_panel = cx.add_view(|cx| TerminalPanel::new(workspace, cx));
- workspace.add_panel(terminal_panel, cx);
-
- let copilot = cx.add_view(|cx| copilot_button::CopilotButton::new(cx));
- let diagnostic_summary =
- cx.add_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx));
- let activity_indicator =
- activity_indicator::ActivityIndicator::new(workspace, app_state.languages.clone(), cx);
- let active_buffer_language =
- cx.add_view(|_| language_selector::ActiveBufferLanguage::new(workspace));
- let feedback_button =
- cx.add_view(|_| feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace));
- let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new());
- workspace.status_bar().update(cx, |status_bar, cx| {
- status_bar.add_left_item(diagnostic_summary, cx);
- status_bar.add_left_item(activity_indicator, cx);
- status_bar.add_right_item(feedback_button, cx);
- status_bar.add_right_item(copilot, cx);
- status_bar.add_right_item(active_buffer_language, cx);
- status_bar.add_right_item(cursor_position, cx);
- });
+ cx.emit(workspace::Event::PaneAdded(workspace.active_pane().clone()));
+
+ let collab_titlebar_item =
+ cx.add_view(|cx| CollabTitlebarItem::new(workspace, &workspace_handle, cx));
+ workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx);
+
+ let copilot = cx.add_view(|cx| copilot_button::CopilotButton::new(cx));
+ let diagnostic_summary =
+ cx.add_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx));
+ let activity_indicator = activity_indicator::ActivityIndicator::new(
+ workspace,
+ app_state.languages.clone(),
+ cx,
+ );
+ let active_buffer_language =
+ cx.add_view(|_| language_selector::ActiveBufferLanguage::new(workspace));
+ let feedback_button = cx.add_view(|_| {
+ feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace)
+ });
+ let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new());
+ workspace.status_bar().update(cx, |status_bar, cx| {
+ status_bar.add_left_item(diagnostic_summary, cx);
+ status_bar.add_left_item(activity_indicator, cx);
+ status_bar.add_right_item(feedback_button, cx);
+ status_bar.add_right_item(copilot, cx);
+ status_bar.add_right_item(active_buffer_language, cx);
+ status_bar.add_right_item(cursor_position, cx);
+ });
- auto_update::notify_of_any_new_update(cx.weak_handle(), cx);
+ auto_update::notify_of_any_new_update(cx.weak_handle(), cx);
- vim::observe_keystrokes(cx);
+ vim::observe_keystrokes(cx);
- cx.on_window_should_close(|workspace, cx| {
- if let Some(task) = workspace.close(&Default::default(), cx) {
- task.detach_and_log_err(cx);
- }
- false
- });
+ cx.on_window_should_close(|workspace, cx| {
+ if let Some(task) = workspace.close(&Default::default(), cx) {
+ task.detach_and_log_err(cx);
+ }
+ false
+ });
+
+ let project_panel = ProjectPanel::new(workspace, cx);
+ let project_panel_position = project_panel.position(cx);
+ workspace.add_panel(project_panel, cx);
+ if !was_deserialized
+ && workspace
+ .project()
+ .read(cx)
+ .visible_worktrees(cx)
+ .any(|tree| {
+ tree.read(cx)
+ .root_entry()
+ .map_or(false, |entry| entry.is_dir())
+ })
+ {
+ workspace.toggle_dock(project_panel_position, cx);
+ }
+ })?;
+
+ let terminal_panel = TerminalPanel::load(workspace_handle.clone(), cx.clone()).await?;
+ workspace_handle.update(&mut cx, |workspace, cx| {
+ workspace.add_panel(terminal_panel, cx)
+ })?;
+ Ok(())
+ })
}
pub fn build_window_options(