use rmux_client::Connection;
use rmux_proto::request::ListSessionsRequest;
use rmux_proto::{ErrorResponse, ResolveTargetType, Response, RmuxError};
use crate::cli_args::{parse_target_spec, TargetSpec};
use crate::cli_response::expect_command_output;
use super::{unexpected_response, ExitFailure};
pub(super) fn resolve_current_session_target(
connection: &mut Connection,
) -> Result<rmux_proto::SessionName, ExitFailure> {
match connection
.resolve_target(None, ResolveTargetType::Session, false, false)
.map_err(ExitFailure::from_client)?
{
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",
response_name_for_target(&other)
),
)),
},
Response::Error(ErrorResponse { error }) => Err(ExitFailure::new(1, error.to_string())),
other => Err(unexpected_response("resolve-target", &other)),
}
}
pub(super) fn list_session_names(
connection: &mut Connection,
) -> Result<Vec<rmux_proto::SessionName>, ExitFailure> {
let response = connection
.list_sessions(ListSessionsRequest {
format: Some("#{session_name}".to_owned()),
filter: None,
sort_order: None,
reversed: false,
})
.map_err(ExitFailure::from_client)?;
let output = expect_command_output(&response, "list-sessions")?;
String::from_utf8_lossy(output.stdout())
.lines()
.filter(|line| !line.is_empty())
.map(|line| {
rmux_proto::SessionName::new(line).map_err(|error| {
ExitFailure::new(
1,
format!("invalid session name from server '{line}': {error}"),
)
})
})
.collect()
}
pub(super) fn resolve_session_target_spec(
connection: &mut Connection,
target: &TargetSpec,
prefer_unattached: bool,
) -> Result<rmux_proto::SessionName, ExitFailure> {
match resolve_target_spec(
connection,
target,
ResolveTargetType::Session,
false,
prefer_unattached,
)? {
rmux_proto::Target::Session(session_name) => Ok(session_name),
other => Err(ExitFailure::new(
1,
format!(
"resolve-target produced {} where a session target was required",
response_name_for_target(&other)
),
)),
}
}
pub(super) fn resolve_window_target_spec(
connection: &mut Connection,
target: &TargetSpec,
window_index: bool,
) -> Result<rmux_proto::WindowTarget, ExitFailure> {
if let Some(rmux_proto::Target::Window(target)) = target.exact() {
return Ok(target.clone());
}
match resolve_target_spec(
connection,
target,
ResolveTargetType::Window,
window_index,
false,
)? {
rmux_proto::Target::Window(target) => Ok(target),
other => Err(ExitFailure::new(
1,
format!(
"resolve-target produced {} where a window target was required",
response_name_for_target(&other)
),
)),
}
}
pub(super) fn resolve_existing_window_target_spec(
connection: &mut Connection,
target: &TargetSpec,
) -> Result<rmux_proto::WindowTarget, ExitFailure> {
match resolve_target_spec(connection, target, ResolveTargetType::Window, false, false)? {
rmux_proto::Target::Window(target) => Ok(target),
other => Err(ExitFailure::new(
1,
format!(
"resolve-target produced {} where a window target was required",
response_name_for_target(&other)
),
)),
}
}
pub(super) fn resolve_window_target_or_current(
connection: &mut Connection,
target: Option<&TargetSpec>,
command_name: &str,
) -> Result<rmux_proto::WindowTarget, ExitFailure> {
match target {
Some(target) => resolve_window_target_spec(connection, target, false),
None => {
let pane = resolve_current_pane_target(connection, command_name)?;
Ok(rmux_proto::WindowTarget::with_window(
pane.session_name().clone(),
pane.window_index(),
))
}
}
}
pub(super) fn resolve_window_index_target_or_current_session(
connection: &mut Connection,
target: Option<&TargetSpec>,
command_name: &str,
) -> Result<rmux_proto::WindowTarget, ExitFailure> {
if let Some(target) = target {
return resolve_window_target_spec(connection, target, true);
}
let session_name = resolve_session_target_or_current(connection, None, command_name)?;
let implicit = parse_target_spec(&format!("{session_name}:"))
.map_err(|error| ExitFailure::new(1, error))?;
resolve_window_target_spec(connection, &implicit, true)
}
pub(super) fn resolve_pane_target_spec(
connection: &mut Connection,
target: &TargetSpec,
) -> Result<rmux_proto::PaneTarget, ExitFailure> {
match resolve_target_spec(connection, target, ResolveTargetType::Pane, false, false)? {
rmux_proto::Target::Pane(target) => Ok(target),
other => Err(ExitFailure::new(
1,
format!(
"resolve-target produced {} where a pane target was required",
response_name_for_target(&other)
),
)),
}
}
pub(super) fn resolve_existing_window_target_or_current(
connection: &mut Connection,
target: Option<&TargetSpec>,
command_name: &str,
) -> Result<rmux_proto::WindowTarget, ExitFailure> {
match target {
Some(target) => resolve_existing_window_target_spec(connection, target),
None => {
let pane = resolve_current_pane_target(connection, command_name)?;
Ok(rmux_proto::WindowTarget::with_window(
pane.session_name().clone(),
pane.window_index(),
))
}
}
}
pub(super) fn resolve_pane_target_or_current(
connection: &mut Connection,
target: Option<&TargetSpec>,
command_name: &str,
) -> Result<rmux_proto::PaneTarget, ExitFailure> {
match target {
Some(target) => resolve_pane_target_spec(connection, target),
None => resolve_current_pane_target(connection, command_name),
}
}
pub(super) fn resolve_split_window_target_spec(
connection: &mut Connection,
target: &TargetSpec,
) -> Result<rmux_proto::SplitWindowTarget, ExitFailure> {
Ok(rmux_proto::SplitWindowTarget::Pane(
resolve_pane_target_spec(connection, target)?,
))
}
pub(super) fn resolve_select_layout_target_spec(
connection: &mut Connection,
target: &TargetSpec,
) -> Result<rmux_proto::SelectLayoutTarget, ExitFailure> {
Ok(rmux_proto::SelectLayoutTarget::Window(
resolve_window_target_spec(connection, target, false)?,
))
}
pub(super) fn resolve_target_spec(
connection: &mut Connection,
target: &TargetSpec,
target_type: ResolveTargetType,
window_index: bool,
prefer_unattached: bool,
) -> Result<rmux_proto::Target, ExitFailure> {
let response = connection
.resolve_target(
Some(target.raw().to_owned()),
target_type,
window_index,
prefer_unattached,
)
.map_err(ExitFailure::from_client)?;
match response {
Response::ResolveTarget(response) => Ok(response.target),
Response::Error(ErrorResponse { error }) => Err(ExitFailure::new(
1,
target_resolution_error_message(&error, target_type, target.raw()),
)),
other => Err(unexpected_response("resolve-target", &other)),
}
}
fn target_resolution_error_message(
error: &RmuxError,
target_type: ResolveTargetType,
raw_target: &str,
) -> String {
match error {
RmuxError::InvalidTarget { reason, .. } if reason.starts_with("can't find ") => {
reason.clone()
}
RmuxError::InvalidTarget { reason, .. }
if target_type == ResolveTargetType::Pane
&& reason == "pane index does not exist in session" =>
{
format!("can't find pane: {}", pane_target_lookup_token(raw_target))
}
_ => error.to_string(),
}
}
fn pane_target_lookup_token(raw_target: &str) -> &str {
if raw_target.starts_with('%') {
return raw_target;
}
raw_target
.rsplit_once('.')
.map_or(raw_target, |(_, pane)| pane)
}
pub(super) fn display_panes_client_target_error(raw_target: &str) -> ExitFailure {
ExitFailure::new(
1,
format!(
"can't find client: {}",
raw_target.strip_suffix(':').unwrap_or(raw_target)
),
)
}
pub(super) fn response_name_for_target(target: &rmux_proto::Target) -> &'static str {
match target {
rmux_proto::Target::Session(_) => "session target",
rmux_proto::Target::Window(_) => "window target",
rmux_proto::Target::Pane(_) => "pane target",
}
}
pub(super) fn resolve_session_listing_target(
connection: &mut Connection,
target: Option<TargetSpec>,
command_name: &str,
) -> Result<rmux_proto::SessionName, ExitFailure> {
if let Some(target) = target {
return resolve_session_target_spec(connection, &target, false);
}
let _ = command_name;
resolve_current_session_target(connection)
}
pub(super) fn resolve_session_target_or_current(
connection: &mut Connection,
target: Option<&TargetSpec>,
command_name: &str,
) -> Result<rmux_proto::SessionName, ExitFailure> {
match target {
Some(target) => resolve_session_target_spec(connection, target, false),
None => {
let _ = command_name;
resolve_current_session_target(connection)
}
}
}
pub(super) fn resolve_current_pane_target(
connection: &mut Connection,
command_name: &str,
) -> Result<rmux_proto::PaneTarget, ExitFailure> {
let session_name = resolve_session_listing_target(connection, None, command_name)?;
let response = connection
.list_panes(
session_name.clone(),
Some("#{window_index}:#{pane_index}:#{window_active}:#{pane_active}".to_owned()),
)
.map_err(ExitFailure::from_client)?;
let output = expect_command_output(&response, "list-panes")?;
let stdout = String::from_utf8_lossy(output.stdout());
let active_line = stdout
.lines()
.find(|line| {
let mut fields = line.rsplit(':');
fields.next() == Some("1") && fields.next() == Some("1")
})
.ok_or_else(|| ExitFailure::new(1, "select-pane could not resolve the active pane"))?;
let mut parts = active_line.splitn(4, ':');
let window_index = parts
.next()
.and_then(|value| value.parse::<u32>().ok())
.ok_or_else(|| ExitFailure::new(1, "select-pane received an invalid window index"))?;
let pane_index = parts
.next()
.and_then(|value| value.parse::<u32>().ok())
.ok_or_else(|| ExitFailure::new(1, "select-pane received an invalid pane index"))?;
Ok(rmux_proto::PaneTarget::with_window(
session_name,
window_index,
pane_index,
))
}