Automatically close terminal dock when the last terminal was closed

Antonio Scandurra created

Change summary

crates/terminal_view/src/terminal_panel.rs | 68 ++++++++++++++++-------
crates/workspace/src/dock.rs               | 15 ++++-
crates/workspace/src/workspace.rs          | 29 +++++++++-
3 files changed, 85 insertions(+), 27 deletions(-)

Detailed changes

crates/terminal_view/src/terminal_panel.rs 🔗

@@ -1,44 +1,66 @@
-use gpui::{elements::*, Entity, ModelHandle, View, ViewContext, ViewHandle, WeakViewHandle};
+use crate::TerminalView;
+use gpui::{
+    elements::*, AppContext, Entity, ModelHandle, Subscription, View, ViewContext, ViewHandle,
+    WeakViewHandle,
+};
 use project::Project;
 use settings::{Settings, WorkingDirectory};
 use util::ResultExt;
-use workspace::{dock::Panel, DraggedItem, Pane, Workspace};
+use workspace::{dock::Panel, pane, DraggedItem, Pane, Workspace};
 
-use crate::TerminalView;
+pub enum Event {
+    Close,
+}
 
 pub struct TerminalPanel {
     project: ModelHandle<Project>,
     pane: ViewHandle<Pane>,
     workspace: WeakViewHandle<Workspace>,
+    _subscription: Subscription,
 }
 
 impl TerminalPanel {
     pub fn new(workspace: &Workspace, cx: &mut ViewContext<Self>) -> Self {
+        let pane = cx.add_view(|cx| {
+            let window_id = cx.window_id();
+            let mut pane = Pane::new(
+                workspace.weak_handle(),
+                workspace.app_state().background_actions,
+                cx,
+            );
+            pane.on_can_drop(move |drag_and_drop, cx| {
+                drag_and_drop
+                    .currently_dragged::<DraggedItem>(window_id)
+                    .map_or(false, |(_, item)| {
+                        item.handle.act_as::<TerminalView>(cx).is_some()
+                    })
+            });
+            pane
+        });
+        let subscription = cx.subscribe(&pane, Self::handle_pane_event);
         Self {
             project: workspace.project().clone(),
-            pane: cx.add_view(|cx| {
-                let window_id = cx.window_id();
-                let mut pane = Pane::new(
-                    workspace.weak_handle(),
-                    workspace.app_state().background_actions,
-                    cx,
-                );
-                pane.on_can_drop(move |drag_and_drop, cx| {
-                    drag_and_drop
-                        .currently_dragged::<DraggedItem>(window_id)
-                        .map_or(false, |(_, item)| {
-                            item.handle.act_as::<TerminalView>(cx).is_some()
-                        })
-                });
-                pane
-            }),
+            pane,
             workspace: workspace.weak_handle(),
+            _subscription: subscription,
+        }
+    }
+
+    fn handle_pane_event(
+        &mut self,
+        _pane: ViewHandle<Pane>,
+        event: &pane::Event,
+        cx: &mut ViewContext<Self>,
+    ) {
+        match event {
+            pane::Event::Remove => cx.emit(Event::Close),
+            _ => {}
         }
     }
 }
 
 impl Entity for TerminalPanel {
-    type Event = ();
+    type Event = Event;
 }
 
 impl View for TerminalPanel {
@@ -82,4 +104,8 @@ impl View for TerminalPanel {
     }
 }
 
-impl Panel for TerminalPanel {}
+impl Panel for TerminalPanel {
+    fn should_close_on_event(&self, event: &Event, _: &AppContext) -> bool {
+        matches!(event, Event::Close)
+    }
+}

crates/workspace/src/dock.rs 🔗

@@ -8,7 +8,10 @@ use settings::Settings;
 use std::rc::Rc;
 
 pub trait Panel: View {
-    fn should_activate_item_on_event(&self, _: &Self::Event, _: &AppContext) -> bool {
+    fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool {
+        false
+    }
+    fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool {
         false
     }
     fn should_show_badge(&self, _: &AppContext) -> bool {
@@ -53,6 +56,10 @@ impl From<&dyn PanelHandle> for AnyViewHandle {
     }
 }
 
+pub enum Event {
+    Close,
+}
+
 pub struct Dock {
     position: DockPosition,
     items: Vec<Item>,
@@ -138,7 +145,7 @@ impl Dock {
         let subscriptions = [
             cx.observe(&view, |_, _, cx| cx.notify()),
             cx.subscribe(&view, |this, view, event, cx| {
-                if view.read(cx).should_activate_item_on_event(event, cx) {
+                if view.read(cx).should_activate_on_event(event, cx) {
                     if let Some(ix) = this
                         .items
                         .iter()
@@ -146,6 +153,8 @@ impl Dock {
                     {
                         this.activate_item(ix, cx);
                     }
+                } else if view.read(cx).should_close_on_event(event, cx) {
+                    cx.emit(Event::Close);
                 }
             }),
         ];
@@ -183,7 +192,7 @@ impl Dock {
 }
 
 impl Entity for Dock {
-    type Event = ();
+    type Event = Event;
 }
 
 impl View for Dock {

crates/workspace/src/workspace.rs 🔗

@@ -461,7 +461,7 @@ pub struct Workspace {
     leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
     database_id: WorkspaceId,
     app_state: Arc<AppState>,
-    _window_subscriptions: [Subscription; 3],
+    _subscriptions: Vec<Subscription>,
     _apply_leader_updates: Task<Result<()>>,
     _observe_current_user: Task<Result<()>>,
 }
@@ -592,7 +592,7 @@ impl Workspace {
             active_call = Some((call, subscriptions));
         }
 
-        let subscriptions = [
+        let subscriptions = vec![
             cx.observe_fullscreen(|_, _, cx| cx.notify()),
             cx.observe_window_activation(Self::on_window_activation_changed),
             cx.observe_window_bounds(move |_, mut bounds, display, cx| {
@@ -612,6 +612,9 @@ impl Workspace {
                     .spawn(DB.set_window_bounds(workspace_id, bounds, display))
                     .detach_and_log_err(cx);
             }),
+            Self::register_dock(&left_dock, cx),
+            Self::register_dock(&bottom_dock, cx),
+            Self::register_dock(&right_dock, cx),
         ];
 
         let mut this = Workspace {
@@ -640,7 +643,7 @@ impl Workspace {
             _observe_current_user,
             _apply_leader_updates,
             leader_updates_tx,
-            _window_subscriptions: subscriptions,
+            _subscriptions: subscriptions,
         };
         this.project_remote_id_changed(project.read(cx).remote_id(), cx);
         cx.defer(|this, cx| this.update_window_title(cx));
@@ -1316,6 +1319,26 @@ impl Workspace {
         }
     }
 
+    fn register_dock(dock: &ViewHandle<Dock>, cx: &mut ViewContext<Self>) -> Subscription {
+        cx.subscribe(dock, Self::handle_dock_event)
+    }
+
+    fn handle_dock_event(
+        &mut self,
+        dock: ViewHandle<Dock>,
+        event: &dock::Event,
+        cx: &mut ViewContext<Self>,
+    ) {
+        match event {
+            dock::Event::Close => {
+                dock.update(cx, |dock, cx| dock.set_open(false, cx));
+                self.serialize_workspace(cx);
+                cx.focus_self();
+                cx.notify();
+            }
+        }
+    }
+
     pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext<Self>) {
         let dock = match dock_side {
             DockPosition::Left => &mut self.left_dock,