use std::path::Path;
use rmux_client::{connect, Connection};
use rmux_proto::{ErrorResponse, MoveWindowTarget, ResolveTargetType, Response};
use super::{
list_session_names, resolve_current_pane_target, resolve_current_session_target,
resolve_existing_window_target_or_current, resolve_session_listing_target,
resolve_session_target_or_current, resolve_session_target_spec, resolve_target_spec,
resolve_window_index_target_or_current_session, resolve_window_target_or_current,
resolve_window_target_spec, response_name_for_target, run_command_resolved,
unexpected_response, write_lines_output, ExitFailure,
};
use crate::cli_args::{
AlertSessionTargetArgs, KillWindowArgs, LinkWindowArgs, ListWindowsArgs, MoveWindowArgs,
NewWindowArgs, RenameWindowArgs, ResizeWindowArgs, RespawnWindowArgs, RotateWindowArgs,
SessionTargetArgs, SwapWindowArgs, TargetSpec, UnlinkWindowArgs, WindowTargetArgs,
};
pub(super) fn run_link_window(
args: LinkWindowArgs,
socket_path: &Path,
) -> Result<i32, ExitFailure> {
run_command_resolved(socket_path, "link-window", move |connection| {
let source = resolve_window_target_spec(connection, &args.source, false)?;
let target = resolve_window_index_target_or_current_session(
connection,
args.target.as_ref(),
"link-window",
)?;
connection
.link_window(
source,
target,
args.after,
args.before,
args.kill_target,
args.detached,
)
.map_err(ExitFailure::from_client)
})
}
pub(super) fn run_move_window(
args: MoveWindowArgs,
socket_path: &Path,
) -> Result<i32, ExitFailure> {
run_command_resolved(socket_path, "move-window", move |connection| {
let request = resolve_move_window_args(connection, args)?;
connection
.move_window(
request.source,
request.target,
request.renumber,
request.kill_destination,
request.detached,
)
.map_err(ExitFailure::from_client)
})
}
pub(super) fn run_swap_window(
args: SwapWindowArgs,
socket_path: &Path,
) -> Result<i32, ExitFailure> {
run_command_resolved(socket_path, "swap-window", move |connection| {
let source = resolve_window_target_spec(connection, &args.source, false)?;
let target =
resolve_window_target_or_current(connection, args.target.as_ref(), "swap-window")?;
connection
.swap_window(source, target, args.detached)
.map_err(ExitFailure::from_client)
})
}
pub(super) fn run_rotate_window(
args: RotateWindowArgs,
socket_path: &Path,
) -> Result<i32, ExitFailure> {
let direction = args.direction();
let restore_zoom = args.restore_zoom;
run_command_resolved(socket_path, "rotate-window", move |connection| {
let target =
resolve_window_target_or_current(connection, args.target.as_ref(), "rotate-window")?;
connection
.rotate_window_with_zoom(target, direction, restore_zoom)
.map_err(ExitFailure::from_client)
})
}
pub(super) fn run_resize_window(
args: ResizeWindowArgs,
socket_path: &Path,
) -> Result<i32, ExitFailure> {
let adjust = args.adjustment.unwrap_or(1);
let adjustment = if args.up {
Some(rmux_proto::ResizeWindowAdjustment::Up(adjust))
} else if args.down {
Some(rmux_proto::ResizeWindowAdjustment::Down(adjust))
} else if args.left {
Some(rmux_proto::ResizeWindowAdjustment::Left(adjust))
} else if args.right {
Some(rmux_proto::ResizeWindowAdjustment::Right(adjust))
} else {
None
};
run_command_resolved(socket_path, "resize-window", move |connection| {
let target =
resolve_window_target_or_current(connection, args.target.as_ref(), "resize-window")?;
connection
.resize_window(target, args.width, args.height, adjustment)
.map_err(ExitFailure::from_client)
})
}
pub(super) fn run_respawn_window(
args: RespawnWindowArgs,
socket_path: &Path,
) -> Result<i32, ExitFailure> {
let env = if args.environment.is_empty() {
None
} else {
Some(args.environment)
};
run_command_resolved(socket_path, "respawn-window", move |connection| {
let target =
resolve_window_target_or_current(connection, args.target.as_ref(), "respawn-window")?;
connection
.respawn_window_with_environment(
target,
args.kill,
env,
args.start_directory,
(!args.command.is_empty()).then_some(args.command),
)
.map_err(ExitFailure::from_client)
})
}
pub(super) fn run_unlink_window(
args: UnlinkWindowArgs,
socket_path: &Path,
) -> Result<i32, ExitFailure> {
run_command_resolved(socket_path, "unlink-window", move |connection| {
let target = resolve_existing_window_target_or_current(
connection,
args.target.as_ref(),
"unlink-window",
)?;
connection
.unlink_window(target, args.kill_if_last)
.map_err(ExitFailure::from_client)
})
}
struct ResolvedMoveWindowArgs {
source: Option<rmux_proto::WindowTarget>,
target: MoveWindowTarget,
renumber: bool,
kill_destination: bool,
detached: bool,
}
fn resolve_move_window_args(
connection: &mut rmux_client::Connection,
args: MoveWindowArgs,
) -> Result<ResolvedMoveWindowArgs, ExitFailure> {
if !args.reindex
&& (args.source.is_none()
|| !args
.target
.as_ref()
.and_then(|target| target.exact())
.is_some_and(|target| matches!(target, rmux_proto::Target::Window(_))))
{
return Err(ExitFailure::new(
1,
"move-window requires -s source-window and -t destination-window targets",
));
}
Ok(ResolvedMoveWindowArgs {
source: args
.source
.as_ref()
.map(|target| resolve_window_target_spec(connection, target, false))
.transpose()?,
target: if args.reindex {
MoveWindowTarget::Session(match args.target.as_ref() {
Some(target) => resolve_session_target_spec(connection, target, false)?,
None => resolve_current_session(connection)?,
})
} else {
MoveWindowTarget::Window(resolve_window_target_spec(
connection,
args.target.as_ref().expect("validated destination target"),
true,
)?)
},
renumber: args.reindex,
kill_destination: args.kill_target,
detached: args.detached,
})
}
fn resolve_current_session(
connection: &mut rmux_client::Connection,
) -> Result<rmux_proto::SessionName, ExitFailure> {
match connection
.resolve_target(None, rmux_proto::ResolveTargetType::Session, false, false)
.map_err(ExitFailure::from_client)?
{
rmux_proto::Response::ResolveTarget(response) => match response.target {
rmux_proto::Target::Session(session_name) => Ok(session_name),
other => Err(ExitFailure::new(
1,
format!(
"resolve-target produced {} where a session target was required",
super::response_name_for_target(&other)
),
)),
},
rmux_proto::Response::Error(rmux_proto::ErrorResponse { error }) => {
Err(ExitFailure::new(1, error.to_string()))
}
other => Err(super::unexpected_response("resolve-target", &other)),
}
}
pub(super) fn run_new_window(args: NewWindowArgs, socket_path: &Path) -> Result<i32, ExitFailure> {
run_command_resolved(socket_path, "new-window", move |connection| {
let insert_at_target = args.after || args.before;
let (target, target_window_index) = if insert_at_target {
resolve_new_window_placement_target(
connection,
args.target.as_ref(),
args.after,
"new-window",
)?
} else {
resolve_new_window_target_spec(connection, args.target.as_ref())?
};
connection
.new_window_at_with_environment(
target,
target_window_index,
args.name,
args.detached,
(!args.environment.is_empty()).then_some(args.environment),
args.start_directory,
(!args.command.is_empty()).then_some(args.command),
insert_at_target,
)
.map_err(ExitFailure::from_client)
})
}
fn resolve_new_window_placement_target(
connection: &mut Connection,
target: Option<&TargetSpec>,
after: bool,
command_name: &str,
) -> Result<(rmux_proto::SessionName, Option<u32>), ExitFailure> {
let window = match target {
Some(target) => resolve_window_target_spec(connection, target, false)?,
None => {
let pane = resolve_current_pane_target(connection, command_name)?;
rmux_proto::WindowTarget::with_window(pane.session_name().clone(), pane.window_index())
}
};
let window_index = if after {
window.window_index().checked_add(1).ok_or_else(|| {
ExitFailure::new(
1,
format!(
"window index space exhausted for session {}",
window.session_name()
),
)
})?
} else {
window.window_index()
};
Ok((window.session_name().clone(), Some(window_index)))
}
fn resolve_new_window_target_spec(
connection: &mut Connection,
target: Option<&TargetSpec>,
) -> Result<(rmux_proto::SessionName, Option<u32>), ExitFailure> {
let Some(target) = target else {
return resolve_current_session_target(connection).map(|session| (session, None));
};
match target.exact() {
Some(rmux_proto::Target::Session(session_name)) => {
return Ok((session_name.clone(), None));
}
Some(rmux_proto::Target::Window(window_target)) => {
return Ok((
window_target.session_name().clone(),
Some(window_target.window_index()),
));
}
Some(rmux_proto::Target::Pane(_)) => {}
None => {
if let Some(session_name) = new_window_session_only_target(target.raw())? {
return Ok((session_name, None));
}
}
}
match resolve_target_spec(connection, target, ResolveTargetType::Session, false, false)? {
rmux_proto::Target::Session(session_name) => Ok((session_name, None)),
other => Err(ExitFailure::new(
1,
format!(
"resolve-target produced {} where a new-window target was required",
response_name_for_target(&other)
),
)),
}
}
fn new_window_session_only_target(
raw_target: &str,
) -> Result<Option<rmux_proto::SessionName>, ExitFailure> {
let Some((session_name, window_part)) = raw_target.split_once(':') else {
return Ok(None);
};
if !window_part.is_empty() {
return Ok(None);
}
rmux_proto::SessionName::new(session_name.to_owned())
.map(Some)
.map_err(|error| ExitFailure::new(1, error.to_string()))
}
pub(super) fn run_kill_window(
args: KillWindowArgs,
socket_path: &Path,
) -> Result<i32, ExitFailure> {
run_command_resolved(socket_path, "kill-window", move |connection| {
let target =
resolve_window_target_or_current(connection, args.target.as_ref(), "kill-window")?;
connection
.kill_window(target, args.kill_others)
.map_err(ExitFailure::from_client)
})
}
pub(super) fn run_select_window(
args: WindowTargetArgs,
socket_path: &Path,
) -> Result<i32, ExitFailure> {
run_command_resolved(socket_path, "select-window", move |connection| {
let target =
resolve_window_target_or_current(connection, args.target.as_ref(), "select-window")?;
connection
.select_window(target)
.map_err(ExitFailure::from_client)
})
}
pub(super) fn run_rename_window(
args: RenameWindowArgs,
socket_path: &Path,
) -> Result<i32, ExitFailure> {
run_command_resolved(socket_path, "rename-window", move |connection| {
let target =
resolve_window_target_or_current(connection, args.target.as_ref(), "rename-window")?;
connection
.rename_window(target, args.new_name)
.map_err(ExitFailure::from_client)
})
}
pub(super) fn run_next_window(
args: AlertSessionTargetArgs,
socket_path: &Path,
) -> Result<i32, ExitFailure> {
run_command_resolved(socket_path, "next-window", move |connection| {
let target =
resolve_session_listing_target(connection, args.target.clone(), "next-window")?;
connection
.next_window(target, args.alerts_only)
.map_err(ExitFailure::from_client)
})
}
pub(super) fn run_previous_window(
args: AlertSessionTargetArgs,
socket_path: &Path,
) -> Result<i32, ExitFailure> {
run_command_resolved(socket_path, "previous-window", move |connection| {
let target =
resolve_session_listing_target(connection, args.target.clone(), "previous-window")?;
connection
.previous_window(target, args.alerts_only)
.map_err(ExitFailure::from_client)
})
}
pub(super) fn run_last_window(
args: SessionTargetArgs,
socket_path: &Path,
) -> Result<i32, ExitFailure> {
run_command_resolved(socket_path, "last-window", move |connection| {
let target =
resolve_session_target_or_current(connection, args.target.as_ref(), "last-window")?;
connection
.last_window(target)
.map_err(ExitFailure::from_client)
})
}
pub(super) fn run_list_windows(
args: ListWindowsArgs,
socket_path: &Path,
) -> Result<i32, ExitFailure> {
let mut connection = connect(socket_path)
.map_err(|error| ExitFailure::from_client_connect(socket_path, error))?;
let targets = if args.all_sessions {
list_session_names(&mut connection)?
} else {
vec![resolve_session_listing_target(
&mut connection,
args.target,
"list-windows",
)?]
};
let mut lines = Vec::new();
for target in targets {
let response = connection
.list_windows(target, args.format.clone())
.map_err(ExitFailure::from_client)?;
match response {
Response::ListWindows(response) => {
if args.all_sessions && args.format.is_none() {
lines.extend(response.windows.into_iter().map(|window| {
format!("{}:{}", window.target.session_name(), window.rendered)
}));
} else {
lines.extend(response.windows.into_iter().map(|window| window.rendered));
}
}
Response::Error(ErrorResponse { error }) => {
return Err(ExitFailure::new(1, error.to_string()))
}
other => return Err(unexpected_response("list-windows", &other)),
}
}
write_lines_output(&lines)
}