Compare commits
29 commits
307ff23049
...
222f929fd4
Author | SHA1 | Date | |
---|---|---|---|
222f929fd4 | |||
0fc6cd66c6 | |||
1320b6f122 | |||
26f6fd5dd0 | |||
19b12c682d | |||
28a7fc44f9 | |||
21eda33624 | |||
d776129b3e | |||
62d72735da | |||
681e7427ba | |||
d8aaf7feed | |||
b3403ffa36 | |||
a07ae193b5 | |||
c8800a7f53 | |||
92d0010186 | |||
21f7b72298 | |||
2e28acc3c3 | |||
e017d57a53 | |||
6b7c84b673 | |||
782fb694d0 | |||
882e130121 | |||
4615777cae | |||
b00e15a037 | |||
5c3fb7df3f | |||
41a64f039f | |||
be593c82e7 | |||
9c814c43f4 | |||
63335916d9 | |||
aba802e77d |
12 changed files with 412 additions and 221 deletions
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "remux"
|
name = "remux"
|
||||||
version = "0.2.1"
|
version = "0.3.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = [ "Valerie Wolfe <sleeplessval@gmail.com>" ]
|
authors = [ "Valerie Wolfe <sleeplessval@gmail.com>" ]
|
||||||
description = "A friendly command shortener for tmux"
|
description = "A friendly command shortener for tmux"
|
||||||
|
@ -23,7 +23,7 @@ path = "src/main.rs"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
pico-args = { version = "0.5.0", features = [ "combined-flags", "eq-separator" ] }
|
pico-args = { version = "0.5.0", features = [ "combined-flags", "eq-separator" ] }
|
||||||
termion = "2.0.1"
|
termion = "2.0.1"
|
||||||
tmux_interface = "0.2.1"
|
tmux_interface = "0.3.2"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = 's'
|
opt-level = 's'
|
||||||
|
|
31
README.md
31
README.md
|
@ -7,16 +7,20 @@ A tmux wrapper and command shortener written in Rust. ReMux's
|
||||||
goal is to wrap tmux commands to be both shorter, and oriented
|
goal is to wrap tmux commands to be both shorter, and oriented
|
||||||
around session names instead of session IDs.
|
around session names instead of session IDs.
|
||||||
|
|
||||||
|
To further simplify developer usage, the `attach`, `detach`, `has`, and `new`
|
||||||
|
commands can be used without a target field, and will default to the name of
|
||||||
|
the Git repository root directory, if one is found.
|
||||||
|
|
||||||
In their shortest forms, *every* ReMux command is as short or
|
In their shortest forms, *every* ReMux command is as short or
|
||||||
shorter than its equivalent tmux command:
|
shorter than its equivalent tmux command:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|
||||||
# new session
|
# new session
|
||||||
tmux new-session -t foo
|
tmux new-s -t foo
|
||||||
remux n foo
|
remux n foo
|
||||||
|
|
||||||
# lists
|
# list sessions
|
||||||
tmux ls
|
tmux ls
|
||||||
remux l
|
remux l
|
||||||
remux
|
remux
|
||||||
|
@ -26,11 +30,11 @@ tmux a -t foo
|
||||||
remux a foo
|
remux a foo
|
||||||
|
|
||||||
# has
|
# has
|
||||||
tmux has -t foo
|
tmux h -t foo
|
||||||
remux has foo
|
remux h foo
|
||||||
|
|
||||||
# detach
|
# detach
|
||||||
tmux detach-client -t foo
|
tmux det -t foo
|
||||||
remux d foo
|
remux d foo
|
||||||
|
|
||||||
# nesting sessions with '-n' flag
|
# nesting sessions with '-n' flag
|
||||||
|
@ -39,11 +43,16 @@ remux a -n foo
|
||||||
TMUX='' tmux new-session -t foo
|
TMUX='' tmux new-session -t foo
|
||||||
remux n -n foo
|
remux n -n foo
|
||||||
|
|
||||||
```
|
# switch to another session
|
||||||
|
tmux swi -t foo
|
||||||
|
rmux s foo
|
||||||
|
|
||||||
If you're working in a git repository, the `attach`, `has`, and `new` commands
|
# cd to session path
|
||||||
can be used without a session title, and the repository directory name will be
|
tmux run 'printf "#{session_path}" > /tmp/tmux_path'
|
||||||
used instead.
|
cd `cat /tmp/tmux_path`
|
||||||
|
cd `rmux p`
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
|
@ -85,6 +94,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>.
|
Install the package using Cargo with the command <code>cargo install tmux-remux</code>.
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The pretty-print attached symbol (default: `*`) can be set manually by setting `REMUX_ATTACH_SYMBOL`.
|
||||||
|
|
||||||
## Libraries
|
## Libraries
|
||||||
|
|
||||||
- [pico-args](https://crates.io/crates/pico_args) — argument parsing
|
- [pico-args](https://crates.io/crates/pico_args) — argument parsing
|
||||||
|
|
170
src/command.rs
170
src/command.rs
|
@ -1,170 +0,0 @@
|
||||||
use std::{
|
|
||||||
env::current_dir,
|
|
||||||
ffi::OsString,
|
|
||||||
process::exit
|
|
||||||
};
|
|
||||||
|
|
||||||
use pico_args::Arguments;
|
|
||||||
use termion::{ color, style };
|
|
||||||
use tmux_interface::TmuxCommand;
|
|
||||||
|
|
||||||
use crate::error;
|
|
||||||
use crate::util;
|
|
||||||
|
|
||||||
pub fn attach(pargs: &mut Arguments) {
|
|
||||||
util::prevent_nest();
|
|
||||||
|
|
||||||
// get optional flags
|
|
||||||
let read_only = pargs.contains(["-r", "--readonly"]);
|
|
||||||
let detach_other = pargs.contains(["-d", "--detach"]);
|
|
||||||
|
|
||||||
// collect target and window arguments
|
|
||||||
let args = pargs.clone().finish();
|
|
||||||
let target: String;
|
|
||||||
let window: Option<&OsString>;
|
|
||||||
if args.len() < 1 {
|
|
||||||
// missing name will attempt to fall back to repository
|
|
||||||
target = util::repo_fallback();
|
|
||||||
if !util::session_exists(target.clone()) { error::missing_target(); }
|
|
||||||
window = None;
|
|
||||||
} else {
|
|
||||||
target = args.get(0).unwrap().to_string_lossy().to_string();
|
|
||||||
window = args.get(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// focus window if provided
|
|
||||||
if window.is_some() {
|
|
||||||
let target = window.unwrap().to_string_lossy();
|
|
||||||
TmuxCommand::new()
|
|
||||||
.select_window()
|
|
||||||
.target_window(target)
|
|
||||||
.output().ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure the target session exists
|
|
||||||
let exists = util::session_exists(target.clone());
|
|
||||||
if !exists { error::no_target(target.clone()); }
|
|
||||||
|
|
||||||
// build command
|
|
||||||
let mut attach = TmuxCommand::new().attach_session();
|
|
||||||
|
|
||||||
// handle optional flags
|
|
||||||
if read_only { attach.read_only(); }
|
|
||||||
if detach_other { attach.detach_other(); }
|
|
||||||
|
|
||||||
// run command
|
|
||||||
attach
|
|
||||||
.target_session(target)
|
|
||||||
.output().ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn detach(pargs: &mut Arguments) {
|
|
||||||
// get target and error out if not provided
|
|
||||||
let args = pargs.clone().finish();
|
|
||||||
if args.len() < 1 { error::missing_target(); }
|
|
||||||
let target = args.get(0).unwrap().to_string_lossy();
|
|
||||||
|
|
||||||
// make sure the target session exists
|
|
||||||
let exists = util::session_exists(target.clone());
|
|
||||||
if !exists { error::no_target(target.clone()); }
|
|
||||||
|
|
||||||
// build command and run
|
|
||||||
TmuxCommand::new()
|
|
||||||
.detach_client()
|
|
||||||
.target_session(target)
|
|
||||||
.output().ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn has(pargs: &mut Arguments) {
|
|
||||||
// get optional flag
|
|
||||||
let quiet = pargs.contains(["-q", "--quiet"]);
|
|
||||||
|
|
||||||
// collect target argument
|
|
||||||
let args = pargs.clone().finish();
|
|
||||||
let target: String;
|
|
||||||
if args.len() < 1 {
|
|
||||||
// missing name will attempt to fall back to repository
|
|
||||||
target = util::repo_fallback();
|
|
||||||
} else {
|
|
||||||
target = args.get(0).unwrap().to_string_lossy().to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
// run command
|
|
||||||
let success = util::session_exists(target.clone());
|
|
||||||
|
|
||||||
// handle optional flag
|
|
||||||
// inverted; print text if NOT quiet
|
|
||||||
if !quiet { println!("session \"{target}\" {}.", if success { "exists" } else { "does not exist" }); }
|
|
||||||
|
|
||||||
// exit codes for scripts to use
|
|
||||||
exit( if success { 0 } else { 1 });
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn list() {
|
|
||||||
// get session list
|
|
||||||
let sessions = util::get_sessions().unwrap_or(Vec::new());
|
|
||||||
|
|
||||||
// handle empty case
|
|
||||||
if sessions.len() == 0 {
|
|
||||||
println!("no sessions");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// iterate over pretty print
|
|
||||||
println!("sessions:");
|
|
||||||
for session in sessions.into_iter() {
|
|
||||||
let group = session.group.unwrap_or("[untitled]".to_string());
|
|
||||||
let id = session.id.unwrap();
|
|
||||||
let attached = session.attached.unwrap_or(0) > 0;
|
|
||||||
|
|
||||||
println!(
|
|
||||||
" {group} ({bold}{blue}{id}{reset}) {bold}{green}{attach_sym}{reset}",
|
|
||||||
// values
|
|
||||||
attach_sym = if attached { "" } else {""},
|
|
||||||
// formatting
|
|
||||||
bold = style::Bold,
|
|
||||||
blue = color::Fg(color::Blue),
|
|
||||||
green = color::Fg(color::LightGreen),
|
|
||||||
reset = style::Reset
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new(pargs: &mut Arguments) {
|
|
||||||
use pico_args::Error;
|
|
||||||
|
|
||||||
// show nest message
|
|
||||||
util::prevent_nest();
|
|
||||||
|
|
||||||
// get optional flag
|
|
||||||
let target_dir: Result<String, Error> = pargs.value_from_str(["-t", "--target"]);
|
|
||||||
|
|
||||||
// get target and error out if not provided
|
|
||||||
let args = pargs.clone().finish();
|
|
||||||
|
|
||||||
// collect name and command arguments
|
|
||||||
let title: String;
|
|
||||||
let command: Option<&OsString>;
|
|
||||||
if args.len() < 1 {
|
|
||||||
// missing name will attempt to fall back to repository
|
|
||||||
title = util::repo_fallback();
|
|
||||||
command = None;
|
|
||||||
} else {
|
|
||||||
title = args.get(0).unwrap().to_string_lossy().to_string();
|
|
||||||
command = args.get(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// build command
|
|
||||||
let mut new = TmuxCommand::new().new_session();
|
|
||||||
|
|
||||||
// handle shell command argument
|
|
||||||
if command.is_some() { new.shell_command(command.unwrap().to_string_lossy()); }
|
|
||||||
|
|
||||||
// run command
|
|
||||||
new
|
|
||||||
.session_name(title)
|
|
||||||
.attach()
|
|
||||||
.start_directory(target_dir.unwrap_or(current_dir().unwrap().to_string_lossy().to_string()))
|
|
||||||
.output().ok();
|
|
||||||
}
|
|
||||||
|
|
4
src/command/mod.rs
Normal file
4
src/command/mod.rs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
pub mod share;
|
||||||
|
pub mod session;
|
||||||
|
|
52
src/command/session.rs
Normal file
52
src/command/session.rs
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
//! commands accessible from within a session
|
||||||
|
use std::fs::read_to_string;
|
||||||
|
|
||||||
|
use pico_args::Arguments;
|
||||||
|
use tmux_interface::{
|
||||||
|
Tmux,
|
||||||
|
commands
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{ error, flag, util };
|
||||||
|
|
||||||
|
const TMP_ROOT: &str = "/tmp/remux_path";
|
||||||
|
|
||||||
|
pub fn switch(pargs: &mut Arguments) {
|
||||||
|
util::terminal_enforce();
|
||||||
|
// refuse to run outside a session
|
||||||
|
util::session_enforce("switch");
|
||||||
|
|
||||||
|
// consume optional flags
|
||||||
|
let read_only = pargs.contains(flag::READ_ONLY);
|
||||||
|
//TODO: -d flag handling needs to be done manually
|
||||||
|
|
||||||
|
let args = pargs.clone().finish();
|
||||||
|
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 switch = commands::SwitchClient::new();
|
||||||
|
switch = switch.target_session(target);
|
||||||
|
if read_only { switch.read_only = true; }
|
||||||
|
|
||||||
|
Tmux::new()
|
||||||
|
.add_command(switch)
|
||||||
|
.output().ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn path() {
|
||||||
|
util::session_enforce("path");
|
||||||
|
|
||||||
|
let exec = commands::Run::new().shell_command("printf '#{session_path}' > ".to_string() + TMP_ROOT);
|
||||||
|
Tmux::new()
|
||||||
|
.add_command(exec)
|
||||||
|
.output().ok();
|
||||||
|
|
||||||
|
if let Ok(text) = read_to_string(TMP_ROOT) {
|
||||||
|
println!("{text}");
|
||||||
|
std::fs::remove_file(TMP_ROOT).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
193
src/command/share.rs
Normal file
193
src/command/share.rs
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
//! globally available tmux commands.
|
||||||
|
use std::{
|
||||||
|
ffi::OsString,
|
||||||
|
process::exit
|
||||||
|
};
|
||||||
|
|
||||||
|
use pico_args::{ Arguments, Error };
|
||||||
|
use termion::{ color, style };
|
||||||
|
use tmux_interface::{
|
||||||
|
Tmux,
|
||||||
|
commands
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
env::{ self, env_var },
|
||||||
|
error,
|
||||||
|
flag,
|
||||||
|
util
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn attach(pargs: &mut Arguments) {
|
||||||
|
// must be run from terminal
|
||||||
|
util::terminal_enforce();
|
||||||
|
// don't allow unflagged nests
|
||||||
|
util::prevent_nest();
|
||||||
|
|
||||||
|
// consume optional flags
|
||||||
|
let read_only = pargs.contains(flag::READ_ONLY);
|
||||||
|
let detach_other = pargs.contains(flag::DETACH);
|
||||||
|
|
||||||
|
let args = pargs.clone().finish();
|
||||||
|
let target: String;
|
||||||
|
let window: Option<&OsString>;
|
||||||
|
if args.len() < 1 {
|
||||||
|
// missing name will attempt to fall back to repository
|
||||||
|
target = util::repo_fallback();
|
||||||
|
if !util::session_exists(target.clone()) { error::missing_target(); }
|
||||||
|
window = None;
|
||||||
|
} else {
|
||||||
|
target = args.get(0).unwrap().to_string_lossy().to_string();
|
||||||
|
window = args.get(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure the session exists
|
||||||
|
let exists = util::session_exists(target.clone());
|
||||||
|
if !exists { error::no_target(target.clone()); }
|
||||||
|
|
||||||
|
// build attach command
|
||||||
|
let mut attach = commands::AttachSession::new();
|
||||||
|
attach = attach.target_session(target);
|
||||||
|
if read_only { attach.read_only = true; }
|
||||||
|
if detach_other { attach.detach_other = true; }
|
||||||
|
|
||||||
|
let select_window: Option<commands::SelectWindow>;
|
||||||
|
if let Some(window) = window {
|
||||||
|
let mut command = commands::SelectWindow::new();
|
||||||
|
command.target_window = Some(window.to_string_lossy());
|
||||||
|
select_window = Some(command);
|
||||||
|
} else { select_window = None; }
|
||||||
|
|
||||||
|
// build dispatch
|
||||||
|
let mut tmux = Tmux::new().add_command(attach);
|
||||||
|
if let Some(select_window) = select_window { tmux = tmux.add_command(select_window); }
|
||||||
|
tmux.output().ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn detach(pargs: &mut Arguments) {
|
||||||
|
util::terminal_enforce();
|
||||||
|
// get target or fallback
|
||||||
|
let args = pargs.clone().finish();
|
||||||
|
let target: String;
|
||||||
|
if args.len() < 1 {
|
||||||
|
target = util::repo_fallback();
|
||||||
|
} else {
|
||||||
|
target = args.get(0).unwrap().to_string_lossy().to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure the session exists
|
||||||
|
let exists = util::session_exists(target.clone());
|
||||||
|
if !exists { error::no_target(target.clone()); }
|
||||||
|
|
||||||
|
// build and dispatch
|
||||||
|
let detach = commands::DetachClient::new()
|
||||||
|
.target_session(target);
|
||||||
|
Tmux::new()
|
||||||
|
.add_command(detach)
|
||||||
|
.disable_echo().output().ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has(pargs: &mut Arguments) {
|
||||||
|
// consume optional flags
|
||||||
|
let quiet = pargs.contains(flag::QUIET);
|
||||||
|
|
||||||
|
// get target or fallback
|
||||||
|
let args = pargs.clone().finish();
|
||||||
|
let target: String;
|
||||||
|
if args.len() < 1 {
|
||||||
|
target = util::repo_fallback();
|
||||||
|
} else {
|
||||||
|
target = args.get(0).unwrap().to_string_lossy().to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
// run command
|
||||||
|
let success = util::session_exists(target.clone());
|
||||||
|
|
||||||
|
// print if not quiet
|
||||||
|
if !quiet {
|
||||||
|
println!("session \"{target}\" {}.",
|
||||||
|
if success { "exists" }
|
||||||
|
else { "does not exist" }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// emit exit code
|
||||||
|
exit( if success { 0 } else { 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list() {
|
||||||
|
// get session list
|
||||||
|
let sessions = util::get_sessions().unwrap_or(Vec::new());
|
||||||
|
|
||||||
|
// handle empty case
|
||||||
|
if sessions.len() == 0 {
|
||||||
|
println!("no sessions");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get attached session symbol
|
||||||
|
let attach_symbol = env_var(env::ATTACH_SYMBOL);
|
||||||
|
|
||||||
|
// pretty print session list
|
||||||
|
println!("sessions:");
|
||||||
|
for session in sessions.into_iter() {
|
||||||
|
let group = session.group.unwrap_or("[untitled]".to_string());
|
||||||
|
let id = session.id.unwrap();
|
||||||
|
let attached = session.attached.unwrap_or(0) > 0;
|
||||||
|
|
||||||
|
println!(
|
||||||
|
" {group} ({bold}{blue}{id}{reset}) {bold}{green}{attach}{reset}",
|
||||||
|
// values
|
||||||
|
attach = if attached { attach_symbol.clone() } else { "".to_string() },
|
||||||
|
// formatting
|
||||||
|
bold = style::Bold,
|
||||||
|
blue = color::Fg(color::Blue),
|
||||||
|
green = color::Fg(color::LightGreen),
|
||||||
|
reset = style::Reset
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(pargs: &mut Arguments) {
|
||||||
|
util::terminal_enforce();
|
||||||
|
// don't allow unflagged nesting
|
||||||
|
util::prevent_nest();
|
||||||
|
|
||||||
|
// get optional flags
|
||||||
|
let detached = pargs.contains(flag::DETACH);
|
||||||
|
let target_dir: Result<String, Error> = pargs.value_from_str(flag::TARGET);
|
||||||
|
|
||||||
|
// get environment variables
|
||||||
|
let window_name = env_var(env::NEW_WINDOW_NAME);
|
||||||
|
|
||||||
|
// get target or fallback
|
||||||
|
let args = pargs.clone().finish();
|
||||||
|
let title: String;
|
||||||
|
let command: Option<&OsString>;
|
||||||
|
if args.len() < 1 {
|
||||||
|
// attempt repo fallback
|
||||||
|
title = util::repo_fallback();
|
||||||
|
command = None;
|
||||||
|
} else {
|
||||||
|
title = args.get(0).unwrap().to_string_lossy().to_string();
|
||||||
|
command = args.get(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut new = commands::NewSession::new();
|
||||||
|
new = new.group_name(title);
|
||||||
|
if let Some(command) = command { new.shell_command = Some(command.to_string_lossy()); }
|
||||||
|
if detached { new.detached = true; }
|
||||||
|
if let Ok(target_dir) = target_dir { new = new.start_directory(target_dir); }
|
||||||
|
|
||||||
|
let mut tmux = Tmux::new().add_command(new);
|
||||||
|
|
||||||
|
// rename window if var not empty
|
||||||
|
if !window_name.is_empty() {
|
||||||
|
let auto_name = commands::RenameWindow::new()
|
||||||
|
.new_name(window_name);
|
||||||
|
tmux = tmux.add_command(auto_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
tmux.output().ok();
|
||||||
|
}
|
||||||
|
|
13
src/env.rs
Normal file
13
src/env.rs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
use std::env::var;
|
||||||
|
|
||||||
|
pub type EnvVar = (&'static str, &'static str);
|
||||||
|
|
||||||
|
pub static ATTACH_SYMBOL: EnvVar = ("REMUX_ATTACH_SYMBOL", "*");
|
||||||
|
pub static NEW_WINDOW_NAME: EnvVar = ("REMUX_NEW_WINDOW", "");
|
||||||
|
|
||||||
|
pub fn env_var(envvar: EnvVar) -> String {
|
||||||
|
var(envvar.0).unwrap_or(envvar.1.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tmux() -> bool { !var("TMUX").unwrap_or("".to_string()).is_empty() }
|
||||||
|
|
|
@ -37,3 +37,9 @@ pub fn not_nesting() {
|
||||||
exit(6);
|
exit(6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// tried to run a session command outside a session; code 7
|
||||||
|
pub fn not_in_session(cmd: &'static str) {
|
||||||
|
println!("remux: '{cmd}' must be run from within a session");
|
||||||
|
exit(7);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
11
src/flag.rs
Normal file
11
src/flag.rs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
|
||||||
|
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"];
|
||||||
|
|
64
src/help.rs
64
src/help.rs
|
@ -16,16 +16,25 @@ A command wrapper for tmux written in Rust.
|
||||||
usage: remux <command> [<args>]
|
usage: remux <command> [<args>]
|
||||||
|
|
||||||
commands:
|
commands:
|
||||||
help Show help text for remux or a specific command
|
help Show help text for remux, a command, or a help topic.
|
||||||
attach Attach to an existing tmux session
|
attach Attach to an existing tmux session
|
||||||
detach Detach clients from a tmux session
|
detach Detach clients from a tmux session
|
||||||
has Check if a tmux session exists
|
has Check if a tmux session exists
|
||||||
list Pretty-print all tmux sessions
|
list Pretty-print all tmux sessions
|
||||||
new Create a new tmux session
|
new Create a new tmux session
|
||||||
|
|
||||||
Use 'remux help <command>' to see detailed help text for each command."),
|
path print session path (session)
|
||||||
|
switch switch to another session (session)
|
||||||
|
|
||||||
Some("a" | "attach")
|
Use 'remux help <command>' to see detailed help text for each command.
|
||||||
|
|
||||||
|
help topics:
|
||||||
|
env Environment variables"),
|
||||||
|
|
||||||
|
|
||||||
|
// COMMAND HELP
|
||||||
|
|
||||||
|
Some("a" | "attach")
|
||||||
=>
|
=>
|
||||||
println!("remux attach
|
println!("remux attach
|
||||||
Attach to an existing session.
|
Attach to an existing session.
|
||||||
|
@ -40,7 +49,7 @@ args:
|
||||||
flags:
|
flags:
|
||||||
-d, --detach Detach other attached clients from the session
|
-d, --detach Detach other attached clients from the session
|
||||||
-n, --nest Attach the session inside another session.
|
-n, --nest Attach the session inside another session.
|
||||||
-r, --readonly Attach the session as read-only"),
|
-r, --read-only Attach the session as read-only"),
|
||||||
|
|
||||||
Some("d" | "detach")
|
Some("d" | "detach")
|
||||||
=>
|
=>
|
||||||
|
@ -53,12 +62,13 @@ usage: remux detach <session>
|
||||||
args:
|
args:
|
||||||
<session> The session name to detach clients from"),
|
<session> The session name to detach clients from"),
|
||||||
|
|
||||||
Some("has")
|
Some("h" | "has")
|
||||||
=>
|
=>
|
||||||
println!("remux has
|
println!("remux has
|
||||||
Check if the target session exists.
|
Check if the target session exists.
|
||||||
|
|
||||||
usage: remux has [flags] <session>
|
usage: remux has [flags] <session>
|
||||||
|
rmux h [flags] session
|
||||||
|
|
||||||
args:
|
args:
|
||||||
<session> The session to check for
|
<session> The session to check for
|
||||||
|
@ -81,6 +91,7 @@ println!("remux new
|
||||||
Create a new tmux session.
|
Create a new tmux session.
|
||||||
|
|
||||||
usage: remux new [flags] <title> [command]
|
usage: remux new [flags] <title> [command]
|
||||||
|
remux n [flags] <title> [command]
|
||||||
|
|
||||||
args:
|
args:
|
||||||
<title> The title of the new session
|
<title> The title of the new session
|
||||||
|
@ -90,7 +101,48 @@ flags:
|
||||||
-n, --nest Create the session inside another session.
|
-n, --nest Create the session inside another session.
|
||||||
-t, --target <dir> Sets the target directory for the new session."),
|
-t, --target <dir> Sets the target directory for the new session."),
|
||||||
|
|
||||||
// not found
|
Some("root")
|
||||||
|
=>
|
||||||
|
println!("remux path
|
||||||
|
Print the session path (#{{session_path}}) to standard output.
|
||||||
|
Must be run from inside a session.
|
||||||
|
|
||||||
|
usage: remux path
|
||||||
|
remux p"),
|
||||||
|
|
||||||
|
Some("s" | "switch")
|
||||||
|
=>
|
||||||
|
println!("remux switch
|
||||||
|
Switch to a different tmux session.
|
||||||
|
Must be run from inside a session.
|
||||||
|
|
||||||
|
usage: remux switch [flags] <title>
|
||||||
|
remux s [flags] <title>
|
||||||
|
|
||||||
|
args:
|
||||||
|
<title> The title of the session to switch to.
|
||||||
|
|
||||||
|
flags:
|
||||||
|
-r, --read-only Attach the target session as read-only."),
|
||||||
|
|
||||||
|
|
||||||
|
// TOPIC HELP
|
||||||
|
|
||||||
|
Some("env" | "vars")
|
||||||
|
=>
|
||||||
|
println!("remux environment variables
|
||||||
|
|
||||||
|
REMUX_ATTACH_SYMBOL
|
||||||
|
Changes the symbol displayed for attached sessions displayed
|
||||||
|
by the 'list' command.
|
||||||
|
Default: '*'
|
||||||
|
|
||||||
|
REMUX_NEW_WINDOW
|
||||||
|
Provides a default window name when creating a session with
|
||||||
|
the 'new' command, if not empty.
|
||||||
|
Default: ''"),
|
||||||
|
|
||||||
|
// not found
|
||||||
_ => error::no_help(topic.unwrap())
|
_ => error::no_help(topic.unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
35
src/main.rs
35
src/main.rs
|
@ -1,12 +1,11 @@
|
||||||
use std::{
|
use std::env::{ set_var, var };
|
||||||
env::{ set_var, var },
|
|
||||||
io::{ stdout, IsTerminal }
|
|
||||||
};
|
|
||||||
|
|
||||||
use pico_args::Arguments;
|
use pico_args::Arguments;
|
||||||
|
|
||||||
mod command;
|
mod command;
|
||||||
|
mod env;
|
||||||
mod error;
|
mod error;
|
||||||
|
mod flag;
|
||||||
mod help;
|
mod help;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
|
@ -19,17 +18,17 @@ fn main() {
|
||||||
let mut args = Arguments::from_env();
|
let mut args = Arguments::from_env();
|
||||||
|
|
||||||
// consume flags
|
// consume flags
|
||||||
if args.contains(["-h", "--help"]) {
|
if args.contains(flag::HELP) {
|
||||||
help(&mut args);
|
help(&mut args);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if args.contains(["-v", "--version"]) {
|
if args.contains(flag::VERSION) {
|
||||||
version();
|
version();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let nesting = args.contains(["-n", "--nest"]);
|
let nesting = args.contains(flag::NEST);
|
||||||
let tmux_var = var("TMUX").ok();
|
let tmux_var = var("TMUX").ok();
|
||||||
if nesting {
|
if nesting {
|
||||||
if tmux_var.is_none() {
|
if tmux_var.is_none() {
|
||||||
|
@ -38,30 +37,34 @@ fn main() {
|
||||||
set_var("TMUX", "");
|
set_var("TMUX", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
if !stdout().is_terminal() { error::not_terminal(); }
|
|
||||||
|
|
||||||
let subcommand = args.subcommand().unwrap();
|
let subcommand = args.subcommand().unwrap();
|
||||||
|
|
||||||
// invoke subcommand function
|
// invoke subcommand function
|
||||||
match subcommand.as_deref() {
|
match subcommand.as_deref() {
|
||||||
Some("h" | "help")
|
Some("help")
|
||||||
=> help(&mut args),
|
=> help(&mut args),
|
||||||
|
|
||||||
Some("a" | "attach")
|
Some("a" | "attach")
|
||||||
=> command::attach(&mut args),
|
=> command::share::attach(&mut args),
|
||||||
|
|
||||||
Some("d" | "detach")
|
Some("d" | "detach")
|
||||||
=> command::detach(&mut args),
|
=> command::share::detach(&mut args),
|
||||||
|
|
||||||
Some("has")
|
Some("h" | "has")
|
||||||
=> command::has(&mut args),
|
=> command::share::has(&mut args),
|
||||||
|
|
||||||
None |
|
None |
|
||||||
Some("l" | "ls" | "list")
|
Some("l" | "ls" | "list")
|
||||||
=> command::list(),
|
=> command::share::list(),
|
||||||
|
|
||||||
Some("n" | "new")
|
Some("n" | "new")
|
||||||
=> command::new(&mut args),
|
=> command::share::new(&mut args),
|
||||||
|
|
||||||
|
Some("p" | "path")
|
||||||
|
=> command::session::path(),
|
||||||
|
|
||||||
|
Some("s" | "switch")
|
||||||
|
=> command::session::switch(&mut args),
|
||||||
|
|
||||||
_
|
_
|
||||||
=> error::no_subcommand(subcommand.unwrap())
|
=> error::no_subcommand(subcommand.unwrap())
|
||||||
|
|
50
src/util.rs
50
src/util.rs
|
@ -1,44 +1,58 @@
|
||||||
use std::{
|
use std::{
|
||||||
env::{ current_dir, var },
|
env::current_dir,
|
||||||
|
io::{ stdout, IsTerminal },
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
process::exit
|
process::exit
|
||||||
};
|
};
|
||||||
|
|
||||||
use tmux_interface::{
|
use tmux_interface::{
|
||||||
Session, Sessions, TmuxCommand,
|
Session, Tmux,
|
||||||
variables::session::session::SESSION_ALL
|
|
||||||
|
commands,
|
||||||
|
variables::session::SessionsCtl
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::error;
|
use crate::{
|
||||||
|
env,
|
||||||
|
error
|
||||||
|
};
|
||||||
|
|
||||||
/// return a Vec of all sessions or None
|
/// return a Vec of all sessions or None
|
||||||
pub fn get_sessions() -> Option<Vec<Session>> {
|
pub fn get_sessions() -> Option<Vec<Session>> {
|
||||||
let i_sessions = Sessions::get(SESSION_ALL);
|
let sessions = SessionsCtl::new().get_all();
|
||||||
if i_sessions.is_err() { return None; }
|
if let Ok(sessions) = sessions {
|
||||||
let sessions = i_sessions.ok();
|
return Some(sessions.0);
|
||||||
if sessions.is_none() { return None; }
|
} else { return None; }
|
||||||
|
|
||||||
Some(sessions.unwrap().0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// show the tmux nest text if env var is not unset
|
/// show the tmux nest text if env var is not unset
|
||||||
pub fn prevent_nest() {
|
pub fn prevent_nest() {
|
||||||
let tmux = var("TMUX").ok();
|
if env::tmux() {
|
||||||
if tmux.is_some() && tmux.unwrap() != "" {
|
println!("To nest sessions, use the -n flag.");
|
||||||
println!("Sessions should be nested with care; unset TMUX or use the '-n' flag to allow.");
|
exit(6);
|
||||||
exit(1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// enforce a command is being used in-session
|
||||||
|
pub fn session_enforce(cmd: &'static str) {
|
||||||
|
if !env::tmux() { error::not_in_session(cmd); }
|
||||||
|
}
|
||||||
|
|
||||||
/// check whether a target session exists
|
/// check whether a target session exists
|
||||||
pub fn session_exists<S: Into<String>>(target: S) -> bool {
|
pub fn session_exists<S: Into<String>>(target: S) -> bool {
|
||||||
TmuxCommand::new()
|
let has_session = commands::HasSession::new()
|
||||||
.has_session()
|
.target_session(target.into());
|
||||||
.target_session(target.into())
|
Tmux::new().add_command(has_session)
|
||||||
.output().unwrap()
|
.status()
|
||||||
|
.unwrap()
|
||||||
.success()
|
.success()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// enforce a command is being run in a terminal
|
||||||
|
pub fn terminal_enforce() {
|
||||||
|
if !stdout().is_terminal() { error::not_terminal(); }
|
||||||
|
}
|
||||||
|
|
||||||
/// attempt to return the repo name or exit
|
/// attempt to return the repo name or exit
|
||||||
pub fn repo_fallback() -> String {
|
pub fn repo_fallback() -> String {
|
||||||
let repo = repo_root(current_dir().unwrap());
|
let repo = repo_root(current_dir().unwrap());
|
||||||
|
|
Loading…
Reference in a new issue