diff --git a/src/cli.rs b/src/cli.rs index 9c3b595..d3c61da 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -6,7 +6,6 @@ use crate::{ execute_command, get_single_selection, picker::Preview, session::{create_sessions, SessionContainer}, - session_exists, set_up_tmux_env, switch_to_session, tmux::Tmux, Result, TmsError, }; @@ -151,7 +150,7 @@ impl Cli { } Some(CliCommand::Windows) => { - windows_command(config, tmux)?; + windows_command(&config, tmux)?; Ok(SubCommandGiven::Yes) } // Handle the config subcommand @@ -263,20 +262,16 @@ fn switch_command(config: Config, tmux: &Tmux) -> Result<()> { .collect::>(); } - if let Some(target_session) = get_single_selection( - &sessions, - Preview::SessionPane, - config.picker_colors, - config.shortcuts, - tmux.clone(), - )? { + if let Some(target_session) = + get_single_selection(&sessions, Preview::SessionPane, &config, tmux)? + { tmux.switch_client(&target_session.replace('.', "_")); } Ok(()) } -fn windows_command(config: Config, tmux: &Tmux) -> Result<()> { +fn windows_command(config: &Config, tmux: &Tmux) -> Result<()> { let windows = tmux.list_windows( "'#{?window_attached,,#{window_index} #{window_name}}'", None, @@ -290,13 +285,8 @@ fn windows_command(config: Config, tmux: &Tmux) -> Result<()> { .map(|s| s.to_string()) .collect(); - if let Some(target_window) = get_single_selection( - &windows, - Preview::SessionPane, - config.picker_colors, - config.shortcuts, - tmux.clone(), - )? { + if let Some(target_window) = get_single_selection(&windows, Preview::SessionPane, config, tmux)? + { if let Some((windex, _)) = target_window.split_once(' ') { tmux.select_window(windex); } @@ -616,7 +606,7 @@ fn clone_repo_command(args: &CloneRepoCommand, config: Config, tmux: &Tmux) -> R let mut session_name = repo_name.to_string(); - if session_exists(&session_name, tmux) { + if tmux.session_exists(&session_name) { session_name = format!( "{}/{}", path.parent() @@ -629,8 +619,8 @@ fn clone_repo_command(args: &CloneRepoCommand, config: Config, tmux: &Tmux) -> R } tmux.new_session(Some(&session_name), Some(&path.display().to_string())); - set_up_tmux_env(&repo, &session_name, tmux)?; - switch_to_session(&session_name, tmux); + tmux.set_up_tmux_env(&repo, &session_name)?; + tmux.switch_to_session(&session_name); Ok(()) } diff --git a/src/configs.rs b/src/configs.rs index 14aee77..f553e18 100644 --- a/src/configs.rs +++ b/src/configs.rs @@ -5,7 +5,7 @@ use std::{env, fmt::Display, fs::canonicalize, io::Write, path::PathBuf}; use ratatui::style::{Color, Style}; -use crate::{keymap::Keymap, Suggestion}; +use crate::{error::Suggestion, keymap::Keymap}; type Result = error_stack::Result; diff --git a/src/error.rs b/src/error.rs index d4f71ce..9bb0af1 100644 --- a/src/error.rs +++ b/src/error.rs @@ -24,3 +24,12 @@ impl Display for TmsError { } impl Error for TmsError {} + +#[derive(Debug)] +pub struct Suggestion(pub &'static str); +impl Display for Suggestion { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use crossterm::style::Stylize; + f.write_str(&format!("Suggestion: {}", self.0).green().bold().to_string()) + } +} diff --git a/src/lib.rs b/src/lib.rs index dd75a04..23b1bb0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,85 +8,15 @@ pub mod repos; pub mod session; pub mod tmux; -use error_stack::ResultExt; -use git2::Repository; -use std::{fmt::Display, process}; +use configs::Config; +use std::process; use crate::{ - configs::PickerColorConfig, - dirty_paths::DirtyUtf8Path, error::{Result, TmsError}, - keymap::Keymap, picker::{Picker, Preview}, tmux::Tmux, }; -pub fn switch_to_session(repo_short_name: &str, tmux: &Tmux) { - if !is_in_tmux_session() { - tmux.attach_session(Some(repo_short_name), None); - } else { - let result = tmux.switch_client(repo_short_name); - if !result.status.success() { - tmux.attach_session(Some(repo_short_name), None); - } - } -} - -pub fn session_exists(repo_short_name: &str, tmux: &Tmux) -> bool { - // Get the tmux sessions - let sessions = tmux.list_sessions("'#S'"); - - // If the session already exists switch to it, else create the new session and then switch - sessions.lines().any(|line| { - // tmux will return the output with extra ' and \n characters - line.to_owned().retain(|char| char != '\'' && char != '\n'); - line == repo_short_name - }) -} - -pub fn set_up_tmux_env(repo: &Repository, repo_name: &str, tmux: &Tmux) -> Result<()> { - if repo.is_bare() { - if repo - .worktrees() - .change_context(TmsError::GitError)? - .is_empty() - { - // Add the default branch as a tree (usually either main or master) - let head = repo.head().change_context(TmsError::GitError)?; - let head_short = head - .shorthand() - .ok_or(TmsError::NonUtf8Path) - .attach_printable("The selected repository has an unusable path")?; - let path_to_default_tree = format!("{}{}", repo.path().to_string()?, head_short); - let path = std::path::Path::new(&path_to_default_tree); - repo.worktree( - head_short, - path, - Some(git2::WorktreeAddOptions::new().reference(Some(&head))), - ) - .change_context(TmsError::GitError)?; - } - for tree in repo.worktrees().change_context(TmsError::GitError)?.iter() { - let tree = tree.ok_or(TmsError::NonUtf8Path).attach_printable(format!( - "The path to the found sub-tree {tree:?} has a non-utf8 path", - ))?; - let window_name = tree.to_string(); - let path_to_tree = repo - .find_worktree(tree) - .change_context(TmsError::GitError)? - .path() - .to_string()?; - - tmux.new_window(Some(&window_name), Some(&path_to_tree), Some(repo_name)); - } - // Kill that first extra window - tmux.kill_window(&format!("{repo_name}:^")); - } else { - // Extra stuff?? I removed launching python environments here but that could be exposed in the configuration - } - Ok(()) -} - pub fn execute_command(command: &str, args: Vec) -> process::Output { process::Command::new(command) .args(args) @@ -95,26 +25,14 @@ pub fn execute_command(command: &str, args: Vec) -> process::Output { .unwrap_or_else(|_| panic!("Failed to execute command `{command}`")) } -pub fn is_in_tmux_session() -> bool { - std::env::var("TERM_PROGRAM").is_ok_and(|program| program == "tmux") -} - pub fn get_single_selection( list: &[String], preview: Preview, - colors: Option, - keymap: Option, - tmux: Tmux, + config: &Config, + tmux: &Tmux, ) -> Result> { - let mut picker = Picker::new(list, preview, keymap, tmux).set_colors(colors); + let mut picker = Picker::new(list, preview, config.shortcuts.as_ref(), tmux) + .set_colors(config.picker_colors.as_ref()); picker.run() } -#[derive(Debug)] -pub struct Suggestion(&'static str); -impl Display for Suggestion { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use crossterm::style::Stylize; - f.write_str(&format!("Suggestion: {}", self.0).green().bold().to_string()) - } -} diff --git a/src/main.rs b/src/main.rs index e47b359..3bbacd8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,12 +3,11 @@ use error_stack::Report; use tms::{ cli::{Cli, SubCommandGiven}, - error::Result, + error::{Result, Suggestion}, get_single_selection, picker::Preview, session::{create_sessions, SessionContainer}, tmux::Tmux, - Suggestion, }; fn main() -> Result<()> { @@ -32,17 +31,12 @@ fn main() -> Result<()> { let sessions = create_sessions(&config)?; let session_strings = sessions.list(); - let selected_str = if let Some(str) = get_single_selection( - &session_strings, - Preview::None, - config.picker_colors, - config.shortcuts, - tmux.clone(), - )? { - str - } else { - return Ok(()); - }; + let selected_str = + if let Some(str) = get_single_selection(&session_strings, Preview::None, &config, &tmux)? { + str + } else { + return Ok(()); + }; if let Some(session) = sessions.find_session(&selected_str) { session.switch_to(&tmux)?; diff --git a/src/picker.rs b/src/picker.rs index 33fff93..7db4c07 100644 --- a/src/picker.rs +++ b/src/picker.rs @@ -38,21 +38,21 @@ pub enum Preview { None, } -pub struct Picker { +pub struct Picker<'a> { matcher: Nucleo, preview: Preview, - colors: Option, + colors: Option<&'a PickerColorConfig>, selection: ListState, filter: String, cursor_pos: u16, keymap: Keymap, - tmux: Tmux, + tmux: &'a Tmux, } -impl Picker { - pub fn new(list: &[String], preview: Preview, keymap: Option, tmux: Tmux) -> Self { +impl<'a> Picker<'a> { + pub fn new(list: &[String], preview: Preview, keymap: Option<&Keymap>, tmux: &'a Tmux) -> Self { let matcher = Nucleo::new(nucleo::Config::DEFAULT, Arc::new(request_redraw), None, 1); let injector = matcher.injector(); @@ -81,7 +81,7 @@ impl Picker { } } - pub fn set_colors(mut self, colors: Option) -> Self { + pub fn set_colors(mut self, colors: Option<&'a PickerColorConfig>) -> Self { self.colors = colors; self diff --git a/src/session.rs b/src/session.rs index 34ef554..63ac8e0 100644 --- a/src/session.rs +++ b/src/session.rs @@ -11,7 +11,6 @@ use crate::{ dirty_paths::DirtyUtf8Path, error::TmsError, repos::{find_repos, find_submodules}, - session_exists, set_up_tmux_env, switch_to_session, tmux::Tmux, Result, }; @@ -209,12 +208,12 @@ fn switch_to_repo_session(selected_str: &str, found_repo: &Repository, tmux: &Tm }; let repo_short_name = selected_str.replace('.', "_"); - if !session_exists(&repo_short_name, tmux) { + if !tmux.session_exists(&repo_short_name) { tmux.new_session(Some(&repo_short_name), Some(&path)); - set_up_tmux_env(found_repo, &repo_short_name, tmux)?; + tmux.set_up_tmux_env(found_repo, &repo_short_name)?; } - switch_to_session(&repo_short_name, tmux); + tmux.switch_to_session(&repo_short_name); Ok(()) } @@ -222,11 +221,11 @@ fn switch_to_repo_session(selected_str: &str, found_repo: &Repository, tmux: &Tm fn switch_to_bookmark_session(selected_str: &str, tmux: &Tmux, path: &Path) -> Result<()> { let session_name = selected_str.replace('.', "_"); - if !session_exists(&session_name, tmux) { + if !tmux.session_exists(&session_name) { tmux.new_session(Some(&session_name), path.to_str()); } - switch_to_session(&session_name, tmux); + tmux.switch_to_session(&session_name); Ok(()) } diff --git a/src/tmux.rs b/src/tmux.rs index 26cd4e2..44a667f 100644 --- a/src/tmux.rs +++ b/src/tmux.rs @@ -1,5 +1,13 @@ use std::{env, process}; +use error_stack::ResultExt; +use git2::Repository; + +use crate::{ + dirty_paths::DirtyUtf8Path, + error::{Result, TmsError}, +}; + #[derive(Clone)] pub struct Tmux { socket_name: String, @@ -85,6 +93,29 @@ impl Tmux { self.execute_tmux_command(&args) } + pub fn switch_to_session(&self, repo_short_name: &str) { + if !is_in_tmux_session() { + self.attach_session(Some(repo_short_name), None); + } else { + let result = self.switch_client(repo_short_name); + if !result.status.success() { + self.attach_session(Some(repo_short_name), None); + } + } + } + + pub fn session_exists(&self, repo_short_name: &str) -> bool { + // Get the tmux sessions + let sessions = self.list_sessions("'#S'"); + + // If the session already exists switch to it, else create the new session and then switch + sessions.lines().any(|line| { + // tmux will return the output with extra ' and \n characters + line.to_owned().retain(|char| char != '\'' && char != '\n'); + line == repo_short_name + }) + } + // windows pub fn new_window( @@ -159,4 +190,50 @@ impl Tmux { pub fn capture_pane(&self, target_pane: &str) -> process::Output { self.execute_tmux_command(&["capture-pane", "-ep", "-t", target_pane]) } + + pub fn set_up_tmux_env(&self, repo: &Repository, repo_name: &str) -> Result<()> { + if repo.is_bare() { + if repo + .worktrees() + .change_context(TmsError::GitError)? + .is_empty() + { + // Add the default branch as a tree (usually either main or master) + let head = repo.head().change_context(TmsError::GitError)?; + let head_short = head + .shorthand() + .ok_or(TmsError::NonUtf8Path) + .attach_printable("The selected repository has an unusable path")?; + let path = repo.path().to_path_buf().join(head_short); + repo.worktree( + head_short, + &path, + Some(git2::WorktreeAddOptions::new().reference(Some(&head))), + ) + .change_context(TmsError::GitError)?; + } + for tree in repo.worktrees().change_context(TmsError::GitError)?.iter() { + let tree = tree.ok_or(TmsError::NonUtf8Path).attach_printable(format!( + "The path to the found sub-tree {tree:?} has a non-utf8 path", + ))?; + let window_name = tree.to_string(); + let path_to_tree = repo + .find_worktree(tree) + .change_context(TmsError::GitError)? + .path() + .to_string()?; + + self.new_window(Some(&window_name), Some(&path_to_tree), Some(repo_name)); + } + // Kill that first extra window + self.kill_window(&format!("{repo_name}:^")); + } else { + // Extra stuff?? I removed launching python environments here but that could be exposed in the configuration + } + Ok(()) + } +} + +fn is_in_tmux_session() -> bool { + std::env::var("TERM_PROGRAM").is_ok_and(|program| program == "tmux") }