use std::{ collections::HashMap, path::{Path, PathBuf}, sync::{Arc, Weak}, }; use galaxyui::{Entity, EntityId, ModelContext, SingletonEntity, WindowId}; use crate::terminal::model::session::Session; /// The active terminal session in each window. The active session of a window is the current /// session of the most-recently-focused terminal pane of the active tab of the window's workspace. /// /// #### When to use `ActiveSession` /// Generally, if a more specific session is available, it should be preferred. For example, when /// opening a Markdown file from a file link in a block's output, that block's session should be /// the basis. However, sometimes there is no contextual session (such as when opening a file /// in Warp from Finder, or when starting from a cloud object). In that case, the `ActiveSession` /// might be used, but it's often still better to be context-independent. #[derive(Default)] pub struct ActiveSession { window_sessions: HashMap, } /// Active session information for an individual window. #[derive(Default)] struct WindowActiveSession { /// The [`Session`] model for the active session. This is a weak reference so that it doesn't /// prevent cleaning up the session when it closes, in case no other session is activated. session: Option>, /// The active session's working directory, if it's local. path_if_local: Option, /// The [`EntityId`]` for the [`TerminalView`] for the active session, if there is one. terminal_view_id: Option, } impl ActiveSession { /// The workspace's active session, if there is one. pub fn session(&self, window_id: WindowId) -> Option> { self.window_sessions .get(&window_id)? .session .as_ref()? .upgrade() } pub fn terminal_view_id(&self, window_id: WindowId) -> Option { self.window_sessions.get(&window_id)?.terminal_view_id } /// The current working directory of the active session, if it's local. pub fn path_if_local(&self, window_id: WindowId) -> Option<&Path> { self.window_sessions .get(&window_id)? .path_if_local .as_deref() } /// Set the current session, for use in tests. #[cfg(test)] pub fn set_session_for_test( &mut self, window_id: WindowId, session: Arc, path_if_local: Option>, terminal_view_id: Option, ctx: &mut ModelContext, ) { self.set_session_state( window_id, Some(session), path_if_local.map(Into::into), terminal_view_id, ctx, ); } pub(super) fn set_session_state( &mut self, window_id: WindowId, session: Option>, path_if_local: Option, terminal_view_id: Option, ctx: &mut ModelContext, ) { let window_state = self.window_sessions.entry(window_id).or_default(); let session = session.map(|session| Arc::downgrade(&session)); if window_state.session.is_some() != session.is_some() { window_state.session = session; ctx.notify(); } else if let Some((prev_session, next_session)) = window_state.session.as_ref().zip(session) { // Session IDs can't necessarily be compared across terminal panes, so check if the backing // allocation is the same. We can do this because each `Session` is a singleton. if !Weak::ptr_eq(prev_session, &next_session) { window_state.session = Some(next_session); ctx.notify(); } } if window_state.path_if_local != path_if_local { window_state.path_if_local = path_if_local; ctx.notify(); } if window_state.terminal_view_id != terminal_view_id { window_state.terminal_view_id = terminal_view_id; ctx.notify(); } } pub(super) fn close_workspace(&mut self, window_id: WindowId) { self.window_sessions.remove(&window_id); } } impl Entity for ActiveSession { type Event = (); } impl SingletonEntity for ActiveSession {}