From 16d34fc599ffe2a7e2e360017502d049a21c9dcb Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Sat, 14 Dec 2024 09:27:05 -0800 Subject: [PATCH] fix: always search ~/.terminfo for terminfo files Per the manual: 1. Search TERMINFO. 2. Search ~/.terminfo. 3. Search TERMINFO_DIRS. 4. Search /usr/share/terminfo and friends. Technically we're allowed to omit the search in ~/.terminfo entirely, but this would introduce surprising results and we should just do what ncurses does. fixes #121 --- src/terminfo/searcher.rs | 68 ++++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/src/terminfo/searcher.rs b/src/terminfo/searcher.rs index ab8909e..6a98916 100644 --- a/src/terminfo/searcher.rs +++ b/src/terminfo/searcher.rs @@ -16,61 +16,67 @@ use std::env; use std::fs; use std::path::PathBuf; +// The default terminfo location should be /usr/lib/terminfo but that's not guaranteed, so we check +// a few more locations. See https://tldp.org/HOWTO/Text-Terminal-HOWTO-16.html#ss16.2 +const DEFAULT_LOCATIONS: &[&str] = &[ + "/etc/terminfo", + "/usr/share/terminfo", + "/usr/lib/terminfo", + "/lib/terminfo", + #[cfg(target_os = "haiku")] + "/boot/system/data/terminfo", +]; + /// Return path to database entry for `term` pub fn get_dbpath_for_term(term: &str) -> Option { let mut dirs_to_search = Vec::new(); + let mut default_locations = DEFAULT_LOCATIONS.iter().map(PathBuf::from); let first_char = match term.chars().next() { Some(c) => c, None => return None, }; - // Find search directory - // The terminfo manual says: - // - // > If the environment variable TERMINFO is set, it is interpreted - // > as the pathname of a directory containing the compiled description - // > you are working on. Only that directory is searched. - // - // However, the ncurses manual says: + // From the manual. // - // > If the environment variable TERMINFO is defined, any program using - // > curses checks for a local terminal definition before checking in - // > the standard place. - // - // Given that ncurses is the defacto standard, we follow the ncurses manual. + // > The environment variable TERMINFO is checked first, for a terminal + // > database containing the terminal description. if let Some(dir) = env::var_os("TERMINFO") { dirs_to_search.push(PathBuf::from(dir)); } + // > Next, ncurses looks in $HOME/.terminfo for a compiled description. + if let Some(mut homedir) = home::home_dir() { + homedir.push(".terminfo"); + dirs_to_search.push(homedir) + } + + // > Next, if the environment variable TERMINFO_DIRS is set, ncurses interprets + // > the contents of that variable as a list of colon-separated pathnames of + // > terminal databases to be searched. + // > + // > An empty pathname (i.e., if the variable begins or ends with a + // > colon, or contains adjacent colons) is interpreted as the system location + // > /usr/share/terminfo. if let Ok(dirs) = env::var("TERMINFO_DIRS") { for i in dirs.split(':') { if i.is_empty() { - dirs_to_search.push(PathBuf::from("/usr/share/terminfo")); + dirs_to_search.extend(&mut default_locations); } else { dirs_to_search.push(PathBuf::from(i)); } } - } else { - // Found nothing in TERMINFO_DIRS, use the default paths: - // According to /etc/terminfo/README, after looking at - // ~/.terminfo, ncurses will search /etc/terminfo, then - // /lib/terminfo, and eventually /usr/share/terminfo. - // On Haiku the database can be found at /boot/system/data/terminfo - if let Some(mut homedir) = home::home_dir() { - homedir.push(".terminfo"); - dirs_to_search.push(homedir) - } - - dirs_to_search.push(PathBuf::from("/etc/terminfo")); - dirs_to_search.push(PathBuf::from("/lib/terminfo")); - dirs_to_search.push(PathBuf::from("/usr/share/terminfo")); - dirs_to_search.push(PathBuf::from("/boot/system/data/terminfo")); } + // > Finally, ncurses searches these compiled-in locations... + // + // NOTE: We only append these to `dirs_to_search` once. If we've already added these + // directories as specified in `TERMINFO_DIRS`, this operation will be a no-op. + dirs_to_search.extend(&mut default_locations); + // Look for the terminal in all of the search directories for mut p in dirs_to_search { if fs::metadata(&p).is_ok() { - p.push(&first_char.to_string()); + p.push(first_char.to_string()); p.push(term); if fs::metadata(&p).is_ok() { return Some(p); @@ -80,7 +86,7 @@ pub fn get_dbpath_for_term(term: &str) -> Option { // on some installations the dir is named after the hex of the char // (e.g. OS X) - p.push(&format!("{:x}", first_char as usize)); + p.push(format!("{:x}", first_char as usize)); p.push(term); if fs::metadata(&p).is_ok() { return Some(p);