zellij/src/sessions.rs
Kyle Sutherland-Cash e0bd311e84
fix(sessions): use custom lists of adjectives and nouns for generating session names (#2122)
* Create custom lists of adjectives and nouns for generating session names

* move word lists to const slices

* add logic to retry name generation

* refactor

 - reuse the name generator
 - iterator instead of for loop

---------

Co-authored-by: Thomas Linford <linford.t@gmail.com>
2023-07-18 19:12:51 +02:00

369 lines
9.5 KiB
Rust

use std::os::unix::fs::FileTypeExt;
use std::time::SystemTime;
use std::{fs, io, process};
use suggest::Suggest;
use zellij_utils::{
anyhow,
consts::ZELLIJ_SOCK_DIR,
envs,
interprocess::local_socket::LocalSocketStream,
ipc::{ClientToServerMsg, IpcReceiverWithContext, IpcSenderWithContext, ServerToClientMsg},
};
pub(crate) fn get_sessions() -> Result<Vec<String>, io::ErrorKind> {
match fs::read_dir(&*ZELLIJ_SOCK_DIR) {
Ok(files) => {
let mut sessions = Vec::new();
files.for_each(|file| {
let file = file.unwrap();
let file_name = file.file_name().into_string().unwrap();
if file.file_type().unwrap().is_socket() && assert_socket(&file_name) {
sessions.push(file_name);
}
});
Ok(sessions)
},
Err(err) if io::ErrorKind::NotFound != err.kind() => Err(err.kind()),
Err(_) => Ok(Vec::with_capacity(0)),
}
}
pub(crate) fn get_sessions_sorted_by_mtime() -> anyhow::Result<Vec<String>> {
match fs::read_dir(&*ZELLIJ_SOCK_DIR) {
Ok(files) => {
let mut sessions_with_mtime: Vec<(String, SystemTime)> = Vec::new();
for file in files {
let file = file?;
let file_name = file.file_name().into_string().unwrap();
let file_modified_at = file.metadata()?.modified()?;
if file.file_type()?.is_socket() && assert_socket(&file_name) {
sessions_with_mtime.push((file_name, file_modified_at));
}
}
sessions_with_mtime.sort_by_key(|x| x.1); // the oldest one will be the first
let sessions = sessions_with_mtime.iter().map(|x| x.0.clone()).collect();
Ok(sessions)
},
Err(err) if io::ErrorKind::NotFound != err.kind() => Err(err.into()),
Err(_) => Ok(Vec::with_capacity(0)),
}
}
fn assert_socket(name: &str) -> bool {
let path = &*ZELLIJ_SOCK_DIR.join(name);
match LocalSocketStream::connect(path) {
Ok(stream) => {
let mut sender = IpcSenderWithContext::new(stream);
let _ = sender.send(ClientToServerMsg::ConnStatus);
let mut receiver: IpcReceiverWithContext<ServerToClientMsg> = sender.get_receiver();
match receiver.recv() {
Some((ServerToClientMsg::Connected, _)) => true,
None | Some((_, _)) => false,
}
},
Err(e) if e.kind() == io::ErrorKind::ConnectionRefused => {
drop(fs::remove_file(path));
false
},
Err(_) => false,
}
}
pub(crate) fn print_sessions(sessions: Vec<String>) {
let curr_session = envs::get_session_name().unwrap_or_else(|_| "".into());
sessions.iter().for_each(|session| {
let suffix = if curr_session == *session {
" (current)"
} else {
""
};
println!("{}{}", session, suffix);
})
}
pub(crate) fn print_sessions_with_index(sessions: Vec<String>) {
let curr_session = envs::get_session_name().unwrap_or_else(|_| "".into());
for (i, session) in sessions.iter().enumerate() {
let suffix = if curr_session == *session {
" (current)"
} else {
""
};
println!("{}: {}{}", i, session, suffix);
}
}
pub(crate) enum ActiveSession {
None,
One(String),
Many,
}
pub(crate) fn get_active_session() -> ActiveSession {
match get_sessions() {
Ok(sessions) if sessions.is_empty() => ActiveSession::None,
Ok(mut sessions) if sessions.len() == 1 => ActiveSession::One(sessions.pop().unwrap()),
Ok(_) => ActiveSession::Many,
Err(e) => {
eprintln!("Error occurred: {:?}", e);
process::exit(1);
},
}
}
pub(crate) fn kill_session(name: &str) {
let path = &*ZELLIJ_SOCK_DIR.join(name);
match LocalSocketStream::connect(path) {
Ok(stream) => {
let _ = IpcSenderWithContext::new(stream).send(ClientToServerMsg::KillSession);
},
Err(e) => {
eprintln!("Error occurred: {:?}", e);
process::exit(1);
},
};
}
pub(crate) fn list_sessions() {
let exit_code = match get_sessions() {
Ok(sessions) if !sessions.is_empty() => {
print_sessions(sessions);
0
},
Ok(_) => {
eprintln!("No active zellij sessions found.");
1
},
Err(e) => {
eprintln!("Error occurred: {:?}", e);
1
},
};
process::exit(exit_code);
}
#[derive(Debug, Clone)]
pub enum SessionNameMatch {
AmbiguousPrefix(Vec<String>),
UniquePrefix(String),
Exact(String),
None,
}
pub(crate) fn match_session_name(prefix: &str) -> Result<SessionNameMatch, io::ErrorKind> {
let sessions = get_sessions()?;
let filtered_sessions: Vec<_> = sessions.iter().filter(|s| s.starts_with(prefix)).collect();
if filtered_sessions.iter().any(|s| *s == prefix) {
return Ok(SessionNameMatch::Exact(prefix.to_string()));
}
Ok({
match &filtered_sessions[..] {
[] => SessionNameMatch::None,
[s] => SessionNameMatch::UniquePrefix(s.to_string()),
_ => {
SessionNameMatch::AmbiguousPrefix(filtered_sessions.into_iter().cloned().collect())
},
}
})
}
pub(crate) fn session_exists(name: &str) -> Result<bool, io::ErrorKind> {
match match_session_name(name) {
Ok(SessionNameMatch::Exact(_)) => Ok(true),
Ok(_) => Ok(false),
Err(e) => Err(e),
}
}
pub(crate) fn assert_session(name: &str) {
match session_exists(name) {
Ok(result) => {
if result {
return;
} else {
println!("No session named {:?} found.", name);
if let Some(sugg) = get_sessions().unwrap().suggest(name) {
println!(" help: Did you mean `{}`?", sugg);
}
}
},
Err(e) => {
eprintln!("Error occurred: {:?}", e);
},
};
process::exit(1);
}
pub(crate) fn assert_session_ne(name: &str) {
if name.trim().is_empty() {
eprintln!("Session name cannot be empty. Please provide a specific session name.");
process::exit(1);
}
if name == "." || name == ".." {
eprintln!("Invalid session name: \"{}\".", name);
process::exit(1);
}
if name.contains('/') {
eprintln!("Session name cannot contain '/'.");
process::exit(1);
}
match session_exists(name) {
Ok(result) if !result => return,
Ok(_) => println!("Session with name {:?} already exists. Use attach command to connect to it or specify a different name.", name),
Err(e) => eprintln!("Error occurred: {:?}", e),
};
process::exit(1);
}
/// Create a new random name generator
///
/// Used to provide a memorable handle for a session when users don't specify a session name when the session is
/// created.
///
/// Uses the list of adjectives and nouns defined below, with the intention of avoiding unfortunate
/// and offensive combinations. Care should be taken when adding or removing to either list due to the birthday paradox/
/// hash collisions, e.g. with 4096 unique names, the likelihood of a collision in 10 session names is 1%.
pub(crate) fn get_name_generator() -> impl Iterator<Item = String> {
names::Generator::new(&ADJECTIVES, &NOUNS, names::Name::Plain)
}
const ADJECTIVES: &[&'static str] = &[
"adamant",
"adept",
"adventurous",
"arcadian",
"auspicious",
"awesome",
"blossoming",
"brave",
"charming",
"chatty",
"circular",
"considerate",
"cubic",
"curious",
"delighted",
"didactic",
"diligent",
"effulgent",
"erudite",
"excellent",
"exquisite",
"fabulous",
"fascinating",
"friendly",
"glowing",
"gracious",
"gregarious",
"hopeful",
"implacable",
"inventive",
"joyous",
"judicious",
"jumping",
"kind",
"likable",
"loyal",
"lucky",
"marvellous",
"mellifluous",
"nautical",
"oblong",
"outstanding",
"polished",
"polite",
"profound",
"quadratic",
"quiet",
"rectangular",
"remarkable",
"rusty",
"sensible",
"sincere",
"sparkling",
"splendid",
"stellar",
"tenacious",
"tremendous",
"triangular",
"undulating",
"unflappable",
"unique",
"verdant",
"vitreous",
"wise",
"zippy",
];
const NOUNS: &[&'static str] = &[
"aardvark",
"accordion",
"apple",
"apricot",
"bee",
"brachiosaur",
"cactus",
"capsicum",
"clarinet",
"cowbell",
"crab",
"cuckoo",
"cymbal",
"diplodocus",
"donkey",
"drum",
"duck",
"echidna",
"elephant",
"foxglove",
"galaxy",
"glockenspiel",
"goose",
"hill",
"horse",
"iguanadon",
"jellyfish",
"kangaroo",
"lake",
"lemon",
"lemur",
"magpie",
"megalodon",
"mountain",
"mouse",
"muskrat",
"newt",
"oboe",
"ocelot",
"orange",
"panda",
"peach",
"pepper",
"petunia",
"pheasant",
"piano",
"pigeon",
"platypus",
"quasar",
"rhinoceros",
"river",
"rustacean",
"salamander",
"sitar",
"stegosaurus",
"tambourine",
"tiger",
"tomato",
"triceratops",
"ukulele",
"viola",
"weasel",
"xylophone",
"yak",
"zebra",
];