Skip to content

Commit

Permalink
feat: add --tmux flag (deprecates sk-tmux, fixes #596)
Browse files Browse the repository at this point in the history
  • Loading branch information
LoricAndre committed Nov 20, 2024
1 parent 7df8b77 commit b7e3e6e
Show file tree
Hide file tree
Showing 8 changed files with 268 additions and 7 deletions.
74 changes: 74 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions bin/sk-tmux
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

# sk-tmux: starts sk in a tmux pane
# usage: sk-tmux [LAYOUT OPTIONS] [--] [SK OPTIONS]
echo "[WRN] This script is deprecated in favor or \`sk --tmux\` and will be removed in a later release" >&2

fail() {
>&2 echo "$1"
Expand Down
4 changes: 2 additions & 2 deletions man/man1/sk-tmux.1
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
..
.TH sk-tmux 1 "Oct 2018" "sk 0.10.4" "sk-tmux - open sk in tmux split pane"
.TH sk-tmux 1 "Oct 2018" "sk 0.10.4" "! DEPRECATED IN FLAVOR OF sk --tmux ! - sk-tmux - open sk in tmux split pane"

.SH NAME
sk-tmux - open sk in tmux split pane
! DEPRECATED IN FLAVOR OF sk --tmux ! - sk-tmux - open sk in tmux split pane

.SH SYNOPSIS
.B sk-tmux [-u|-d [HEIGHT[%]]] [-l|-r [WIDTH[%]]] [--] [sk OPTIONS]
Expand Down
2 changes: 2 additions & 0 deletions skim/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ crossbeam = "0.8.2"
beef = "0.5.2" # compact cow
defer-drop = "1.3.0"
indexmap = "2.6.0"
rand = "0.8.5"
tmux_interface = "0.3.2"

[features]
default = ["cli"]
Expand Down
8 changes: 5 additions & 3 deletions skim/src/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,12 @@ fn sk_main() -> Result<i32, SkMainError> {
//------------------------------------------------------------------------------
// output

let Some(result) = Skim::run_with(&opts, rx_item) else {
return Ok(0);
let Some(result) = (match opts.tmux {
Some(_) => crate::tmux::run_with(&opts),
None => Skim::run_with(&opts, rx_item),
}) else {
return Ok(135);
};

if result.is_abort {
return Ok(130);
}
Expand Down
1 change: 1 addition & 0 deletions skim/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ mod selection;
mod spinlock;
mod theme;
mod util;
pub mod tmux;

//------------------------------------------------------------------------------
pub trait AsAny {
Expand Down
2 changes: 0 additions & 2 deletions skim/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -633,8 +633,6 @@ pub struct SkimOptions {
#[arg(long, short, help_heading = "Scripting")]
pub filter: Option<String>,

/// Reserved for later use
///
/// Run in a tmux popup
///
/// Format: sk --tmux <center|top|bottom|left|right>[,SIZE[%]][,SIZE[%]]
Expand Down
183 changes: 183 additions & 0 deletions skim/src/tmux.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
use std::{borrow::Cow, sync::Arc};

use rand::{distributions::Alphanumeric, Rng};
use tmux_interface::Tmux;
use tuikit::key::Key;

use crate::{event::Event, SkimItem, SkimOptions, SkimOutput};

#[derive(Debug)]
enum TmuxWindowDir {
Center,
Top,
Bottom,
Left,
Right,
}

impl From<&str> for TmuxWindowDir {
fn from(value: &str) -> Self {
use TmuxWindowDir::*;
match value {
"center" => Center,
"top" => Top,
"bottom" => Bottom,
"left" => Left,
"right" => Right,
_ => Center,
}
}
}

#[derive(Debug)]
pub struct TmuxOptions<'a> {
pub width: &'a str,
pub height: &'a str,
pub x: &'a str,
pub y: &'a str,
}

struct SkimTmuxOutput {
line: String,
}

impl SkimItem for SkimTmuxOutput {
fn text(&self) -> Cow<'_, str> {
Cow::from(&self.line)
}
}

impl<'a> From<&'a String> for TmuxOptions<'a> {
fn from(value: &'a String) -> Self {
let (raw_dir, size) = value.split_once(",").unwrap_or((value, "50%"));
let dir = TmuxWindowDir::from(raw_dir);
let (height, width) = if let Some((lhs, rhs)) = size.split_once(",") {
match dir {
TmuxWindowDir::Center | TmuxWindowDir::Left | TmuxWindowDir::Right => (rhs, lhs),
TmuxWindowDir::Top | TmuxWindowDir::Bottom => (lhs, rhs),
}
} else {
match dir {
TmuxWindowDir::Left | TmuxWindowDir::Right => ("100%", size),
TmuxWindowDir::Top | TmuxWindowDir::Bottom => (size, "100%"),
TmuxWindowDir::Center => (size, size),
}
};

let (x, y) = match dir {
TmuxWindowDir::Center => ("C", "C"),
TmuxWindowDir::Top => ("C", "0%"),
TmuxWindowDir::Bottom => ("C", "100%"),
TmuxWindowDir::Left => ("0%", "C"),
TmuxWindowDir::Right => ("100%", "C"),
};

Self { height, width, x, y }
}
}

pub fn run_with(opts: &SkimOptions) -> Option<SkimOutput> {
// Create temp dir for downstream output
let temp_dir_name = format!(
"sk-tmux-{}",
&rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(8)
.map(char::from)
.collect::<String>(),
);
let temp_dir = std::env::temp_dir().join(&temp_dir_name);
std::fs::create_dir(&temp_dir).unwrap_or_else(|_| panic!("Failed to create temp dir {}", temp_dir.display()));

let tmp_stdout = temp_dir.join("stdout");
let tmp_stderr = temp_dir.join("stderr");

// Build args to send to downstream sk invocation
let mut tmux_shell_cmd = String::new();
let mut prev_is_tmux_flag = false;
for arg in std::env::args() {
debug!("[tmux] Got arg {}", arg);
if prev_is_tmux_flag {
prev_is_tmux_flag = false;
if !arg.starts_with("-") {
continue;
}
}
if arg == "--tmux" {
debug!("[tmux] Found tmux arg, skipping this and the next");
prev_is_tmux_flag = true;
continue;
} else if arg.starts_with("--tmux") {
debug!("[tmux] Found equal tmux arg, skipping");
continue;
}
tmux_shell_cmd.push_str(&format!(" {arg}"));
}
tmux_shell_cmd.push_str(&format!(" >{} 2>{}", tmp_stdout.display(), tmp_stderr.display()));

// Run downstream sk in tmux
let raw_tmux_opts = &opts.tmux.clone().unwrap();
let tmux_opts = TmuxOptions::from(raw_tmux_opts);
let tmux_cmd = tmux_interface::commands::tmux_command::TmuxCommand::new()
.name("popup")
.push_flag("-E")
.push_option("-h", tmux_opts.height)
.push_option("-w", tmux_opts.width)
.push_option("-x", tmux_opts.x)
.push_option("-y", tmux_opts.y)
.push_param(tmux_shell_cmd)
.to_owned();

let status = Tmux::with_command(tmux_cmd)
.output()
.expect("Failed to run command in popup")
.status();

let output_ending = if opts.print0 { "\0" } else { "\n" };
let stdout_bytes = std::fs::read_to_string(tmp_stdout).unwrap_or_default();
let mut stdout = stdout_bytes.split(output_ending);

let query_str = if opts.print_query && status.success() {
stdout.next().expect("Not enough lines to unpack in downstream result")
} else {
""
};

let command_str = if opts.print_cmd && status.success() {
stdout.next().expect("Not enough lines to unpack in downstream result")
} else {
""
};

let accept_key = if !opts.expect.is_empty() && status.success() {
Some(
stdout
.next()
.expect("Not enough lines to unpack in downstream result")
.to_string(),
)
} else {
None
};

let mut selected_items: Vec<Arc<dyn SkimItem>> = vec![];
for line in stdout {
selected_items.push(Arc::new(SkimTmuxOutput { line: line.to_string() }));
}

let is_abort = !status.success();
let final_event = match is_abort {
true => Event::EvActAbort,
false => Event::EvActAccept(accept_key),
};

let skim_output = SkimOutput {
final_event,
is_abort,
final_key: Key::Null,
query: query_str.to_string(),
cmd: command_str.to_string(),
selected_items,
};
Some(skim_output)
}

0 comments on commit b7e3e6e

Please sign in to comment.