'new' now fails on duplicate names, reduced code reuse

This commit is contained in:
Valerie Wolfe 2024-07-01 11:52:33 -04:00
parent 21bd6f40ca
commit b24778113a
4 changed files with 90 additions and 80 deletions

View file

@ -1,8 +1,5 @@
//! globally available tmux commands. //! globally available tmux commands.
use std::{ use std::process::exit;
ffi::OsString,
process::exit
};
use pico_args::{ Arguments, Error }; use pico_args::{ Arguments, Error };
use termion::{ color, style }; use termion::{ color, style };
@ -27,18 +24,9 @@ pub fn attach(state: &mut State) {
let read_only = state.flags.read_only; let read_only = state.flags.read_only;
let detach_other = state.flags.detached; let detach_other = state.flags.detached;
let args = state.args.clone().finish(); // consume arguments
let target: String; let target = state.target_title().unwrap();
let window: Option<&OsString>; let window = state.target();
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);
}
// do not allow attaching to the same session // do not allow attaching to the same session
if state.session && target == state.title.clone().unwrap() { error::same_session(); } if state.session && target == state.title.clone().unwrap() { error::same_session(); }
@ -56,7 +44,7 @@ pub fn attach(state: &mut State) {
let select_window: Option<commands::SelectWindow>; let select_window: Option<commands::SelectWindow>;
if let Some(window) = window { if let Some(window) = window {
let mut command = commands::SelectWindow::new(); let mut command = commands::SelectWindow::new();
command.target_window = Some(window.to_string_lossy()); command.target_window = Some(window.into());
select_window = Some(command); select_window = Some(command);
} else { select_window = None; } } else { select_window = None; }
@ -69,34 +57,28 @@ pub fn attach(state: &mut State) {
} }
pub fn context_action(state: &State) { pub fn context_action(state: &State) {
let repo = util::repo_root(std::env::current_dir().unwrap()); if !state.session {
if !state.session && repo.is_some() { if let Some(repository) = &state.repository {
let target = util::repo_fallback(); let target = repository.name.clone();
let mut args = Arguments::from_vec( vec![(&target).into()] ); let mut args = Arguments::from_vec( vec![(&target).into()] );
let mut substate = State::new(&mut args); let mut substate = State::new(&mut args);
substate.flags = state.flags.clone(); substate.flags = state.flags.clone();
if util::session_exists(&target) { if util::session_exists(&target) {
attach(&mut substate); attach(&mut substate);
} else { } else {
new(&mut substate); new(&mut substate);
}
return;
} }
return;
} else {
// fallback behavior is list
list(&state);
} }
// fallback behavior is list
list(&state);
} }
pub fn detach(state: &mut State) { pub fn detach(state: &mut State) {
util::terminal_enforce(); util::terminal_enforce();
// get target or fallback
let args = state.args.clone().finish(); let target = state.target_title().unwrap();
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 // make sure the session exists
let exists = util::session_exists(target.clone()); let exists = util::session_exists(target.clone());
@ -114,14 +96,8 @@ pub fn has(state: &mut State) {
// consume optional flags // consume optional flags
let quiet = state.flags.quiet; let quiet = state.flags.quiet;
// get target or fallback // get target
let args = state.args.clone().finish(); let target = state.target_title().unwrap();
let target: String;
if args.len() < 1 {
target = util::repo_fallback();
} else {
target = args.get(0).unwrap().to_string_lossy().to_string();
}
// run command // run command
let success = util::session_exists(target.clone()); let success = util::session_exists(target.clone());
@ -189,22 +165,17 @@ pub fn new(state: &mut State) {
// get environment variables // get environment variables
let window_name = env_var(env::NEW_WINDOW_NAME); let window_name = env_var(env::NEW_WINDOW_NAME);
// get target or fallback // consume arguments
let args = state.args.clone().finish(); let title = state.target_title().unwrap();
let title: String; let command = state.target();
let command: Option<&OsString>;
if args.len() < 1 { // don't allow duplicate names
// attempt repo fallback let exists = util::session_exists(title.clone());
title = util::repo_fallback(); if exists { error::target_exists(title.clone()); }
command = None;
} else {
title = args.get(0).unwrap().to_string_lossy().to_string();
command = args.get(1);
}
let mut new = commands::NewSession::new(); let mut new = commands::NewSession::new();
new = new.session_name(title); new = new.session_name(title);
if let Some(command) = command { new.shell_command = Some(command.to_string_lossy()); } if let Some(command) = command { new.shell_command = Some(command.into()); }
if detached { new.detached = true; } if detached { new.detached = true; }
if let Ok(target_dir) = target_dir { new = new.start_directory(target_dir); } if let Ok(target_dir) = target_dir { new = new.start_directory(target_dir); }

View file

@ -6,6 +6,7 @@ pub fn no_subcommand(subcommand: String) {
exit(1); exit(1);
} }
/// target session not found; code 2 /// 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(); let target = target.into();
@ -13,12 +14,14 @@ pub fn no_target<S: Into<String>>(target: S) {
exit(2); exit(2);
} }
/// help topic doesn't exist; code 3 /// help topic doesn't exist; code 3
pub fn no_help(topic: String) { pub fn no_help(topic: String) {
println!("remux: no help for \"{topic}\""); println!("remux: no help for \"{topic}\"");
exit(3); exit(3);
} }
/// user provided no target; code 4 /// user provided no target; code 4
pub fn missing_target() { pub fn missing_target() {
println!("remux: no target provided"); println!("remux: no target provided");
@ -31,12 +34,21 @@ pub fn same_session() {
exit(4); exit(4);
} }
/// a session with the target name already exists; code 4
pub fn target_exists<S: Into<String>>(target: S) {
let target = target.into();
println!("remux: session \"{target}\" already exists");
exit(4);
}
/// non-terminal environment prevention; code 5 /// non-terminal environment prevention; code 5
pub fn not_terminal() { pub fn not_terminal() {
println!("remux: not running from a terminal"); println!("remux: not running from a terminal");
exit(5); exit(5);
} }
/// tried to nest while not in a session; code 6 /// tried to nest while not in a session; code 6
pub fn not_nesting() { pub fn not_nesting() {
println!("remux: inappropriate nesting flag (-n); not in a session"); println!("remux: inappropriate nesting flag (-n); not in a session");
@ -56,6 +68,7 @@ pub fn conflict_nest(reason: Option<&'static str>) {
exit(6); exit(6);
} }
/// tried to run a session command outside a session; code 7 /// 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) {
println!("remux: '{cmd}' must be run from within a session"); println!("remux: '{cmd}' must be run from within a session");

View file

@ -1,4 +1,7 @@
use std::env; use std::{
env,
path::PathBuf
};
use pico_args::Arguments; use pico_args::Arguments;
@ -6,7 +9,7 @@ use crate::{
env::TMUX, env::TMUX,
error, error,
flag::Flags, flag::Flags,
util::session_name util::{ find, session_name }
}; };
pub struct State<'a> { pub struct State<'a> {
@ -15,7 +18,9 @@ pub struct State<'a> {
pub session: bool, pub session: bool,
tmux_var: Option<String>, tmux_var: Option<String>,
pub title: Option<String> pub title: Option<String>,
pub repository: Option<Repository>
} }
impl State<'_> { impl State<'_> {
@ -25,13 +30,17 @@ impl State<'_> {
let tmux_var = env::var(TMUX).ok(); let tmux_var = env::var(TMUX).ok();
let session = tmux_var.is_some(); let session = tmux_var.is_some();
let title = if session { session_name() } else { None }; let title = if session { session_name() } else { None };
let repository = Repository::find();
State { State {
args, args,
flags, flags,
session, session,
tmux_var, tmux_var,
title title,
repository
} }
} }
@ -54,5 +63,34 @@ impl State<'_> {
} }
pub fn target(&mut self) -> Option<String> { self.args.subcommand().unwrap_or(None) } 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();
None
}
}
}
pub struct Repository {
pub path: PathBuf,
pub name: String
}
impl Repository {
pub fn find() -> Option<Repository> {
let path = find(".git", env::current_dir().unwrap());
if let Some(path) = path {
let name = path.file_name().unwrap().to_string_lossy().to_string();
let inner = Repository {
path,
name
};
Some(inner)
} else { None }
}
} }

View file

@ -1,5 +1,4 @@
use std::{ use std::{
env::current_dir,
io::{ stdout, IsTerminal }, io::{ stdout, IsTerminal },
path::PathBuf path::PathBuf
}; };
@ -48,23 +47,12 @@ pub fn terminal_enforce() {
if !stdout().is_terminal() { error::not_terminal(); } if !stdout().is_terminal() { error::not_terminal(); }
} }
/// attempt to return the repo name or exit /// recursively propagate up directories to find a child
pub fn repo_fallback() -> String { pub fn find(target: &'static str, path: PathBuf) -> Option<PathBuf> {
let repo = repo_root(current_dir().unwrap()); if path.join(target).exists() { return Some(path); }
if repo.is_none() { error::missing_target(); }
let target = repo.unwrap().file_name().unwrap().to_string_lossy().to_string();
target
}
/// recursively attempt to find a git root directory
pub fn repo_root(path: PathBuf) -> Option<PathBuf> {
// if .git dir is found, return
if path.join(".git").exists() { return Some(path); }
// otherwise, attempt to traverse
let parent = path.parent(); let parent = path.parent();
if let Some(parent) = parent { repo_root(parent.to_path_buf()) } if let Some(parent) = parent { return find(target, parent.to_path_buf()) }
else { None } else { None }
} }