'new' now fails on duplicate names, reduced code reuse
This commit is contained in:
parent
21bd6f40ca
commit
b24778113a
4 changed files with 90 additions and 80 deletions
|
@ -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,9 +57,9 @@ 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();
|
||||||
|
@ -81,22 +69,16 @@ pub fn context_action(state: &State) {
|
||||||
new(&mut substate);
|
new(&mut substate);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else {
|
}
|
||||||
|
}
|
||||||
// fallback behavior is list
|
// fallback behavior is list
|
||||||
list(&state);
|
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); }
|
||||||
|
|
||||||
|
|
13
src/error.rs
13
src/error.rs
|
@ -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");
|
||||||
|
|
46
src/state.rs
46
src/state.rs
|
@ -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 }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
20
src/util.rs
20
src/util.rs
|
@ -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 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue