@@ -1,11 +1,6 @@
-use std::{
- any::TypeId,
- collections::{HashMap, HashSet},
-};
-
-use gpui::{AnyWeakModelHandle, Entity, ModelHandle, ViewContext, WeakModelHandle};
+use gpui::{ModelHandle, ViewContext};
use settings::{Settings, WorkingDirectory};
-use workspace::Workspace;
+use workspace::{programs::ProgramManager, Workspace};
use crate::{
terminal_container_view::{
@@ -14,73 +9,20 @@ use crate::{
Event, Terminal,
};
-// TODO: Need to put this basic structure in workspace, and make 'program handles'
-// based off of the 'searchable item' pattern except with models this way, the workspace's clients
-// can register their models as programs.
-// Programs are:
-// - Kept alive by the program manager, they need to emit an event to get dropped from it
-// - Can be interacted with directly, (closed, activated), etc, bypassing associated view(s)
-// - Have special rendering methods that the program manager offers to fill out the status bar
-// - Can emit events for the program manager which:
-// - Add a jewel (notification, change, etc.)
-// - Drop the program
-// - ???
-// - Program Manager is kept in a global, listens for window drop so it can drop all it's program handles
-// - Start by making up the infrastructure, then just render the first item as the modal terminal in it's spot
-// update),
-
-struct ProgramManager {
- window_to_programs: HashMap<usize, HashSet<AnyWeakModelHandle>>,
-}
-
-impl ProgramManager {
- pub fn add_program<T: Entity>(&mut self, window: usize, program: WeakModelHandle<T>) {
- let mut programs = if let Some(programs) = self.window_to_programs.remove(&window) {
- programs
- } else {
- HashSet::default()
- };
-
- programs.insert(AnyWeakModelHandle::from(program));
- self.window_to_programs.insert(window, programs);
- }
-
- pub fn get_programs<T: Entity>(
- &self,
- window: &usize,
- ) -> impl Iterator<Item = WeakModelHandle<T>> + '_ {
- self.window_to_programs
- .get(window)
- .into_iter()
- .flat_map(|programs| {
- programs
- .iter()
- .filter(|program| program.model_type() != TypeId::of::<T>())
- .map(|program| program.downcast().unwrap())
- })
- }
-}
-
-#[derive(Debug)]
-struct StoredTerminal(ModelHandle<Terminal>);
-
pub fn deploy_modal(workspace: &mut Workspace, _: &DeployModal, cx: &mut ViewContext<Workspace>) {
- // cx.window_id()
+ let window = cx.window_id();
// Pull the terminal connection out of the global if it has been stored
- let possible_terminal =
- cx.update_default_global::<Option<StoredTerminal>, _, _>(|possible_connection, _| {
- possible_connection.take()
- });
+ let possible_terminal = ProgramManager::remove::<Terminal, _>(window, cx);
- if let Some(StoredTerminal(stored_terminal)) = possible_terminal {
+ if let Some(terminal_handle) = possible_terminal {
workspace.toggle_modal(cx, |_, cx| {
// Create a view from the stored connection if the terminal modal is not already shown
- cx.add_view(|cx| TerminalContainer::from_terminal(stored_terminal.clone(), true, cx))
+ cx.add_view(|cx| TerminalContainer::from_terminal(terminal_handle.clone(), true, cx))
});
// Toggle Modal will dismiss the terminal modal if it is currently shown, so we must
// store the terminal back in the global
- cx.set_global::<Option<StoredTerminal>>(Some(StoredTerminal(stored_terminal.clone())));
+ ProgramManager::insert_or_replace::<Terminal, _>(window, terminal_handle, cx);
} else {
// No connection was stored, create a new terminal
if let Some(closed_terminal_handle) = workspace.toggle_modal(cx, |workspace, cx| {
@@ -101,21 +43,19 @@ pub fn deploy_modal(workspace: &mut Workspace, _: &DeployModal, cx: &mut ViewCon
cx.subscribe(&terminal_handle, on_event).detach();
// Set the global immediately if terminal construction was successful,
// in case the user opens the command palette
- cx.set_global::<Option<StoredTerminal>>(Some(StoredTerminal(
- terminal_handle.clone(),
- )));
+ ProgramManager::insert_or_replace::<Terminal, _>(window, terminal_handle, cx);
}
this
}) {
- // Terminal modal was dismissed. Store terminal if the terminal view is connected
+ // Terminal modal was dismissed and the terminal view is connected, store the terminal
if let TerminalContainerContent::Connected(connected) =
&closed_terminal_handle.read(cx).content
{
let terminal_handle = connected.read(cx).handle();
// Set the global immediately if terminal construction was successful,
// in case the user opens the command palette
- cx.set_global::<Option<StoredTerminal>>(Some(StoredTerminal(terminal_handle)));
+ ProgramManager::insert_or_replace::<Terminal, _>(window, terminal_handle, cx);
}
}
}
@@ -129,7 +69,8 @@ pub fn on_event(
) {
// Dismiss the modal if the terminal quit
if let Event::CloseTerminal = event {
- cx.set_global::<Option<StoredTerminal>>(None);
+ ProgramManager::remove::<Terminal, _>(cx.window_id(), cx);
+
if workspace.modal::<TerminalContainer>().is_some() {
workspace.dismiss_modal(cx)
}
@@ -0,0 +1,77 @@
+// TODO: Need to put this basic structure in workspace, and make 'program handles'
+// based off of the 'searchable item' pattern except with models. This way, the workspace's clients
+// can register their models as programs with a specific identity and capable of notifying the workspace
+// Programs are:
+// - Kept alive by the program manager, they need to emit an event to get dropped from it
+// - Can be interacted with directly, (closed, activated, etc.) by the program manager, bypassing
+// associated view(s)
+// - Have special rendering methods that the program manager requires them to implement to fill out
+// the status bar
+// - Can emit events for the program manager which:
+// - Add a jewel (notification, change, etc.)
+// - Drop the program
+// - ???
+// - Program Manager is kept in a global, listens for window drop so it can drop all it's program handles
+
+use collections::HashMap;
+use gpui::{AnyModelHandle, Entity, ModelHandle, View, ViewContext};
+
+/// This struct is going to be the starting point for the 'program manager' feature that will
+/// eventually be implemented to provide a collaborative way of engaging with identity-having
+/// features like the terminal.
+pub struct ProgramManager {
+ // TODO: Make this a hashset or something
+ modals: HashMap<usize, AnyModelHandle>,
+}
+
+impl ProgramManager {
+ pub fn insert_or_replace<T: Entity, V: View>(
+ window: usize,
+ program: ModelHandle<T>,
+ cx: &mut ViewContext<V>,
+ ) -> Option<AnyModelHandle> {
+ cx.update_global::<ProgramManager, _, _>(|pm, _| {
+ pm.insert_or_replace_internal::<T>(window, program)
+ })
+ }
+
+ pub fn remove<T: Entity, V: View>(
+ window: usize,
+ cx: &mut ViewContext<V>,
+ ) -> Option<ModelHandle<T>> {
+ cx.update_global::<ProgramManager, _, _>(|pm, _| pm.remove_internal::<T>(window))
+ }
+
+ pub fn new() -> Self {
+ Self {
+ modals: Default::default(),
+ }
+ }
+
+ /// Inserts or replaces the model at the given location.
+ fn insert_or_replace_internal<T: Entity>(
+ &mut self,
+ window: usize,
+ program: ModelHandle<T>,
+ ) -> Option<AnyModelHandle> {
+ self.modals.insert(window, AnyModelHandle::from(program))
+ }
+
+ /// Remove the program associated with this window, if it's of the given type
+ fn remove_internal<T: Entity>(&mut self, window: usize) -> Option<ModelHandle<T>> {
+ let program = self.modals.remove(&window);
+ if let Some(program) = program {
+ if program.is::<T>() {
+ // Guaranteed to be some, but leave it in the option
+ // anyway for the API
+ program.downcast()
+ } else {
+ // Model is of the incorrect type, put it back
+ self.modals.insert(window, program);
+ None
+ }
+ } else {
+ None
+ }
+ }
+}
@@ -5,6 +5,7 @@
/// specific locations.
pub mod pane;
pub mod pane_group;
+pub mod programs;
pub mod searchable;
pub mod sidebar;
mod status_bar;
@@ -36,6 +37,7 @@ use log::error;
pub use pane::*;
pub use pane_group::*;
use postage::prelude::Stream;
+use programs::ProgramManager;
use project::{fs, Fs, Project, ProjectEntryId, ProjectPath, ProjectStore, Worktree, WorktreeId};
use searchable::SearchableItemHandle;
use serde::Deserialize;
@@ -146,6 +148,8 @@ impl_actions!(workspace, [ToggleProjectOnline, ActivatePane]);
pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
pane::init(cx);
+ cx.set_global(ProgramManager::new());
+
cx.add_global_action(open);
cx.add_global_action({
let app_state = Arc::downgrade(&app_state);