use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use tokio::runtime::Runtime;
use tokio::sync::RwLock;
use tokio::task::JoinHandle;
use crate::config::Config;
use crate::pane::bell::BellState;
use crate::pane::mouse::MouseState;
use crate::pane::render_cache::RenderCache;
use crate::scroll_state::ScrollState;
use crate::session_logger::{SharedSessionLogger, create_shared_logger};
use crate::tab::{
apply_login_shell_flag, build_shell_env, configure_terminal_from_config, get_shell_command,
};
use crate::terminal::TerminalManager;
use crate::ui_constants::VISUAL_BELL_FLASH_DURATION_MS;
use super::bounds::PaneBounds;
use super::common::{PaneBackground, PaneId, RestartState};
pub struct Pane {
pub id: PaneId,
pub terminal: Arc<RwLock<TerminalManager>>,
pub scroll_state: ScrollState,
pub mouse: MouseState,
pub bell: BellState,
pub cache: RenderCache,
pub refresh_task: Option<JoinHandle<()>>,
pub working_directory: Option<String>,
pub last_activity_time: std::time::Instant,
pub last_seen_generation: u64,
pub anti_idle_last_activity: std::time::Instant,
pub anti_idle_last_generation: u64,
pub silence_notified: bool,
pub exit_notified: bool,
pub session_logger: SharedSessionLogger,
pub bounds: PaneBounds,
pub background: PaneBackground,
pub restart_state: Option<RestartState>,
pub is_active: Arc<AtomicBool>,
pub(crate) shutdown_fast: bool,
}
impl Pane {
pub fn new(
id: PaneId,
config: &Config,
_runtime: Arc<Runtime>,
working_directory: Option<String>,
) -> anyhow::Result<Self> {
let mut terminal = TerminalManager::new_with_scrollback(
config.cols,
config.rows,
config.scrollback.scrollback_lines,
)?;
configure_terminal_from_config(&mut terminal, config);
let work_dir = working_directory
.as_deref()
.or(config.working_directory.as_deref());
#[allow(unused_mut)] let (shell_cmd, mut shell_args) = get_shell_command(config);
apply_login_shell_flag(&mut shell_args, config);
let shell_args_deref = shell_args.as_deref();
let shell_env = build_shell_env(config.shell_env.as_ref());
terminal.spawn_custom_shell_with_dir(
&shell_cmd,
shell_args_deref,
work_dir,
shell_env.as_ref(),
)?;
let session_logger = create_shared_logger();
let terminal = Arc::new(RwLock::new(terminal));
Ok(Self {
id,
terminal,
scroll_state: ScrollState::new(),
mouse: MouseState::new(),
bell: BellState::new(),
cache: RenderCache::new(),
refresh_task: None,
working_directory: working_directory.or_else(|| config.working_directory.clone()),
last_activity_time: std::time::Instant::now(),
last_seen_generation: 0,
anti_idle_last_activity: std::time::Instant::now(),
anti_idle_last_generation: 0,
silence_notified: false,
exit_notified: false,
session_logger,
bounds: PaneBounds::default(),
background: PaneBackground::new(),
restart_state: None,
is_active: Arc::new(AtomicBool::new(false)),
shutdown_fast: false,
})
}
pub fn new_with_command(
id: PaneId,
config: &Config,
_runtime: Arc<Runtime>,
working_directory: Option<String>,
command: String,
args: Vec<String>,
) -> anyhow::Result<Self> {
let mut terminal = TerminalManager::new_with_scrollback(
config.cols,
config.rows,
config.scrollback.scrollback_lines,
)?;
configure_terminal_from_config(&mut terminal, config);
let work_dir = working_directory
.as_deref()
.or(config.working_directory.as_deref());
let shell_env = build_shell_env(config.shell_env.as_ref());
terminal.spawn_custom_shell_with_dir(
&command,
Some(args.as_slice()),
work_dir,
shell_env.as_ref(),
)?;
let session_logger = create_shared_logger();
let terminal = Arc::new(RwLock::new(terminal));
Ok(Self {
id,
terminal,
scroll_state: ScrollState::new(),
mouse: MouseState::new(),
bell: BellState::new(),
cache: RenderCache::new(),
refresh_task: None,
working_directory: working_directory.or_else(|| config.working_directory.clone()),
last_activity_time: std::time::Instant::now(),
last_seen_generation: 0,
anti_idle_last_activity: std::time::Instant::now(),
anti_idle_last_generation: 0,
silence_notified: false,
exit_notified: false,
session_logger,
bounds: PaneBounds::default(),
background: PaneBackground::new(),
restart_state: None,
is_active: Arc::new(AtomicBool::new(false)),
shutdown_fast: false,
})
}
pub fn new_wrapping_terminal(
id: PaneId,
terminal: Arc<RwLock<TerminalManager>>,
working_directory: Option<String>,
is_active: Arc<AtomicBool>,
) -> Self {
let session_logger = create_shared_logger();
Self {
id,
terminal,
scroll_state: ScrollState::new(),
mouse: MouseState::new(),
bell: BellState::new(),
cache: RenderCache::new(),
refresh_task: None,
working_directory,
last_activity_time: std::time::Instant::now(),
last_seen_generation: 0,
anti_idle_last_activity: std::time::Instant::now(),
anti_idle_last_generation: 0,
silence_notified: false,
exit_notified: false,
session_logger,
bounds: PaneBounds::default(),
background: PaneBackground::new(),
restart_state: None,
is_active,
shutdown_fast: false,
}
}
pub fn new_for_tmux(
id: PaneId,
config: &Config,
_runtime: Arc<Runtime>,
) -> anyhow::Result<Self> {
let mut terminal = TerminalManager::new_with_scrollback(
config.cols,
config.rows,
config.scrollback.scrollback_lines,
)?;
configure_terminal_from_config(&mut terminal, config);
let session_logger = create_shared_logger();
let terminal = Arc::new(RwLock::new(terminal));
Ok(Self {
id,
terminal,
scroll_state: ScrollState::new(),
mouse: MouseState::new(),
bell: BellState::new(),
cache: RenderCache::new(),
refresh_task: None,
working_directory: None,
last_activity_time: std::time::Instant::now(),
last_seen_generation: 0,
anti_idle_last_activity: std::time::Instant::now(),
anti_idle_last_generation: 0,
silence_notified: false,
exit_notified: false,
session_logger,
bounds: PaneBounds::default(),
background: PaneBackground::new(),
restart_state: None,
is_active: Arc::new(AtomicBool::new(false)),
shutdown_fast: false,
})
}
pub fn is_bell_active(&self) -> bool {
if let Some(flash_start) = self.bell.visual_flash {
flash_start.elapsed().as_millis() < VISUAL_BELL_FLASH_DURATION_MS
} else {
false
}
}
pub fn is_running(&self) -> bool {
if let Ok(term) = self.terminal.try_write() {
let running = term.is_running();
if !running {
crate::debug_info!(
"PANE_EXIT",
"Pane {} terminal detected as NOT running (shell exited)",
self.id
);
}
running
} else {
true }
}
pub fn get_cwd(&self) -> Option<String> {
if let Ok(term) = self.terminal.try_write() {
term.shell_integration_cwd()
} else {
self.working_directory.clone()
}
}
pub fn set_background(&mut self, background: PaneBackground) {
self.background = background;
}
pub fn background(&self) -> &PaneBackground {
&self.background
}
pub fn set_background_image(&mut self, path: Option<String>) {
self.background.image_path = path;
}
pub fn get_background_image(&self) -> Option<&str> {
self.background.image_path.as_deref()
}
}
impl Drop for Pane {
fn drop(&mut self) {
if self.shutdown_fast {
log::info!(
"Fast-dropping pane {} (cleanup handled externally)",
self.id
);
return;
}
log::info!("Dropping pane {}", self.id);
if let Some(ref mut logger) = *self.session_logger.lock() {
match logger.stop() {
Ok(path) => {
log::info!("Session log saved to: {:?}", path);
}
Err(e) => {
log::warn!("Failed to stop session logging: {}", e);
}
}
}
self.stop_refresh_task();
std::thread::sleep(std::time::Duration::from_millis(50));
if let Ok(mut term) = self.terminal.try_write()
&& term.is_running()
{
log::info!("Killing terminal for pane {}", self.id);
let _ = term.kill();
}
}
}