use std::fmt;
use serde::{Deserialize, Serialize};
use crate::types::{PaneRef, TerminalSizeSpec};
use crate::SessionName;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum ProcessCommandSpec {
Argv(Vec<String>),
Shell(String),
}
impl From<ProcessCommandSpec> for rmux_proto::ProcessCommand {
fn from(value: ProcessCommandSpec) -> Self {
match value {
ProcessCommandSpec::Argv(argv) => Self::Argv(argv),
ProcessCommandSpec::Shell(command) => Self::Shell(command),
}
}
}
impl ProcessCommandSpec {
pub(crate) fn is_empty(&self) -> bool {
match self {
Self::Argv(argv) => argv.is_empty() || argv.first().is_some_and(String::is_empty),
Self::Shell(command) => command.is_empty(),
}
}
}
#[derive(Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProcessSpec {
#[serde(default)]
pub command: Option<Vec<String>>,
#[serde(default)]
pub process_command: Option<ProcessCommandSpec>,
#[serde(default)]
pub environment: Option<Vec<String>>,
}
impl ProcessSpec {
#[must_use]
pub fn argv<I, S>(command: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Self {
process_command: Some(ProcessCommandSpec::Argv(
command.into_iter().map(Into::into).collect(),
)),
..Self::default()
}
}
#[must_use]
pub fn shell(command: impl Into<String>) -> Self {
Self {
process_command: Some(ProcessCommandSpec::Shell(command.into())),
..Self::default()
}
}
pub(crate) fn into_proto_parts(
self,
) -> (
Option<Vec<String>>,
Option<rmux_proto::ProcessCommand>,
Option<Vec<String>>,
) {
(
self.command,
self.process_command.map(rmux_proto::ProcessCommand::from),
self.environment,
)
}
}
impl fmt::Debug for ProcessSpec {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("ProcessSpec")
.field("command", &self.command)
.field("process_command", &self.process_command)
.finish_non_exhaustive()
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct NewSessionReuse {
#[serde(default)]
pub attach_if_exists: bool,
#[serde(default)]
pub detach_other_clients: bool,
#[serde(default)]
pub kill_other_clients: bool,
#[serde(default)]
pub flags: Option<Vec<String>>,
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AttachSessionReuse {
#[serde(default)]
pub detach_other_clients: bool,
#[serde(default)]
pub kill_other_clients: bool,
#[serde(default)]
pub read_only: bool,
#[serde(default)]
pub skip_environment_update: bool,
#[serde(default)]
pub flags: Option<Vec<String>>,
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ClientTerminalSpec {
#[serde(default)]
pub terminal_features: Vec<String>,
#[serde(default)]
pub utf8: bool,
}
impl From<ClientTerminalSpec> for rmux_proto::ClientTerminalContext {
fn from(value: ClientTerminalSpec) -> Self {
Self {
terminal_features: value.terminal_features,
utf8: value.utf8,
}
}
}
impl From<rmux_proto::ClientTerminalContext> for ClientTerminalSpec {
fn from(value: rmux_proto::ClientTerminalContext) -> Self {
Self {
terminal_features: value.terminal_features,
utf8: value.utf8,
}
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct NewSessionSpec {
#[serde(default)]
pub session_name: Option<SessionName>,
#[serde(default)]
pub working_directory: Option<String>,
#[serde(default)]
pub detached: bool,
#[serde(default)]
pub size: Option<TerminalSizeSpec>,
#[serde(default)]
pub process: ProcessSpec,
#[serde(default)]
pub group_target: Option<SessionName>,
#[serde(default)]
pub reuse: NewSessionReuse,
#[serde(default)]
pub window_name: Option<String>,
#[serde(default)]
pub print_session_info: bool,
#[serde(default)]
pub print_format: Option<String>,
}
impl From<NewSessionSpec> for rmux_proto::NewSessionExtRequest {
fn from(value: NewSessionSpec) -> Self {
let (command, process_command, environment) = value.process.into_proto_parts();
Self {
session_name: value.session_name,
working_directory: value.working_directory,
detached: value.detached,
size: value.size.map(Into::into),
environment,
group_target: value.group_target,
attach_if_exists: value.reuse.attach_if_exists,
detach_other_clients: value.reuse.detach_other_clients,
kill_other_clients: value.reuse.kill_other_clients,
flags: value.reuse.flags,
window_name: value.window_name,
print_session_info: value.print_session_info,
print_format: value.print_format,
command,
process_command,
}
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AttachSessionSpec {
#[serde(default)]
pub target: Option<SessionName>,
#[serde(default)]
pub target_spec: Option<String>,
#[serde(default)]
pub reuse: AttachSessionReuse,
#[serde(default)]
pub working_directory: Option<String>,
#[serde(default)]
pub client_terminal: ClientTerminalSpec,
#[serde(default)]
pub client_size: Option<TerminalSizeSpec>,
}
impl From<AttachSessionSpec> for rmux_proto::AttachSessionExt2Request {
fn from(value: AttachSessionSpec) -> Self {
Self {
target: value.target,
target_spec: value.target_spec,
detach_other_clients: value.reuse.detach_other_clients,
kill_other_clients: value.reuse.kill_other_clients,
read_only: value.reuse.read_only,
skip_environment_update: value.reuse.skip_environment_update,
flags: value.reuse.flags,
working_directory: value.working_directory,
client_terminal: value.client_terminal.into(),
client_size: value.client_size.map(Into::into),
}
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SubscriptionSpec {
#[serde(default)]
pub subscriptions: Vec<String>,
#[serde(default)]
pub subscriptions_format: Vec<String>,
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RefreshClientSpec {
#[serde(default)]
pub target_client: Option<String>,
#[serde(default)]
pub adjustment: Option<u32>,
#[serde(default)]
pub clear_pan: bool,
#[serde(default)]
pub pan_left: bool,
#[serde(default)]
pub pan_right: bool,
#[serde(default)]
pub pan_up: bool,
#[serde(default)]
pub pan_down: bool,
#[serde(default)]
pub status_only: bool,
#[serde(default)]
pub clipboard_query: bool,
#[serde(default)]
pub flags: Option<String>,
#[serde(default)]
pub flags_alias: Option<String>,
#[serde(default)]
pub subscriptions: SubscriptionSpec,
#[serde(default)]
pub control_size: Option<String>,
#[serde(default)]
pub colour_report: Option<String>,
}
impl From<RefreshClientSpec> for rmux_proto::RefreshClientRequest {
fn from(value: RefreshClientSpec) -> Self {
Self {
target_client: value.target_client,
adjustment: value.adjustment,
clear_pan: value.clear_pan,
pan_left: value.pan_left,
pan_right: value.pan_right,
pan_up: value.pan_up,
pan_down: value.pan_down,
status_only: value.status_only,
clipboard_query: value.clipboard_query,
flags: value.flags,
flags_alias: value.flags_alias,
subscriptions: value.subscriptions.subscriptions,
subscriptions_format: value.subscriptions.subscriptions_format,
control_size: value.control_size,
colour_report: value.colour_report,
}
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum SplitDirectionSpec {
#[default]
Vertical,
Horizontal,
}
impl From<SplitDirectionSpec> for rmux_proto::SplitDirection {
fn from(value: SplitDirectionSpec) -> Self {
match value {
SplitDirectionSpec::Vertical => Self::Vertical,
SplitDirectionSpec::Horizontal => Self::Horizontal,
}
}
}
impl From<rmux_proto::SplitDirection> for SplitDirectionSpec {
fn from(value: rmux_proto::SplitDirection) -> Self {
match value {
rmux_proto::SplitDirection::Vertical => Self::Vertical,
rmux_proto::SplitDirection::Horizontal => Self::Horizontal,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum SplitTargetSpec {
Session(SessionName),
Pane(PaneRef),
}
impl From<SplitTargetSpec> for rmux_proto::SplitWindowTarget {
fn from(value: SplitTargetSpec) -> Self {
match value {
SplitTargetSpec::Session(session_name) => Self::Session(session_name),
SplitTargetSpec::Pane(target) => Self::Pane(target.into()),
}
}
}
impl From<rmux_proto::SplitWindowTarget> for SplitTargetSpec {
fn from(value: rmux_proto::SplitWindowTarget) -> Self {
match value {
rmux_proto::SplitWindowTarget::Session(session_name) => Self::Session(session_name),
rmux_proto::SplitWindowTarget::Pane(target) => Self::Pane(target.into()),
}
}
}
impl From<&SplitTargetSpec> for rmux_proto::SplitWindowTarget {
fn from(value: &SplitTargetSpec) -> Self {
match value {
SplitTargetSpec::Session(session_name) => Self::Session(session_name.clone()),
SplitTargetSpec::Pane(target) => Self::Pane(target.into()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SplitSpec {
pub target: SplitTargetSpec,
#[serde(default)]
pub direction: SplitDirectionSpec,
#[serde(default)]
pub before: bool,
#[serde(default)]
pub process: ProcessSpec,
}
impl From<SplitSpec> for rmux_proto::SplitWindowExtRequest {
fn from(value: SplitSpec) -> Self {
let (command, process_command, environment) = value.process.into_proto_parts();
Self {
target: value.target.into(),
direction: value.direction.into(),
before: value.before,
environment,
command,
process_command,
start_directory: None,
keep_alive_on_exit: None,
}
}
}