Compare commits
No commits in common. "main" and "completion" have entirely different histories.
main
...
completion
11 changed files with 65 additions and 129 deletions
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "remux"
|
||||
version = "0.4.0"
|
||||
version = "0.3.6"
|
||||
edition = "2021"
|
||||
authors = [ "Valerie Wolfe <sleeplessval@gmail.com>" ]
|
||||
description = "A friendly command shortener for tmux"
|
||||
|
|
10
README.md
10
README.md
|
@ -92,16 +92,10 @@ using an AUR package manager such as <a href="https://github.com/Morganamilo/par
|
|||
Install the package using Cargo with the command <code>cargo install tmux-remux</code>.
|
||||
</details>
|
||||
|
||||
### Supplemental
|
||||
### Man Page
|
||||
|
||||
<details>
|
||||
<summary>Bash Completions</summary>
|
||||
Copy <code>bash-completion/remux</code> to the appropriate directory, typically
|
||||
<code>/usr/share/bash-completion</code>.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Man Page: Section 1</summary>
|
||||
<summary>Section 1</summary>
|
||||
Copy <code>man/remux.1</code> into <code>/usr/share/man/man1/</code>.
|
||||
</details>
|
||||
|
||||
|
|
17
man/remux.1
17
man/remux.1
|
@ -1,5 +1,6 @@
|
|||
.Dd $Mdocdate$
|
||||
.Dt REMUX 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm remux
|
||||
.Nd a command shortener for
|
||||
|
@ -7,7 +8,6 @@
|
|||
.Sh SYNOPSIS
|
||||
.Nm remux
|
||||
.Op Fl dhnqrtv
|
||||
.Op Fl D Ar path
|
||||
.Op Ar command
|
||||
.Op args...
|
||||
.Sh DESCRIPTION
|
||||
|
@ -35,8 +35,6 @@ Attaches to an existing session.
|
|||
.Bl -tag -width Ds -compact
|
||||
.It Fl d , Fl -detach
|
||||
Detach all other connections to the session.
|
||||
.It Fl D , Fl -dir Ar path
|
||||
Sets the working directory for the given command.
|
||||
.It Fl n , Fl -nest
|
||||
Allow nesting (attaching a session from inside another session).
|
||||
.It Fl r , Fl -read-only
|
||||
|
@ -103,8 +101,8 @@ aliases: p
|
|||
Prints the session path.
|
||||
.Ed
|
||||
.It Xo Ic switch
|
||||
.Op Fl rd
|
||||
.Op Ar title
|
||||
.Op Fl r , Fl -read-only
|
||||
.Ar title
|
||||
.Xc
|
||||
.Bd -literal -compact
|
||||
aliases: s
|
||||
|
@ -112,12 +110,10 @@ Switches from the current session to the target.
|
|||
.Ed
|
||||
.Pp
|
||||
.Bl -tag -width Ds -compact
|
||||
.It Fl d , Fl -detach
|
||||
Detaches other clients from the target session.
|
||||
.It Fl r , Fl -read-only
|
||||
Switch to the target session in read-only mode.
|
||||
.It Ar title
|
||||
The title of the session to switch to. If blank, the previous session will be used.
|
||||
The title of the session to switch to.
|
||||
.El
|
||||
.It Ic title
|
||||
.Bd -literal -compact
|
||||
|
@ -139,11 +135,6 @@ Default: '>'
|
|||
.It Ev REMUX_NEW_WINDOW
|
||||
Provides a default windows name when creating a new session. Unused if empty.
|
||||
Default: (unset)
|
||||
.It Ev REMUX_PREVIOUS_SYMBOL
|
||||
Changes the symbol displayed for the previous session in the
|
||||
.Ic list
|
||||
command.
|
||||
Default: '-'
|
||||
.It Ev REMUX_REPO_FILE
|
||||
The filename to match on when trying to find the root of a repository.
|
||||
Default: '.git'
|
||||
|
|
|
@ -8,18 +8,21 @@ use tmux_interface::{
|
|||
use crate::{
|
||||
error,
|
||||
state::State,
|
||||
util::{
|
||||
self,
|
||||
message,
|
||||
MSG_PREVIOUS, MSG_SESSION_PATH, NULL
|
||||
}
|
||||
util::{ self, NULL }
|
||||
};
|
||||
|
||||
pub fn path(state: &mut State) {
|
||||
state.session_enforce("path");
|
||||
|
||||
if let Some(message) = message(MSG_SESSION_PATH) {
|
||||
println!("{message}");
|
||||
let message = commands::DisplayMessage::new().print().message("#{session_path}");
|
||||
|
||||
let result = Tmux::new().add_command(message).output().unwrap();
|
||||
let text = String::from_utf8(result.0.stdout);
|
||||
|
||||
if let Ok(output) = text {
|
||||
// trim the trailing line break
|
||||
let target = output.len() - 1;
|
||||
println!("{}", &output[0..target]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,33 +33,21 @@ pub fn switch(state: &mut State) {
|
|||
|
||||
// consume optional flags
|
||||
let read_only = state.flags.read_only;
|
||||
let detach_other = state.flags.detached;
|
||||
//TODO: -d flag handling needs to be done manually
|
||||
|
||||
let args = state.args.clone().finish();
|
||||
let target: String = match if let Some(inner) = args.get(0) { inner.to_str() } else { None } {
|
||||
None |
|
||||
Some("-") => if let Some(prev) = message(MSG_PREVIOUS) { prev }
|
||||
else { error::missing_target() },
|
||||
|
||||
Some(inner) => inner.to_owned()
|
||||
};
|
||||
if args.len() < 1 { error::missing_target(); }
|
||||
let target = args.get(0).unwrap().to_string_lossy().to_string();
|
||||
|
||||
let exists = util::session_exists(target.clone());
|
||||
if !exists { error::no_target(target.clone()); }
|
||||
|
||||
let mut tmux = Tmux::new();
|
||||
|
||||
if detach_other {
|
||||
let detach = commands::DetachClient::new()
|
||||
.target_session(&target);
|
||||
tmux = tmux.add_command(detach);
|
||||
}
|
||||
|
||||
let mut switch = commands::SwitchClient::new();
|
||||
switch = switch.target_session(&target);
|
||||
switch = switch.target_session(target);
|
||||
if read_only { switch.read_only = true; }
|
||||
|
||||
tmux.add_command(switch)
|
||||
Tmux::new()
|
||||
.add_command(switch)
|
||||
.stderr(NULL).output().ok();
|
||||
}
|
||||
|
||||
|
|
|
@ -9,19 +9,11 @@ use tmux_interface::{
|
|||
};
|
||||
|
||||
use crate::{
|
||||
env::{
|
||||
self,
|
||||
env_var,
|
||||
SYMBOL_ATTACH, SYMBOL_CURRENT, SYMBOL_PREV
|
||||
},
|
||||
env::{ self, env_var },
|
||||
error,
|
||||
flag,
|
||||
state::State,
|
||||
util::{
|
||||
self,
|
||||
message,
|
||||
MSG_PREVIOUS, NULL
|
||||
}
|
||||
util::{ self, NULL }
|
||||
};
|
||||
|
||||
pub fn attach(state: &mut State) {
|
||||
|
@ -127,18 +119,16 @@ pub fn list(state: &mut State) {
|
|||
let sessions = util::get_sessions().unwrap_or(Vec::new());
|
||||
|
||||
let search = state.target();
|
||||
let previous = message(MSG_PREVIOUS);
|
||||
|
||||
// handle empty case
|
||||
if sessions.len() == 0 {
|
||||
if !state.flags.quiet { println!("no sessions"); }
|
||||
println!("no sessions");
|
||||
return;
|
||||
}
|
||||
|
||||
// get attached session symbol
|
||||
let attach_symbol = env_var(SYMBOL_ATTACH);
|
||||
let current_symbol = env_var(SYMBOL_CURRENT);
|
||||
let prev_symbol = env_var(SYMBOL_PREV);
|
||||
let attach_symbol = env_var(env::ATTACH_SYMBOL);
|
||||
let current_symbol = env_var(env::CURRENT_SYMBOL);
|
||||
|
||||
// pretty print session list
|
||||
if !state.flags.quiet { println!("sessions:"); }
|
||||
|
@ -151,17 +141,13 @@ pub fn list(state: &mut State) {
|
|||
let id = session.id.unwrap();
|
||||
|
||||
let attached = session.attached.unwrap_or(0) > 0;
|
||||
|
||||
let compare = Some(name.clone());
|
||||
let marker =
|
||||
if compare == state.title { current_symbol.clone() }
|
||||
else if state.session && compare == previous { prev_symbol.clone() }
|
||||
else { " ".to_string() };
|
||||
let current = Some(name.clone()) == state.title;
|
||||
|
||||
println!(
|
||||
" {marker} {name}{reset} ({bold}{blue}{id}{reset}) {bold}{green}{attach}{reset}",
|
||||
" {current} {name}{reset} ({bold}{blue}{id}{reset}) {bold}{green}{attach}{reset}",
|
||||
// values
|
||||
attach = if attached { attach_symbol.clone() } else { "".to_string() },
|
||||
current = if current { current_symbol.clone() } else { " ".to_string() },
|
||||
// formatting
|
||||
bold = style::Bold,
|
||||
blue = color::Fg(color::Blue),
|
||||
|
|
11
src/env.rs
11
src/env.rs
|
@ -2,13 +2,12 @@ use std::env::var;
|
|||
|
||||
pub type EnvVar = (&'static str, &'static str);
|
||||
|
||||
pub const NEW_WINDOW_NAME: EnvVar = ("REMUX_NEW_WINDOW", "");
|
||||
pub const REPO_FILE: EnvVar = ("REMUX_REPO_FILE", ".git");
|
||||
pub const SYMBOL_ATTACH: EnvVar = ("REMUX_ATTACH_SYMBOL", "*");
|
||||
pub const SYMBOL_CURRENT: EnvVar = ("REMUX_CURRENT_SYMBOL", ">");
|
||||
pub const SYMBOL_PREV: EnvVar = ("REMUX_PREVIOUS_SYMBOL", "-");
|
||||
pub static ATTACH_SYMBOL: EnvVar = ("REMUX_ATTACH_SYMBOL", "*");
|
||||
pub static CURRENT_SYMBOL: EnvVar = ("REMUX_CURRENT_SYMBOL", ">");
|
||||
pub static NEW_WINDOW_NAME: EnvVar = ("REMUX_NEW_WINDOW", "");
|
||||
pub static REPO_FILE: EnvVar = ("REMUX_REPO_FILE", ".git");
|
||||
|
||||
pub const TMUX: &str = "TMUX";
|
||||
pub static TMUX: &str = "TMUX";
|
||||
|
||||
/// get or default an environment variable
|
||||
pub fn env_var(envvar: EnvVar) -> String {
|
||||
|
|
29
src/error.rs
29
src/error.rs
|
@ -1,14 +1,14 @@
|
|||
use std::process::exit;
|
||||
|
||||
/// no subcommand that matches user input; code 1
|
||||
pub fn no_subcommand(subcommand: String) -> ! {
|
||||
pub fn no_subcommand(subcommand: String) {
|
||||
eprintln!("remux: no command match for \"{subcommand}\"");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
/// target session not found; code 2
|
||||
pub fn no_target<S: Into<String>>(target: S) -> ! {
|
||||
pub fn no_target<S: Into<String>>(target: S) {
|
||||
let target = target.into();
|
||||
eprintln!("remux: no session \"{target}\" exists");
|
||||
exit(2);
|
||||
|
@ -16,26 +16,26 @@ pub fn no_target<S: Into<String>>(target: S) -> ! {
|
|||
|
||||
|
||||
/// help topic doesn't exist; code 3
|
||||
pub fn no_help(topic: String) -> ! {
|
||||
pub fn no_help(topic: String) {
|
||||
eprintln!("remux: no help for \"{topic}\"");
|
||||
exit(3);
|
||||
}
|
||||
|
||||
|
||||
/// user provided no target; code 4
|
||||
pub fn missing_target() -> ! {
|
||||
pub fn missing_target() {
|
||||
eprintln!("remux: no target provided");
|
||||
exit(4);
|
||||
}
|
||||
|
||||
/// refuse to attach to current session; code 4
|
||||
pub fn same_session() -> ! {
|
||||
pub fn same_session() {
|
||||
eprintln!("remux: cannot attach to same session");
|
||||
exit(4);
|
||||
}
|
||||
|
||||
/// a session with the target name already exists; code 4
|
||||
pub fn target_exists<S: Into<String>>(target: S) -> ! {
|
||||
pub fn target_exists<S: Into<String>>(target: S) {
|
||||
let target = target.into();
|
||||
eprintln!("remux: session \"{target}\" already exists");
|
||||
exit(4);
|
||||
|
@ -43,26 +43,26 @@ pub fn target_exists<S: Into<String>>(target: S) -> ! {
|
|||
|
||||
|
||||
/// non-terminal environment prevention; code 5
|
||||
pub fn not_terminal() -> ! {
|
||||
pub fn not_terminal() {
|
||||
eprintln!("remux: not running from a terminal");
|
||||
exit(5);
|
||||
}
|
||||
|
||||
|
||||
/// tried to nest while not in a session; code 6
|
||||
pub fn not_nesting() -> ! {
|
||||
pub fn not_nesting() {
|
||||
eprintln!("remux: inappropriate nesting flag (-n); not in a session");
|
||||
exit(6);
|
||||
}
|
||||
|
||||
/// operation requires nesting flag; code 6
|
||||
pub fn prevent_nest() -> ! {
|
||||
pub fn prevent_nest() {
|
||||
eprintln!("remux: the nesting flag (-n) is required for nesting operation");
|
||||
exit(6);
|
||||
}
|
||||
|
||||
/// operation conflicts with nesting flag; code 6
|
||||
pub fn conflict_nest(reason: Option<&'static str>) -> ! {
|
||||
pub fn conflict_nest(reason: Option<&'static str>) {
|
||||
if let Some(reason) = reason { eprintln!("remux: inappropriate nesting flag (-n): {reason}"); }
|
||||
else { eprintln!("remux: nesting flag (-n) is inappropriate for this operation."); }
|
||||
exit(6);
|
||||
|
@ -70,15 +70,8 @@ pub fn conflict_nest(reason: Option<&'static str>) -> ! {
|
|||
|
||||
|
||||
/// tried to run a session command outside a session; code 7
|
||||
pub fn not_in_session(cmd: &'static str) -> ! {
|
||||
pub fn not_in_session(cmd: &'static str) {
|
||||
eprintln!("remux: '{cmd}' must be run from within a session");
|
||||
exit(7);
|
||||
}
|
||||
|
||||
|
||||
/// failed to set working directory; code 8
|
||||
pub fn working_dir_fail(working_dir: &str) -> ! {
|
||||
eprintln!("remux: failed to set working directory to '{working_dir}'");
|
||||
exit(8);
|
||||
}
|
||||
|
||||
|
|
23
src/flag.rs
23
src/flag.rs
|
@ -3,14 +3,13 @@ use pico_args::Arguments;
|
|||
|
||||
type Flag = [&'static str;2];
|
||||
|
||||
pub static DETACH: Flag = [ "-d", "--detach" ];
|
||||
pub static HELP: Flag = [ "-h", "--help" ];
|
||||
pub static NEST: Flag = [ "-n", "--nest" ];
|
||||
pub static QUIET: Flag = [ "-q", "--quiet" ];
|
||||
pub static READ_ONLY: Flag = [ "-r", "--read-only" ];
|
||||
pub static TARGET: Flag = [ "-t", "--target" ];
|
||||
pub static VERSION: Flag = [ "-v", "--version" ];
|
||||
pub static WORKING_DIR: Flag = [ "-D", "--dir" ];
|
||||
pub static DETACH: Flag = ["-d", "--detach"];
|
||||
pub static HELP: Flag = ["-h", "--help"];
|
||||
pub static NEST: Flag = ["-n", "--nest"];
|
||||
pub static QUIET: Flag = ["-q", "--quiet"];
|
||||
pub static READ_ONLY: Flag = ["-r", "--read-only"];
|
||||
pub static TARGET: Flag = ["-t", "--target"];
|
||||
pub static VERSION: Flag = ["-v", "--version"];
|
||||
|
||||
pub struct Flags {
|
||||
pub detached: bool,
|
||||
|
@ -18,7 +17,6 @@ pub struct Flags {
|
|||
pub quiet: bool,
|
||||
pub read_only: bool,
|
||||
pub target: Option<String>,
|
||||
pub working_dir: Option<String>
|
||||
}
|
||||
|
||||
impl Flags {
|
||||
|
@ -29,15 +27,13 @@ impl Flags {
|
|||
let quiet = args.contains(QUIET);
|
||||
let read_only = args.contains(READ_ONLY);
|
||||
let target = args.value_from_str(TARGET).ok();
|
||||
let working_dir = args.value_from_str(WORKING_DIR).ok();
|
||||
|
||||
Flags {
|
||||
detached,
|
||||
nested,
|
||||
quiet,
|
||||
read_only,
|
||||
target,
|
||||
working_dir
|
||||
target
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,8 +43,7 @@ impl Flags {
|
|||
nested: self.nested,
|
||||
quiet: self.quiet,
|
||||
read_only: self.read_only,
|
||||
target: self.target.clone(),
|
||||
working_dir: self.working_dir.clone()
|
||||
target: None
|
||||
}
|
||||
}
|
||||
|
||||
|
|
19
src/state.rs
19
src/state.rs
|
@ -9,7 +9,7 @@ use crate::{
|
|||
env::{ env_var, REPO_FILE, TMUX },
|
||||
error,
|
||||
flag::Flags,
|
||||
util::{ find, message, MSG_SESSION_NAME }
|
||||
util::{ find, session_name }
|
||||
};
|
||||
|
||||
pub struct State<'a> {
|
||||
|
@ -29,10 +29,7 @@ impl State<'_> {
|
|||
let flags = Flags::from(args);
|
||||
let tmux_var = env::var(TMUX).ok();
|
||||
let session = tmux_var.is_some();
|
||||
|
||||
if let Some(ref path) = flags.working_dir { State::set_working_dir(&path); }
|
||||
|
||||
let title = if session { message(MSG_SESSION_NAME) } else { None };
|
||||
let title = if session { session_name() } else { None };
|
||||
let repository = Repository::find();
|
||||
|
||||
State {
|
||||
|
@ -65,19 +62,15 @@ impl State<'_> {
|
|||
if !self.session { error::not_in_session(cmd); }
|
||||
}
|
||||
|
||||
fn set_working_dir(path: &str) {
|
||||
let result = env::set_current_dir(path);
|
||||
if result.is_err() {
|
||||
error::working_dir_fail(path);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn target(&mut self) -> Option<String> { self.args.subcommand().unwrap_or(None) }
|
||||
pub fn target_title(&mut self) -> Option<String> {
|
||||
let from_args = self.target();
|
||||
if from_args.is_some() { return from_args; }
|
||||
else if let Some(repository) = &self.repository { Some(repository.name.clone()) }
|
||||
else { error::missing_target() }
|
||||
else {
|
||||
error::missing_target();
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
12
src/util.rs
12
src/util.rs
|
@ -14,20 +14,14 @@ use crate::error;
|
|||
|
||||
pub const NULL: Option<StdIO> = Some(StdIO::Null);
|
||||
|
||||
pub const MSG_PREVIOUS: &str = "#{client_last_session}";
|
||||
pub const MSG_SESSION_NAME: &str = "#S";
|
||||
pub const MSG_SESSION_PATH: &str = "#{session_path}";
|
||||
pub const MSG_WINDOW_NAME: &str = "#{window_name}";
|
||||
|
||||
pub fn message(fstr: &str) -> Option<String> {
|
||||
let message = commands::DisplayMessage::new().print().message(fstr);
|
||||
pub fn session_name() -> Option<String> {
|
||||
let message = commands::DisplayMessage::new().print().message("#{session_name}");
|
||||
|
||||
let result = Tmux::new().add_command(message).output();
|
||||
if let Ok(output) = result {
|
||||
let text = String::from_utf8(output.0.stdout);
|
||||
if let Ok(title) = text {
|
||||
if title.len() > 0 { Some(title[0..title.len() - 1].to_owned()) }
|
||||
else { None }
|
||||
Some(title[0..title.len() - 1].to_owned())
|
||||
} else { None }
|
||||
} else { None }
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue