use std::path::Path;
use rmux_proto::{PaneTarget, RmuxError, SplitDirection, SplitWindowResponse, SplitWindowTarget};
use crate::pane_io::{PaneAlertCallback, PaneExitCallback};
use crate::pane_terminal_lookup::missing_pane_terminal;
use crate::pane_terminal_process::open_pane_terminal;
use crate::terminal::TerminalProfile;
use super::super::lifecycle_state::terminal_size_from_geometry;
use super::super::{
pane_terminal_geometry_for_session, session_not_found, HandlerState, PaneLifecycleSpawn,
PaneOutputSpawn,
};
#[cfg(windows)]
use super::clone_terminal_for_exit_watcher;
use super::clone_terminal_for_output_reader;
use super::preview::{preview_split, split_window_internal_direction, split_window_session_name};
impl HandlerState {
#[allow(clippy::too_many_arguments)]
pub(crate) fn split_window(
&mut self,
target: SplitWindowTarget,
direction: SplitDirection,
before: bool,
socket_path: &Path,
environment_overrides: Option<&[String]>,
command: Option<&[String]>,
pane_alert_callback: Option<PaneAlertCallback>,
pane_exit_callback: Option<PaneExitCallback>,
) -> Result<SplitWindowResponse, RmuxError> {
let session_name = split_window_session_name(&target).clone();
let internal_direction = split_window_internal_direction(direction);
let previous_session = self
.sessions
.session(&session_name)
.cloned()
.ok_or_else(|| session_not_found(&session_name))?;
let (window_index, new_pane_index, _preview_pane_geometry) =
preview_split(&self.sessions, &target, internal_direction, before)?;
let runtime_session_name =
self.runtime_session_name_for_window(&session_name, window_index);
let new_pane_id = self.sessions.allocate_pane_id();
let (session_id, window_id, new_pane_id, new_pane_geometry, requested_cwd) = self
.commit_split_into_session(
&session_name,
&target,
window_index,
new_pane_id,
new_pane_index,
internal_direction,
before,
)?;
let profile = TerminalProfile::for_session(
&self.environment,
&self.options,
&session_name,
session_id.as_u32(),
socket_path,
true,
environment_overrides,
Some(new_pane_id),
requested_cwd.as_deref(),
)?;
let runtime_window_name = profile.runtime_window_name(command);
let lifecycle_cwd = profile.cwd().to_path_buf();
let terminal = open_pane_terminal(
new_pane_geometry,
profile,
runtime_window_name.clone(),
command,
)?;
let pid = terminal.pid();
let output_reader =
match clone_terminal_for_output_reader(&terminal, &session_name, new_pane_id) {
Ok(output_reader) => output_reader,
Err(error) => {
self.replace_session(&session_name, previous_session)?;
return Err(error);
}
};
#[cfg(windows)]
let exit_watcher =
match clone_terminal_for_exit_watcher(&terminal, &session_name, new_pane_id) {
Ok(exit_watcher) => exit_watcher,
Err(error) => {
self.replace_session(&session_name, previous_session)?;
return Err(error);
}
};
if let Err(error) = self.terminals.insert_pane(
runtime_session_name.clone(),
new_pane_id,
window_index,
new_pane_index,
terminal,
) {
self.replace_session(&session_name, previous_session)?;
return Err(error);
}
if let Err(error) = self.insert_pane_output(
&runtime_session_name,
new_pane_id,
PaneOutputSpawn {
geometry: new_pane_geometry,
output_reader,
#[cfg(windows)]
exit_watcher: Some(exit_watcher),
pane_alert_callback,
pane_exit_callback,
},
) {
let _ = self
.terminals
.remove_pane(&runtime_session_name, new_pane_id);
self.replace_session(&session_name, previous_session)?;
return Err(error);
}
if let Err(error) = self.resize_terminals(&session_name) {
let rollback_target =
PaneTarget::with_window(session_name.clone(), window_index, new_pane_index);
self.remove_pane_output(&runtime_session_name, new_pane_id);
if self
.terminals
.remove_pane(&runtime_session_name, new_pane_id)
.is_none()
{
return Err(RmuxError::Server(format!(
"failed to roll back session {session_name} after {error}: missing pane terminal for {rollback_target}"
)));
}
self.restore_session_after_resize_error(&session_name, previous_session, &error)?;
return Err(error);
}
let sessions_to_synchronize = self
.window_link_slots_for(&session_name, window_index)
.into_iter()
.map(|slot| slot.session_name)
.collect::<Vec<_>>();
self.synchronize_linked_window_from_slot(&session_name, window_index)?;
for synchronized_session in sessions_to_synchronize {
self.synchronize_session_group_from(&synchronized_session)?;
}
self.record_pane_lifecycle_spawn(PaneLifecycleSpawn {
session_id,
window_id,
pane_id: new_pane_id,
command: command.map(<[String]>::to_vec),
working_directory: Some(lifecycle_cwd),
private_environment: environment_overrides.map(<[String]>::to_vec),
dimensions: terminal_size_from_geometry(new_pane_geometry),
pid: Some(pid),
});
let output_sequence = self.pane_output_generation(&runtime_session_name, new_pane_id);
self.update_pane_lifecycle_output_sequence(new_pane_id, output_sequence);
self.sync_pane_lifecycle_dimensions_for_session(&session_name);
Ok(SplitWindowResponse {
pane: PaneTarget::with_window(session_name, window_index, new_pane_index),
})
}
#[allow(clippy::too_many_arguments)]
fn commit_split_into_session(
&mut self,
session_name: &rmux_proto::SessionName,
target: &SplitWindowTarget,
window_index: u32,
new_pane_id: rmux_core::PaneId,
expected_pane_index: u32,
direction: SplitDirection,
before: bool,
) -> Result<
(
rmux_core::SessionId,
rmux_core::WindowId,
rmux_core::PaneId,
rmux_core::PaneGeometry,
Option<std::path::PathBuf>,
),
RmuxError,
> {
let session = self
.sessions
.session_mut(session_name)
.ok_or_else(|| session_not_found(session_name))?;
let (target_window_index, target_pane_index) = match target {
SplitWindowTarget::Session(_) => {
(session.active_window_index(), session.active_pane_index())
}
SplitWindowTarget::Pane(pane) => (pane.window_index(), pane.pane_index()),
};
let committed_index = session.split_pane_in_window_with_id_and_direction_before(
target_window_index,
target_pane_index,
new_pane_id,
direction,
before,
)?;
debug_assert_eq!(committed_index, expected_pane_index);
let pane = session
.window_at(window_index)
.expect("split target window must exist")
.pane(committed_index)
.ok_or_else(|| missing_pane_terminal(session_name, window_index, committed_index))?;
let pane_id = pane.id();
let pane_geometry =
pane_terminal_geometry_for_session(session, &self.options, pane.geometry());
let window_id = session
.window_at(window_index)
.expect("split target window must exist")
.id();
Ok((
session.id(),
window_id,
pane_id,
pane_geometry,
session.cwd().map(std::path::Path::to_path_buf),
))
}
}