use std::{collections::HashSet, env};
use clap::{CommandFactory, Parser};
use clap_complete::CompleteEnv;
use error_stack::Report;
use tms::{
cli::{Cli, SubCommandGiven},
configs::SessionSortOrderConfig,
error::{Result, Suggestion},
session::{create_sessions, SessionContainer},
tmux::Tmux,
};
fn main() -> Result<()> {
Report::install_debug_hook::<Suggestion>(|value, context| {
context.push_body(format!("{value}"));
});
#[cfg(any(not(debug_assertions), test))]
Report::install_debug_hook::<std::panic::Location>(|_value, _context| {});
let bin_name = std::env::current_exe()
.ok()
.and_then(|exe| exe.file_name().map(|exe| exe.to_string_lossy().to_string()))
.unwrap_or("tms".into());
match CompleteEnv::with_factory(Cli::command)
.bin(bin_name)
.try_complete(env::args_os(), None)
{
Ok(true) => return Ok(()),
Err(e) => {
panic!("failed to generate completions: {e}");
}
Ok(false) => {}
};
let cli_args = Cli::parse();
let tmux = Tmux::default();
let config = match cli_args.handle_sub_commands(&tmux)? {
SubCommandGiven::Yes => return Ok(()),
SubCommandGiven::No(config) => config, };
let sessions = create_sessions(&config)?;
let (session_strings, active_sessions) = get_session_list(&sessions, &config, &tmux);
let mut picker = tms::picker::Picker::new(
&session_strings,
None,
config.shortcuts.as_ref(),
config.input_position.unwrap_or_default(),
&tmux,
)
.set_colors(config.picker_colors.as_ref());
if let Some(active) = active_sessions {
picker = picker.set_active_sessions(active);
}
let selected_str = if let Some(str) = picker.run()? {
str
} else {
return Ok(());
};
if let Some(session) = sessions.find_session(&selected_str) {
session.switch_to(&tmux, &config)?;
}
Ok(())
}
fn get_session_list(
sessions: &impl SessionContainer,
config: &tms::configs::Config,
tmux: &Tmux,
) -> (Vec<String>, Option<HashSet<String>>) {
let all_sessions = sessions.list();
if matches!(
config.session_sort_order,
Some(SessionSortOrderConfig::LastAttached)
) {
let active_sessions_raw =
tmux.list_sessions("'#{?session_attached,,#{session_name}#,#{session_last_attached}}'");
let active_sessions: Vec<(&str, i64)> = active_sessions_raw
.trim()
.split('\n')
.filter_map(|line| {
let line = line.trim_matches('\'');
let (name, timestamp) = line.split_once(',')?;
let timestamp = timestamp.parse::<i64>().ok()?;
Some((name, timestamp))
})
.collect();
let active_names: HashSet<&str> = active_sessions.iter().map(|(name, _)| *name).collect();
let active_names_owned: HashSet<String> =
active_names.iter().map(|s| s.to_string()).collect();
let (mut active_list, mut inactive_list): (Vec<String>, Vec<String>) =
all_sessions.into_iter().partition(|session_name| {
let normalized = session_name.replace(['.', '-'], "_");
active_names.contains(session_name.as_str())
|| active_names.contains(&normalized.as_str())
});
active_list.sort_by_cached_key(|name| {
let normalized = name.replace(['.', '-'], "_");
active_sessions
.iter()
.find(|(active_name, _)| *active_name == name || *active_name == normalized)
.map(|(_, timestamp)| -timestamp) .unwrap_or(0)
});
inactive_list.sort();
active_list.extend(inactive_list);
(active_list, Some(active_names_owned))
} else {
(all_sessions, None)
}
}