use crate::escape_format;
use std::ffi::OsString;
use std::fmt;
use super::{
Chainable, Effects, Op, Operation, PaneSlot, PaneTarget, Safety, Scope, Slot, WindowTarget,
};
use super::{PANE_FORMAT, Resolver};
use crate::window::assignment;
use crate::{Command, SplitDirection};
#[derive(Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct SplitWindow {
pub(crate) target: WindowTarget,
vertical: bool,
#[cfg_attr(feature = "serde", serde(default))]
before: bool,
#[cfg_attr(
feature = "serde",
serde(
serialize_with = "crate::plan::wire::optional_argument",
deserialize_with = "crate::plan::wire::parse_optional_argument"
)
)]
#[cfg_attr(
feature = "schema",
schemars(with = "Option<crate::plan::wire::Argument>")
)]
start_directory: Option<OsString>,
#[cfg_attr(
feature = "serde",
serde(
serialize_with = "crate::plan::wire::optional_argument",
deserialize_with = "crate::plan::wire::parse_optional_argument"
)
)]
#[cfg_attr(
feature = "schema",
schemars(with = "Option<crate::plan::wire::Argument>")
)]
command: Option<OsString>,
#[cfg_attr(
feature = "serde",
serde(
serialize_with = "crate::plan::wire::pairs",
deserialize_with = "crate::plan::wire::parse_pairs"
)
)]
#[cfg_attr(
feature = "schema",
schemars(with = "Vec<(crate::plan::wire::Argument, crate::plan::wire::Argument)>")
)]
environment: Vec<(OsString, OsString)>,
focus: bool,
}
impl SplitWindow {
#[must_use]
pub fn new(target: impl Into<WindowTarget>) -> Self {
Self {
target: target.into(),
vertical: true,
before: false,
start_directory: None,
command: None,
environment: Vec::new(),
focus: false,
}
}
#[must_use]
pub fn environment(mut self, name: impl Into<OsString>, value: impl Into<OsString>) -> Self {
self.environment.push((name.into(), value.into()));
self
}
#[must_use]
pub const fn horizontal(self) -> Self {
self.direction(SplitDirection::Right)
}
#[must_use]
pub const fn direction(mut self, direction: SplitDirection) -> Self {
self.vertical = direction.is_vertical();
self.before = direction.before();
self
}
#[must_use]
pub fn start_directory(mut self, directory: impl Into<OsString>) -> Self {
self.start_directory = Some(directory.into());
self
}
#[must_use]
pub fn command(mut self, command: impl Into<OsString>) -> Self {
self.command = Some(command.into());
self
}
#[must_use]
pub const fn focus(mut self) -> Self {
self.focus = true;
self
}
pub(crate) const fn focuses(&self) -> bool {
self.focus
}
pub(crate) fn render(&self, resolve: Resolver<'_>) -> Option<Command> {
let mut command = Command::new("split-window")
.arg("-P")
.arg("-F")
.arg(PANE_FORMAT)
.arg("-t")
.arg(self.target.token(resolve)?)
.arg(if self.vertical { "-v" } else { "-h" });
if self.before {
command = command.arg("-b");
}
if !self.focus {
command = command.arg("-d");
}
if let Some(directory) = &self.start_directory {
command = command.arg("-c").arg(escape_format(directory));
}
for (name, value) in &self.environment {
command = command.arg("-e").sensitive_arg(assignment(name, value));
}
if let Some(shell_command) = &self.command {
command = command.arg("--").sensitive_arg(shell_command.clone());
}
Some(command)
}
}
impl fmt::Debug for SplitWindow {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("SplitWindow")
.field("target", &self.target)
.field("vertical", &self.vertical)
.field("before", &self.before)
.field("has_start_directory", &self.start_directory.is_some())
.field("has_command", &self.command.is_some())
.field("environment_count", &self.environment.len())
.field("focus", &self.focus)
.finish()
}
}
operation!(
SplitWindow,
creates = Slot<PaneSlot>,
effects = Effects {
creates: Some(Scope::Pane),
..Effects::MUTATING
},
safety = Safety::Mutating
);
#[derive(Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct SendKeys {
pub(crate) target: PaneTarget,
#[cfg_attr(
feature = "serde",
serde(
serialize_with = "crate::plan::wire::optional_argument",
deserialize_with = "crate::plan::wire::parse_optional_argument"
)
)]
#[cfg_attr(
feature = "schema",
schemars(with = "Option<crate::plan::wire::Argument>")
)]
text: Option<OsString>,
#[cfg_attr(
feature = "serde",
serde(
serialize_with = "crate::plan::wire::list",
deserialize_with = "crate::plan::wire::parse_list"
)
)]
#[cfg_attr(
feature = "schema",
schemars(with = "Vec<crate::plan::wire::Argument>")
)]
keys: Vec<OsString>,
enter: bool,
}
impl SendKeys {
#[must_use]
pub fn new(target: impl Into<PaneTarget>) -> Self {
Self {
target: target.into(),
text: None,
keys: Vec::new(),
enter: false,
}
}
#[must_use]
pub fn text(mut self, text: impl Into<OsString>) -> Self {
self.text = Some(text.into());
self
}
#[must_use]
pub fn keys<K: Into<OsString>>(mut self, keys: impl IntoIterator<Item = K>) -> Self {
self.keys = keys.into_iter().map(Into::into).collect();
self
}
#[must_use]
pub const fn enter(mut self) -> Self {
self.enter = true;
self
}
pub(crate) fn text_conflicts_with_keys(&self) -> bool {
self.text.is_some() && !self.keys.is_empty()
}
pub(crate) fn render(&self, resolve: Resolver<'_>) -> Option<Command> {
let mut command = Command::new("send-keys")
.arg("-t")
.arg(self.target.token(resolve)?);
if let Some(text) = &self.text {
let mut literal = text.clone();
if self.enter {
literal.push("\r");
}
command = command.arg("-l").arg("--").sensitive_arg(literal);
} else if !self.keys.is_empty() || self.enter {
command = command.arg("--");
for key in &self.keys {
command = command.arg(key.clone());
}
if self.enter {
command = command.arg("Enter");
}
}
Some(command)
}
}
impl fmt::Debug for SendKeys {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("SendKeys")
.field("target", &self.target)
.field("has_text", &self.text.is_some())
.field("key_count", &self.keys.len())
.field("enter", &self.enter)
.finish()
}
}
operation!(
SendKeys,
creates = (),
effects = Effects::MUTATING,
safety = Safety::Mutating,
chainable
);
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct SelectPane {
pub(crate) target: PaneTarget,
}
impl SelectPane {
#[must_use]
pub fn new(target: impl Into<PaneTarget>) -> Self {
Self {
target: target.into(),
}
}
pub(crate) fn render(&self, resolve: Resolver<'_>) -> Option<Command> {
Some(
Command::new("select-pane")
.arg("-t")
.arg(self.target.token(resolve)?),
)
}
}
operation!(
SelectPane,
creates = (),
effects = Effects {
idempotent: true,
..Effects::MUTATING
},
safety = Safety::Mutating,
chainable
);
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct CapturePane {
pub(crate) target: PaneTarget,
escape_sequences: bool,
}
impl CapturePane {
#[must_use]
pub fn new(target: impl Into<PaneTarget>) -> Self {
Self {
target: target.into(),
escape_sequences: false,
}
}
#[must_use]
pub const fn escape_sequences(mut self) -> Self {
self.escape_sequences = true;
self
}
pub(crate) fn render(&self, resolve: Resolver<'_>) -> Option<Command> {
let mut command = Command::new("capture-pane")
.arg("-p")
.arg("-t")
.arg(self.target.token(resolve)?);
if self.escape_sequences {
command = command.arg("-e");
}
Some(command)
}
}
operation!(
CapturePane,
creates = (),
effects = Effects {
read_only: true,
idempotent: true,
reads_output: true,
..Effects::MUTATING
},
safety = Safety::ReadOnly
);
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct KillPane {
pub(crate) target: PaneTarget,
}
impl KillPane {
#[must_use]
pub fn new(target: impl Into<PaneTarget>) -> Self {
Self {
target: target.into(),
}
}
#[must_use]
pub const fn target(&self) -> &PaneTarget {
&self.target
}
pub(crate) fn render(&self, resolve: Resolver<'_>) -> Option<Command> {
Some(
Command::new("kill-pane")
.arg("-t")
.arg(self.target.token(resolve)?),
)
}
}
operation!(
KillPane,
creates = (),
effects = Effects {
destructive: true,
..Effects::MUTATING
},
safety = Safety::Destructive,
chainable
);