mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-12 13:24:19 +00:00
Began program manager, made terminal modals per-window
This commit is contained in:
parent
d189972a0d
commit
1375c5f1a1
3 changed files with 93 additions and 71 deletions
|
@ -1,11 +1,6 @@
|
||||||
use std::{
|
use gpui::{ModelHandle, ViewContext};
|
||||||
any::TypeId,
|
|
||||||
collections::{HashMap, HashSet},
|
|
||||||
};
|
|
||||||
|
|
||||||
use gpui::{AnyWeakModelHandle, Entity, ModelHandle, ViewContext, WeakModelHandle};
|
|
||||||
use settings::{Settings, WorkingDirectory};
|
use settings::{Settings, WorkingDirectory};
|
||||||
use workspace::Workspace;
|
use workspace::{programs::ProgramManager, Workspace};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
terminal_container_view::{
|
terminal_container_view::{
|
||||||
|
@ -14,73 +9,20 @@ use crate::{
|
||||||
Event, Terminal,
|
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>) {
|
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
|
// Pull the terminal connection out of the global if it has been stored
|
||||||
let possible_terminal =
|
let possible_terminal = ProgramManager::remove::<Terminal, _>(window, cx);
|
||||||
cx.update_default_global::<Option<StoredTerminal>, _, _>(|possible_connection, _| {
|
|
||||||
possible_connection.take()
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(StoredTerminal(stored_terminal)) = possible_terminal {
|
if let Some(terminal_handle) = possible_terminal {
|
||||||
workspace.toggle_modal(cx, |_, cx| {
|
workspace.toggle_modal(cx, |_, cx| {
|
||||||
// Create a view from the stored connection if the terminal modal is not already shown
|
// 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
|
// Toggle Modal will dismiss the terminal modal if it is currently shown, so we must
|
||||||
// store the terminal back in the global
|
// 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 {
|
} else {
|
||||||
// No connection was stored, create a new terminal
|
// No connection was stored, create a new terminal
|
||||||
if let Some(closed_terminal_handle) = workspace.toggle_modal(cx, |workspace, cx| {
|
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();
|
cx.subscribe(&terminal_handle, on_event).detach();
|
||||||
// Set the global immediately if terminal construction was successful,
|
// Set the global immediately if terminal construction was successful,
|
||||||
// in case the user opens the command palette
|
// in case the user opens the command palette
|
||||||
cx.set_global::<Option<StoredTerminal>>(Some(StoredTerminal(
|
ProgramManager::insert_or_replace::<Terminal, _>(window, terminal_handle, cx);
|
||||||
terminal_handle.clone(),
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this
|
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) =
|
if let TerminalContainerContent::Connected(connected) =
|
||||||
&closed_terminal_handle.read(cx).content
|
&closed_terminal_handle.read(cx).content
|
||||||
{
|
{
|
||||||
let terminal_handle = connected.read(cx).handle();
|
let terminal_handle = connected.read(cx).handle();
|
||||||
// Set the global immediately if terminal construction was successful,
|
// Set the global immediately if terminal construction was successful,
|
||||||
// in case the user opens the command palette
|
// 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
|
// Dismiss the modal if the terminal quit
|
||||||
if let Event::CloseTerminal = event {
|
if let Event::CloseTerminal = event {
|
||||||
cx.set_global::<Option<StoredTerminal>>(None);
|
ProgramManager::remove::<Terminal, _>(cx.window_id(), cx);
|
||||||
|
|
||||||
if workspace.modal::<TerminalContainer>().is_some() {
|
if workspace.modal::<TerminalContainer>().is_some() {
|
||||||
workspace.dismiss_modal(cx)
|
workspace.dismiss_modal(cx)
|
||||||
}
|
}
|
||||||
|
|
77
crates/workspace/src/programs.rs
Normal file
77
crates/workspace/src/programs.rs
Normal file
|
@ -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.
|
/// specific locations.
|
||||||
pub mod pane;
|
pub mod pane;
|
||||||
pub mod pane_group;
|
pub mod pane_group;
|
||||||
|
pub mod programs;
|
||||||
pub mod searchable;
|
pub mod searchable;
|
||||||
pub mod sidebar;
|
pub mod sidebar;
|
||||||
mod status_bar;
|
mod status_bar;
|
||||||
|
@ -36,6 +37,7 @@ use log::error;
|
||||||
pub use pane::*;
|
pub use pane::*;
|
||||||
pub use pane_group::*;
|
pub use pane_group::*;
|
||||||
use postage::prelude::Stream;
|
use postage::prelude::Stream;
|
||||||
|
use programs::ProgramManager;
|
||||||
use project::{fs, Fs, Project, ProjectEntryId, ProjectPath, ProjectStore, Worktree, WorktreeId};
|
use project::{fs, Fs, Project, ProjectEntryId, ProjectPath, ProjectStore, Worktree, WorktreeId};
|
||||||
use searchable::SearchableItemHandle;
|
use searchable::SearchableItemHandle;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
@ -146,6 +148,8 @@ impl_actions!(workspace, [ToggleProjectOnline, ActivatePane]);
|
||||||
pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
|
pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
|
||||||
pane::init(cx);
|
pane::init(cx);
|
||||||
|
|
||||||
|
cx.set_global(ProgramManager::new());
|
||||||
|
|
||||||
cx.add_global_action(open);
|
cx.add_global_action(open);
|
||||||
cx.add_global_action({
|
cx.add_global_action({
|
||||||
let app_state = Arc::downgrade(&app_state);
|
let app_state = Arc::downgrade(&app_state);
|
||||||
|
|
Loading…
Reference in a new issue