From ace9627b793b58bd5fb3beb5f187d848273d9d41 Mon Sep 17 00:00:00 2001 From: Valerie Date: Mon, 24 Jun 2024 16:32:41 -0400 Subject: [PATCH] major refactor; parameters are handled by a state struct --- src/command/session.rs | 46 ++++++++++++++++++--------------- src/command/share.rs | 54 +++++++++++++++++++++------------------ src/env.rs | 5 ++-- src/error.rs | 11 ++++++++ src/flag.rs | 30 ++++++++++++++++++++++ src/main.rs | 40 +++++++++++++---------------- src/state.rs | 58 ++++++++++++++++++++++++++++++++++++++++++ src/util.rs | 33 ++++++++++-------------- 8 files changed, 188 insertions(+), 89 deletions(-) create mode 100644 src/state.rs diff --git a/src/command/session.rs b/src/command/session.rs index ca33fa5..f030db6 100644 --- a/src/command/session.rs +++ b/src/command/session.rs @@ -1,26 +1,41 @@ //! 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 }; +use crate::{ + error, + state::State, + util +}; -const TMP_ROOT: &str = "/tmp/remux_path"; +pub fn path(state: &mut State) { + state.session_enforce("path"); -pub fn switch(pargs: &mut Arguments) { + 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]); + } +} + +pub fn switch(state: &mut State) { util::terminal_enforce(); // refuse to run outside a session - util::session_enforce("switch"); + state.session_enforce("switch"); // consume optional flags - let read_only = pargs.contains(flag::READ_ONLY); + let read_only = state.flags.read_only; //TODO: -d flag handling needs to be done manually - let args = pargs.clone().finish(); + let args = state.args.clone().finish(); if args.len() < 1 { error::missing_target(); } let target = args.get(0).unwrap().to_string_lossy().to_string(); @@ -36,17 +51,8 @@ pub fn switch(pargs: &mut Arguments) { .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(); - } +pub fn which(state: State) { + state.session_enforce("which"); + if let Some(title) = state.title { println!("{title}"); } } diff --git a/src/command/share.rs b/src/command/share.rs index 6927240..fb89728 100644 --- a/src/command/share.rs +++ b/src/command/share.rs @@ -15,20 +15,19 @@ use crate::{ env::{ self, env_var }, error, flag, + state::State, util }; -pub fn attach(pargs: &mut Arguments) { - // must be run from terminal +pub fn attach(state: &mut State) { util::terminal_enforce(); - // don't allow unflagged nests - util::prevent_nest(); + state.nest_init(); // consume optional flags - let read_only = pargs.contains(flag::READ_ONLY); - let detach_other = pargs.contains(flag::DETACH); + let read_only = state.flags.read_only; + let detach_other = state.flags.detached; - let args = pargs.clone().finish(); + let args = state.args.clone().finish(); let target: String; let window: Option<&OsString>; if args.len() < 1 { @@ -41,6 +40,9 @@ pub fn attach(pargs: &mut Arguments) { window = args.get(1); } + // do not allow attaching to the same session + if state.session && target == state.title.clone().unwrap() { error::same_session(); } + // make sure the session exists let exists = util::session_exists(target.clone()); if !exists { error::no_target(target.clone()); } @@ -62,17 +64,20 @@ pub fn attach(pargs: &mut Arguments) { let mut tmux = Tmux::new().add_command(attach); if let Some(select_window) = select_window { tmux = tmux.add_command(select_window); } tmux.output().ok(); + + state.nest_deinit(); } -pub fn context_action() { +pub fn context_action(state: &State) { let repo = util::repo_root(std::env::current_dir().unwrap()); - if !env::tmux() && repo.is_some() { + if !state.session && repo.is_some() { let target = util::repo_fallback(); let mut args = Arguments::from_vec( vec![(&target).into()] ); + let mut substate = State::new(&mut args); if util::session_exists(&target) { - attach(&mut args); + attach(&mut substate); } else { - new(&mut args); + new(&mut substate); } return; } else { @@ -81,10 +86,10 @@ pub fn context_action() { } } -pub fn detach(pargs: &mut Arguments) { +pub fn detach(state: &mut State) { util::terminal_enforce(); // get target or fallback - let args = pargs.clone().finish(); + let args = state.args.clone().finish(); let target: String; if args.len() < 1 { target = util::repo_fallback(); @@ -104,12 +109,12 @@ pub fn detach(pargs: &mut Arguments) { .disable_echo().output().ok(); } -pub fn has(pargs: &mut Arguments) { +pub fn has(state: &mut State) { // consume optional flags - let quiet = pargs.contains(flag::QUIET); + let quiet = state.flags.quiet; // get target or fallback - let args = pargs.clone().finish(); + let args = state.args.clone().finish(); let target: String; if args.len() < 1 { target = util::repo_fallback(); @@ -148,12 +153,12 @@ pub fn list() { // pretty print session list println!("sessions:"); for session in sessions.into_iter() { - let group = session.group.unwrap_or("[untitled]".to_string()); + let name = session.name.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}", + " {name} ({bold}{blue}{id}{reset}) {bold}{green}{attach}{reset}", // values attach = if attached { attach_symbol.clone() } else { "".to_string() }, // formatting @@ -165,20 +170,19 @@ pub fn list() { } } -pub fn new(pargs: &mut Arguments) { +pub fn new(state: &mut State) { util::terminal_enforce(); - // don't allow unflagged nesting - util::prevent_nest(); + state.nest_init(); // get optional flags - let detached = pargs.contains(flag::DETACH); - let target_dir: Result = pargs.value_from_str(flag::TARGET); + let detached = state.flags.detached; + let target_dir: Result = state.args.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 args = state.args.clone().finish(); let title: String; let command: Option<&OsString>; if args.len() < 1 { @@ -191,7 +195,7 @@ pub fn new(pargs: &mut Arguments) { } let mut new = commands::NewSession::new(); - new = new.group_name(title); + new = new.session_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); } diff --git a/src/env.rs b/src/env.rs index a5b4450..4376a30 100644 --- a/src/env.rs +++ b/src/env.rs @@ -5,9 +5,10 @@ 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 static TMUX: &str = "TMUX"; + +/// get or default an environment variable 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() } - diff --git a/src/error.rs b/src/error.rs index 194245f..c792fe2 100644 --- a/src/error.rs +++ b/src/error.rs @@ -25,6 +25,12 @@ pub fn missing_target() { exit(4); } +/// refuse to attach to current session; code 4 +pub fn same_session() { + println!("remux: cannot attach to same session"); + exit(4); +} + /// non-terminal environment prevention; code 5 pub fn not_terminal() { println!("remux: not running from a terminal"); @@ -37,6 +43,11 @@ pub fn not_nesting() { exit(6); } +pub fn prevent_nest() { + println!("remux: cannot nest sessions without the nest flag ('-n')"); + 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"); diff --git a/src/flag.rs b/src/flag.rs index 4c7066d..e518724 100644 --- a/src/flag.rs +++ b/src/flag.rs @@ -1,4 +1,6 @@ +use pico_args::Arguments; + type Flag = [&'static str;2]; pub static DETACH: Flag = ["-d", "--detach"]; @@ -9,3 +11,31 @@ 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, + pub nested: bool, + pub quiet: bool, + pub read_only: bool, + pub target: Option, +} + +impl Flags { + + pub fn from(args: &mut Arguments) -> Flags { + let detached = args.contains(DETACH); + let nested = args.contains(NEST); + let quiet = args.contains(QUIET); + let read_only = args.contains(READ_ONLY); + let target = args.value_from_str(TARGET).ok(); + + Flags { + detached, + nested, + quiet, + read_only, + target + } + } + +} + diff --git a/src/main.rs b/src/main.rs index ca3b08c..1c2c6f9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,3 @@ -use std::env::{ set_var, var }; use pico_args::Arguments; @@ -7,9 +6,12 @@ mod env; mod error; mod flag; mod help; +mod script; +mod state; mod util; use help::{ help, version }; +use state::State; static VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -28,52 +30,44 @@ fn main() { return; } - let nesting = args.contains(flag::NEST); - let tmux_var = var("TMUX").ok(); - if nesting { - if tmux_var.is_none() { - error::not_nesting(); - } - set_var("TMUX", ""); - } + let mut state = State::new(&mut args); - let subcommand = args.subcommand().unwrap(); + let target = state.target(); // invoke subcommand function - match subcommand.as_deref() { + match target.as_deref() { Some("help") => help(&mut args), None - => command::share::context_action(), + => command::share::context_action(&state), Some("a" | "attach") - => command::share::attach(&mut args), + => command::share::attach(&mut state), Some("d" | "detach") - => command::share::detach(&mut args), + => command::share::detach(&mut state), Some("h" | "has") - => command::share::has(&mut args), + => command::share::has(&mut state), Some("l" | "ls" | "list") => command::share::list(), Some("n" | "new") - => command::share::new(&mut args), + => command::share::new(&mut state), Some("p" | "path") - => command::session::path(), + => command::session::path(&mut state), Some("s" | "switch") - => command::session::switch(&mut args), + => command::session::switch(&mut state), + + Some("w" | "which" | "title") + => command::session::which(state), _ - => error::no_subcommand(subcommand.unwrap()) + => error::no_subcommand(target.unwrap()) } - // re-set TMUX var if we unset it for nest mode - if nesting { - set_var("TMUX", tmux_var.unwrap()); - } } diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..dec9cb8 --- /dev/null +++ b/src/state.rs @@ -0,0 +1,58 @@ +use std::env; + +use pico_args::Arguments; + +use crate::{ + env::TMUX, + error, + flag::Flags, + util::session_name +}; + +pub struct State<'a> { + pub args: &'a mut Arguments, + pub flags: Flags, + + pub session: bool, + tmux_var: Option, + pub title: Option +} + +impl State<'_> { + + pub fn new(args: &mut Arguments) -> State { + let flags = Flags::from(args); + let tmux_var = env::var(TMUX).ok(); + let session = tmux_var.is_some(); + let title = if session { session_name() } else { None }; + + State { + args, + flags, + session, + tmux_var, + title + } + } + + pub fn nest_init(&self) { + if self.flags.nested { + if self.session { env::set_var(TMUX, ""); } // nesting & session => ok + else { error::not_nesting(); } // nesting & !session => error + } else if self.session { error::prevent_nest(); } // !nesting & session => error + // !nesting & !session => ok + } + + pub fn nest_deinit(&self) { + if self.flags.nested && self.session { + env::set_var(TMUX, self.tmux_var.as_ref().unwrap()); + } + } + + pub fn session_enforce(&self, cmd: &'static str) { + if !self.session { error::not_in_session(cmd); } + } + + pub fn target(&mut self) -> Option { self.args.subcommand().unwrap_or(None) } +} + diff --git a/src/util.rs b/src/util.rs index 986af11..397617b 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,8 +1,7 @@ use std::{ env::current_dir, io::{ stdout, IsTerminal }, - path::PathBuf, - process::exit + path::PathBuf }; use tmux_interface::{ @@ -12,10 +11,19 @@ use tmux_interface::{ variables::session::SessionsCtl }; -use crate::{ - env, - error -}; +use crate::error; + +pub fn session_name() -> Option { + 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 { + Some(title[0..title.len() - 1].to_owned()) + } else { None } + } else { None } +} /// return a Vec of all sessions or None pub fn get_sessions() -> Option> { @@ -25,19 +33,6 @@ pub fn get_sessions() -> Option> { } else { return None; } } -/// show the tmux nest text if env var is not unset -pub fn prevent_nest() { - if env::tmux() { - println!("To nest sessions, use the -n flag."); - exit(6); - } -} - -/// 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 pub fn session_exists>(target: S) -> bool { let has_session = commands::HasSession::new()