Files
galaxy/app/src/session_management.rs
T

237 lines
7.4 KiB
Rust

use std::collections::HashSet;
use chrono::NaiveDateTime;
use warpui::{AppContext, Entity, EntityId, WindowId};
use crate::context_chips::prompt_snapshot::PromptSnapshot;
use crate::pane_group::PaneGroup;
use crate::terminal::model::blockgrid::BlockGrid;
use crate::terminal::shared_session::SharedSessionStatus;
use crate::{
pane_group::PaneId,
workspace::{PaneViewLocator, Workspace},
};
/// Contains session metadata, including a prompt and running command (if there is one).
#[derive(Clone)]
pub struct SessionNavigationData {
/// The prompt of the session.
prompt: String,
/// The various parts of the prompt, like virtual environment and working directory.
prompt_elements: SessionNavigationPromptElements,
/// A running commmand, if there is one.
command_context: CommandContext,
/// A `PaneViewLocator` to navigate to the session.
pane_view_locator: PaneViewLocator,
/// The id of the window the session is located in.
window_id: WindowId,
/// The timestamp of the last interaction.
last_focus_ts: Option<NaiveDateTime>,
/// Whether or not the session is in a read-only state.
is_read_only: bool,
/// The sharing status of the session.
shared_session_status: SharedSessionStatus,
}
impl SessionNavigationData {
/// Returns whether the session is the session identified by `session_id`.
pub fn is_for_session(&self, session_id: PaneId) -> bool {
session_id == self.pane_view_locator().pane_id
}
}
/// Contains prompt data for rendering in the command palette.
#[derive(Clone)]
pub struct SessionNavigationPromptElements {
/// The raw terminal grid of the PS1 prompt, populated when `honor_ps1` is
/// active. When present, the command palette renders this grid directly.
pub ps1_prompt_grid: Option<BlockGrid>,
/// A snapshot of the user's configured prompt chips and their current
/// values. Used as the default prompt representation in the command palette.
pub prompt_chip_snapshot: Option<PromptSnapshot>,
}
/// Represents the execution context of a session - what command or AI interaction
/// was last run or is currently running.
#[derive(Clone, Debug)]
pub enum CommandContext {
/// The last executed terminal command
LastRunCommand {
last_run_command: String,
mins_since_completion: Option<i64>,
},
/// The last completed AI interaction
LastRunAIBlock {
prompt: String, // The prompt that initiated the AI interaction
},
/// Currently running terminal command
RunningCommand { running_command: String },
/// Currently running AI interaction
RunningAIBlock {
prompt: String, // The prompt for the active AI conversation
},
/// No command context (e.g. just launched terminal)
None,
}
impl CommandContext {
pub fn a11y_description(&self) -> Option<String> {
match self {
Self::None => None,
Self::LastRunCommand {
last_run_command, ..
} => Some(format!("Last run command {}", last_run_command.clone())),
Self::LastRunAIBlock { prompt } => Some(format!("Last AI interaction: {prompt}")),
Self::RunningCommand { running_command } => {
Some(format!("Currently running {running_command}"))
}
Self::RunningAIBlock { prompt } => {
Some(format!("Currently running AI interaction: {prompt}"))
}
}
}
}
impl SessionNavigationData {
#[allow(clippy::too_many_arguments)]
pub fn new(
prompt: String,
prompt_elements: SessionNavigationPromptElements,
command_context: CommandContext,
pane_view_locator: PaneViewLocator,
last_focus_ts: Option<NaiveDateTime>,
is_read_only: bool,
window_id: WindowId,
shared_session_status: SharedSessionStatus,
) -> Self {
SessionNavigationData {
prompt,
prompt_elements,
command_context,
pane_view_locator,
last_focus_ts,
is_read_only,
window_id,
shared_session_status,
}
}
pub fn prompt(&self) -> &str {
&self.prompt
}
pub fn prompt_elements(&self) -> &SessionNavigationPromptElements {
&self.prompt_elements
}
pub fn command_context(&self) -> CommandContext {
self.command_context.clone()
}
pub fn pane_view_locator(&self) -> PaneViewLocator {
self.pane_view_locator
}
pub fn window_id(&self) -> WindowId {
self.window_id
}
pub fn last_focus_ts(&self) -> Option<NaiveDateTime> {
self.last_focus_ts
}
pub fn is_read_only(&self) -> bool {
self.is_read_only
}
pub fn shared_session_status(&self) -> SharedSessionStatus {
self.shared_session_status.clone()
}
/// Fetches all sessions currently open in the app.
pub fn all_sessions(app: &AppContext) -> impl Iterator<Item = SessionNavigationData> + '_ {
app.window_ids()
.filter_map(move |window_id| {
let workspaces = app.views_of_type::<Workspace>(window_id)?;
Some(workspaces.into_iter().flat_map(move |workspace| {
workspace.as_ref(app).workspace_sessions(window_id, app)
}))
})
.flatten()
}
}
pub struct RunningSessionSummary<'a> {
/// Does not include long running blocks for viewer of a shared session.
pub long_running_cmds: Vec<&'a SessionNavigationData>,
}
impl<'a> RunningSessionSummary<'a> {
pub fn new(sessions: &'a [SessionNavigationData]) -> Self {
let long_running_cmds: Vec<_> = sessions
.iter()
.filter(|session| {
matches!(
session.command_context(),
CommandContext::RunningCommand { .. } | CommandContext::RunningAIBlock { .. }
) && !session.shared_session_status().is_viewer()
&& !session.is_read_only()
})
.collect();
Self { long_running_cmds }
}
pub fn windows_running(&self) -> HashSet<WindowId> {
self.long_running_cmds
.iter()
.map(|session| session.window_id())
.collect()
}
pub fn tabs_running(&self) -> HashSet<EntityId> {
self.long_running_cmds
.iter()
.map(|session| session.pane_view_locator().pane_group_id)
.collect()
}
pub fn processes_in_window(&self, window_id: &WindowId) -> Vec<&SessionNavigationData> {
self.long_running_cmds
.iter()
.filter(|&session| session.window_id() == *window_id)
.cloned()
.collect()
}
}
pub enum SessionSource {
None,
Set {
active_pane_id: PaneId,
active_tab_id: EntityId,
active_window_id: WindowId,
},
}
impl Entity for SessionSource {
type Event = ();
}
pub fn num_shared_sessions(ctx: &AppContext) -> usize {
let mut num_shared_sessions = 0;
let window_ids: Vec<WindowId> = ctx.window_ids().collect();
for window_id in window_ids {
let Some(pane_group_views) = ctx.views_of_type::<PaneGroup>(window_id) else {
continue;
};
for pane_group_view in pane_group_views {
pane_group_view.read(ctx, |pane_group, ctx| {
num_shared_sessions += pane_group.number_of_shared_sessions(ctx);
})
}
}
num_shared_sessions
}