mirror of
https://github.com/fish-shell/fish-shell.git
synced 2024-11-22 06:45:11 +08:00
Switch to the terminfo crate
This allows us to get the terminfo information without linking against curses. That means we can get by without a bunch of awkward C-API trickery. There is no global "cur_term" kept by a library for us that we need to invalidate. Note that it still requires a "unhashed terminfo database", and I don't know how well it handles termcap. I am not actually sure if there are systems that *can't* have terminfo, everything I looked at has the ncurses terminfo available to install at least.
This commit is contained in:
parent
2c2ab0c1fa
commit
fc794bab4c
|
@ -25,7 +25,6 @@ endif()
|
|||
include(GNUInstallDirs)
|
||||
add_definitions(-D_UNICODE=1)
|
||||
|
||||
include(cmake/ConfigureChecks.cmake)
|
||||
include(cmake/gettext.cmake)
|
||||
|
||||
# Set up PCRE2
|
||||
|
|
84
Cargo.lock
generated
84
Cargo.lock
generated
|
@ -117,9 +117,16 @@ dependencies = [
|
|||
"rand_pcg",
|
||||
"rsconf",
|
||||
"serial_test",
|
||||
"terminfo",
|
||||
"widestring",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.12"
|
||||
|
@ -219,6 +226,18 @@ dependencies = [
|
|||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.25.1"
|
||||
|
@ -231,6 +250,16 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.17"
|
||||
|
@ -291,6 +320,44 @@ dependencies = [
|
|||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_codegen"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a"
|
||||
dependencies = [
|
||||
"phf_generator",
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_generator"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.29"
|
||||
|
@ -414,6 +481,12 @@ dependencies = [
|
|||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.13.1"
|
||||
|
@ -442,6 +515,17 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "terminfo"
|
||||
version = "0.8.0"
|
||||
source = "git+https://github.com/meh/rust-terminfo?rev=870327dd79beaecf50db09a15931d5ee1de2a24d#870327dd79beaecf50db09a15931d5ee1de2a24d"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"nom",
|
||||
"phf",
|
||||
"phf_codegen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.7"
|
||||
|
|
|
@ -35,6 +35,7 @@ once_cell = "1.17.0"
|
|||
rand = { version = "0.8.5", features = ["small_rng"] }
|
||||
widestring = "1.0.2"
|
||||
git-version = "0.3"
|
||||
terminfo = { git = "https://github.com/meh/rust-terminfo", rev = "870327dd79beaecf50db09a15931d5ee1de2a24d" }
|
||||
|
||||
[dev-dependencies]
|
||||
rand_pcg = "0.3.1"
|
||||
|
|
44
build.rs
44
build.rs
|
@ -3,7 +3,7 @@
|
|||
use rsconf::{LinkType, Target};
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
setup_paths();
|
||||
|
@ -38,48 +38,6 @@ fn main() {
|
|||
// Keep verbose mode on until we've ironed out rust build script stuff
|
||||
target.set_verbose(true);
|
||||
detect_cfgs(&mut target);
|
||||
|
||||
// Handle case where CMake has found curses for us and where we have to find it ourselves.
|
||||
rsconf::rebuild_if_env_changed("CURSES_LIBRARY_LIST");
|
||||
let curses_lib_list = env::var("CURSES_LIBRARY_LIST");
|
||||
let curses_libraries = if let Ok(lib_path_list) = curses_lib_list.as_ref() {
|
||||
let lib_paths = lib_path_list.split(',').filter(|s| !s.is_empty());
|
||||
let curses_libnames: Vec<_> = lib_paths
|
||||
.map(|libpath| {
|
||||
let stem = Path::new(libpath).file_stem().unwrap().to_str().unwrap();
|
||||
// Ubuntu-32bit-fetched-pcre2's ncurses doesn't have the "lib" prefix.
|
||||
stem.strip_prefix("lib").unwrap_or(stem)
|
||||
})
|
||||
.collect();
|
||||
// We don't need to test the libs because presumably CMake already did
|
||||
rsconf::link_libraries(&curses_libnames, LinkType::Default);
|
||||
curses_libnames
|
||||
} else {
|
||||
let mut curses_libraries = vec![];
|
||||
let libs = ["ncurses", "curses"];
|
||||
for lib in libs {
|
||||
if target.has_library(lib) && target.has_symbol("setupterm", lib) {
|
||||
rsconf::link_library(lib, LinkType::Default);
|
||||
curses_libraries.push(lib);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if curses_libraries.is_empty() {
|
||||
panic!("Could not locate a compatible curses library (tried {libs:?})");
|
||||
}
|
||||
curses_libraries
|
||||
};
|
||||
|
||||
for lib in curses_libraries {
|
||||
if target.has_symbol("_nc_cur_term", lib) {
|
||||
rsconf::enable_cfg("have_nc_cur_term");
|
||||
if target.has_symbol("cur_term", lib) {
|
||||
rsconf::warn!("curses provides both cur_term and _nc_cur_term");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check target system support for certain functionality dynamically when the build is invoked,
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
# The following defines affect the environment configuration tests are run in:
|
||||
# CMAKE_REQUIRED_DEFINITIONS, CMAKE_REQUIRED_FLAGS, CMAKE_REQUIRED_LIBRARIES,
|
||||
# and CMAKE_REQUIRED_INCLUDES
|
||||
list(APPEND CMAKE_REQUIRED_DEFINITIONS -D_GNU_SOURCE=1)
|
||||
|
||||
# Try using CMake's own logic to locate curses/ncurses
|
||||
find_package(Curses)
|
||||
if(NOT ${CURSES_FOUND})
|
||||
# CMake has trouble finding platform-specific system libraries
|
||||
# installed to multiarch paths (e.g. /usr/lib/x86_64-linux-gnu)
|
||||
# if not symlinked or passed in as a manual define.
|
||||
message("Falling back to pkg-config for (n)curses detection")
|
||||
include(FindPkgConfig)
|
||||
pkg_search_module(CURSES REQUIRED ncurses curses)
|
||||
set(CURSES_CURSES_LIBRARY ${CURSES_LIBRARIES})
|
||||
set(CURSES_LIBRARY ${CURSES_LIBRARIES})
|
||||
endif()
|
||||
|
||||
# Fix undefined reference to tparm on RHEL 6 and potentially others
|
||||
# If curses is found via CMake, it also links against tinfo if it exists. But if we use our
|
||||
# fallback pkg-config logic above, we need to do this manually.
|
||||
find_library(CURSES_TINFO tinfo)
|
||||
if (CURSES_TINFO)
|
||||
set(CURSES_LIBRARY ${CURSES_LIBRARY} ${CURSES_TINFO})
|
||||
else()
|
||||
# on NetBSD, libtinfo has a longer name (libterminfo)
|
||||
find_library(CURSES_TINFO terminfo)
|
||||
if (CURSES_TINFO)
|
||||
set(CURSES_LIBRARY ${CURSES_LIBRARY} ${CURSES_TINFO})
|
||||
endif()
|
||||
endif()
|
470
src/curses.rs
470
src/curses.rs
|
@ -8,7 +8,7 @@
|
|||
//! This is intentionally very bare bones and only implements the subset of curses functionality
|
||||
//! used by fish
|
||||
|
||||
use self::sys::*;
|
||||
use crate::common::ToCString;
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
@ -36,85 +36,6 @@ pub fn term() -> Option<Arc<Term>> {
|
|||
.map(Arc::clone)
|
||||
}
|
||||
|
||||
/// Convert a nul-terminated pointer, which must not be itself null, to a CString.
|
||||
fn ptr_to_cstr(ptr: *const libc::c_char) -> CString {
|
||||
assert!(!ptr.is_null());
|
||||
unsafe { CStr::from_ptr(ptr).to_owned() }
|
||||
}
|
||||
|
||||
/// Convert a nul-terminated pointer to a CString, or None if the pointer is null.
|
||||
fn try_ptr_to_cstr(ptr: *const libc::c_char) -> Option<CString> {
|
||||
if ptr.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(ptr_to_cstr(ptr))
|
||||
}
|
||||
}
|
||||
|
||||
/// Private module exposing system curses ffi.
|
||||
mod sys {
|
||||
pub const OK: i32 = 0;
|
||||
|
||||
/// tputs callback argument type and the callback type itself.
|
||||
/// N.B. The C++ had a check for TPUTS_USES_INT_ARG for the first parameter of tputs
|
||||
/// which was to support OpenIndiana, which used a char.
|
||||
pub type tputs_arg = libc::c_int;
|
||||
pub type putc_t = extern "C" fn(tputs_arg) -> libc::c_int;
|
||||
|
||||
extern "C" {
|
||||
#[cfg(not(have_nc_cur_term))]
|
||||
pub static mut cur_term: *const core::ffi::c_void;
|
||||
#[cfg(have_nc_cur_term)]
|
||||
pub fn _nc_cur_term() -> *const core::ffi::c_void;
|
||||
|
||||
/// setupterm(3) is a low-level call to begin doing any sort of `term.h`/`curses.h` work.
|
||||
/// It's called internally by ncurses's `initscr()` and `newterm()`, but the C++ code called
|
||||
/// it directly from [`initialize_curses_using_fallbacks()`].
|
||||
pub fn setupterm(
|
||||
term: *const libc::c_char,
|
||||
filedes: libc::c_int,
|
||||
errret: *mut libc::c_int,
|
||||
) -> libc::c_int;
|
||||
|
||||
/// Frees the `cur_term` TERMINAL pointer.
|
||||
pub fn del_curterm(term: *const core::ffi::c_void) -> libc::c_int;
|
||||
|
||||
/// Sets the `cur_term` TERMINAL pointer.
|
||||
pub fn set_curterm(term: *const core::ffi::c_void) -> *const core::ffi::c_void;
|
||||
|
||||
/// Checks for the presence of a termcap flag identified by the first two characters of
|
||||
/// `id`.
|
||||
pub fn tgetflag(id: *const libc::c_char) -> libc::c_int;
|
||||
|
||||
/// Checks for the presence and value of a number capability in the termcap/termconf
|
||||
/// database. A return value of `-1` indicates not found.
|
||||
pub fn tgetnum(id: *const libc::c_char) -> libc::c_int;
|
||||
|
||||
pub fn tgetstr(
|
||||
id: *const libc::c_char,
|
||||
area: *mut *mut libc::c_char,
|
||||
) -> *const libc::c_char;
|
||||
|
||||
pub fn tparm(str: *const libc::c_char, ...) -> *const libc::c_char;
|
||||
|
||||
pub fn tputs(str: *const libc::c_char, affcnt: libc::c_int, putc: putc_t) -> libc::c_int;
|
||||
}
|
||||
}
|
||||
|
||||
/// The ncurses `cur_term` TERMINAL pointer.
|
||||
fn get_curterm() -> *const core::ffi::c_void {
|
||||
#[cfg(have_nc_cur_term)]
|
||||
unsafe {
|
||||
sys::_nc_cur_term()
|
||||
}
|
||||
#[cfg(not(have_nc_cur_term))]
|
||||
unsafe {
|
||||
sys::cur_term
|
||||
}
|
||||
}
|
||||
|
||||
pub use sys::tputs_arg as TputsArg;
|
||||
|
||||
/// The safe wrapper around curses functionality, initialized by a successful call to [`setup()`]
|
||||
/// and obtained thereafter by calls to [`term()`].
|
||||
///
|
||||
|
@ -275,157 +196,157 @@ pub struct Term {
|
|||
impl Term {
|
||||
/// Initialize a new `Term` instance, prepopulating the values of all the curses string
|
||||
/// capabilities we care about in the process.
|
||||
fn new() -> Self {
|
||||
fn new(db: terminfo::Database) -> Self {
|
||||
Term {
|
||||
// String capabilities
|
||||
enter_bold_mode: get_str_cap("md"),
|
||||
enter_italics_mode: get_str_cap("ZH"),
|
||||
exit_italics_mode: get_str_cap("ZR"),
|
||||
enter_dim_mode: get_str_cap("mh"),
|
||||
enter_underline_mode: get_str_cap("us"),
|
||||
exit_underline_mode: get_str_cap("ue"),
|
||||
enter_reverse_mode: get_str_cap("mr"),
|
||||
enter_standout_mode: get_str_cap("so"),
|
||||
exit_standout_mode: get_str_cap("se"),
|
||||
enter_blink_mode: get_str_cap("mb"),
|
||||
enter_protected_mode: get_str_cap("mp"),
|
||||
enter_shadow_mode: get_str_cap("ZM"),
|
||||
exit_shadow_mode: get_str_cap("ZU"),
|
||||
enter_secure_mode: get_str_cap("mk"),
|
||||
enter_alt_charset_mode: get_str_cap("as"),
|
||||
exit_alt_charset_mode: get_str_cap("ae"),
|
||||
set_a_foreground: get_str_cap("AF"),
|
||||
set_foreground: get_str_cap("Sf"),
|
||||
set_a_background: get_str_cap("AB"),
|
||||
set_background: get_str_cap("Sb"),
|
||||
exit_attribute_mode: get_str_cap("me"),
|
||||
set_title: get_str_cap("ts"),
|
||||
clear_screen: get_str_cap("cl"),
|
||||
cursor_up: get_str_cap("up"),
|
||||
cursor_down: get_str_cap("do"),
|
||||
cursor_left: get_str_cap("le"),
|
||||
cursor_right: get_str_cap("nd"),
|
||||
parm_left_cursor: get_str_cap("LE"),
|
||||
parm_right_cursor: get_str_cap("RI"),
|
||||
clr_eol: get_str_cap("ce"),
|
||||
clr_eos: get_str_cap("cd"),
|
||||
enter_bold_mode: get_str_cap(&db, "md"),
|
||||
enter_italics_mode: get_str_cap(&db, "ZH"),
|
||||
exit_italics_mode: get_str_cap(&db, "ZR"),
|
||||
enter_dim_mode: get_str_cap(&db, "mh"),
|
||||
enter_underline_mode: get_str_cap(&db, "us"),
|
||||
exit_underline_mode: get_str_cap(&db, "ue"),
|
||||
enter_reverse_mode: get_str_cap(&db, "mr"),
|
||||
enter_standout_mode: get_str_cap(&db, "so"),
|
||||
exit_standout_mode: get_str_cap(&db, "se"),
|
||||
enter_blink_mode: get_str_cap(&db, "mb"),
|
||||
enter_protected_mode: get_str_cap(&db, "mp"),
|
||||
enter_shadow_mode: get_str_cap(&db, "ZM"),
|
||||
exit_shadow_mode: get_str_cap(&db, "ZU"),
|
||||
enter_secure_mode: get_str_cap(&db, "mk"),
|
||||
enter_alt_charset_mode: get_str_cap(&db, "as"),
|
||||
exit_alt_charset_mode: get_str_cap(&db, "ae"),
|
||||
set_a_foreground: get_str_cap(&db, "AF"),
|
||||
set_foreground: get_str_cap(&db, "Sf"),
|
||||
set_a_background: get_str_cap(&db, "AB"),
|
||||
set_background: get_str_cap(&db, "Sb"),
|
||||
exit_attribute_mode: get_str_cap(&db, "me"),
|
||||
set_title: get_str_cap(&db, "ts"),
|
||||
clear_screen: get_str_cap(&db, "cl"),
|
||||
cursor_up: get_str_cap(&db, "up"),
|
||||
cursor_down: get_str_cap(&db, "do"),
|
||||
cursor_left: get_str_cap(&db, "le"),
|
||||
cursor_right: get_str_cap(&db, "nd"),
|
||||
parm_left_cursor: get_str_cap(&db, "LE"),
|
||||
parm_right_cursor: get_str_cap(&db, "RI"),
|
||||
clr_eol: get_str_cap(&db, "ce"),
|
||||
clr_eos: get_str_cap(&db, "cd"),
|
||||
|
||||
// Number capabilities
|
||||
max_colors: get_num_cap("Co"),
|
||||
init_tabs: get_num_cap("it"),
|
||||
max_colors: get_num_cap(&db, "Co"),
|
||||
init_tabs: get_num_cap(&db, "it"),
|
||||
|
||||
// Flag/boolean capabilities
|
||||
eat_newline_glitch: get_flag_cap("xn"),
|
||||
auto_right_margin: get_flag_cap("am"),
|
||||
eat_newline_glitch: get_flag_cap(&db, "xn"),
|
||||
auto_right_margin: get_flag_cap(&db, "am"),
|
||||
|
||||
// Keys. See `man terminfo` for these strings.
|
||||
key_a1: get_str_cap("K1"),
|
||||
key_a3: get_str_cap("K3"),
|
||||
key_b2: get_str_cap("K2"),
|
||||
key_backspace: get_str_cap("kb"),
|
||||
key_beg: get_str_cap("@1"),
|
||||
key_btab: get_str_cap("kB"),
|
||||
key_c1: get_str_cap("K4"),
|
||||
key_c3: get_str_cap("K5"),
|
||||
key_cancel: get_str_cap("@2"),
|
||||
key_catab: get_str_cap("ka"),
|
||||
key_clear: get_str_cap("kC"),
|
||||
key_close: get_str_cap("@3"),
|
||||
key_command: get_str_cap("@4"),
|
||||
key_copy: get_str_cap("@5"),
|
||||
key_create: get_str_cap("@6"),
|
||||
key_ctab: get_str_cap("kt"),
|
||||
key_dc: get_str_cap("kD"),
|
||||
key_dl: get_str_cap("kL"),
|
||||
key_down: get_str_cap("kd"),
|
||||
key_eic: get_str_cap("kM"),
|
||||
key_end: get_str_cap("@7"),
|
||||
key_enter: get_str_cap("@8"),
|
||||
key_eol: get_str_cap("kE"),
|
||||
key_eos: get_str_cap("kS"),
|
||||
key_exit: get_str_cap("@9"),
|
||||
key_f0: get_str_cap("k0"),
|
||||
key_f1: get_str_cap("k1"),
|
||||
key_f10: get_str_cap("k;"),
|
||||
key_f11: get_str_cap("F1"),
|
||||
key_f12: get_str_cap("F2"),
|
||||
key_f13: get_str_cap("F3"),
|
||||
key_f14: get_str_cap("F4"),
|
||||
key_f15: get_str_cap("F5"),
|
||||
key_f16: get_str_cap("F6"),
|
||||
key_f17: get_str_cap("F7"),
|
||||
key_f18: get_str_cap("F8"),
|
||||
key_f19: get_str_cap("F9"),
|
||||
key_f2: get_str_cap("k2"),
|
||||
key_f20: get_str_cap("FA"),
|
||||
key_a1: get_str_cap(&db, "K1"),
|
||||
key_a3: get_str_cap(&db, "K3"),
|
||||
key_b2: get_str_cap(&db, "K2"),
|
||||
key_backspace: get_str_cap(&db, "kb"),
|
||||
key_beg: get_str_cap(&db, "@1"),
|
||||
key_btab: get_str_cap(&db, "kB"),
|
||||
key_c1: get_str_cap(&db, "K4"),
|
||||
key_c3: get_str_cap(&db, "K5"),
|
||||
key_cancel: get_str_cap(&db, "@2"),
|
||||
key_catab: get_str_cap(&db, "ka"),
|
||||
key_clear: get_str_cap(&db, "kC"),
|
||||
key_close: get_str_cap(&db, "@3"),
|
||||
key_command: get_str_cap(&db, "@4"),
|
||||
key_copy: get_str_cap(&db, "@5"),
|
||||
key_create: get_str_cap(&db, "@6"),
|
||||
key_ctab: get_str_cap(&db, "kt"),
|
||||
key_dc: get_str_cap(&db, "kD"),
|
||||
key_dl: get_str_cap(&db, "kL"),
|
||||
key_down: get_str_cap(&db, "kd"),
|
||||
key_eic: get_str_cap(&db, "kM"),
|
||||
key_end: get_str_cap(&db, "@7"),
|
||||
key_enter: get_str_cap(&db, "@8"),
|
||||
key_eol: get_str_cap(&db, "kE"),
|
||||
key_eos: get_str_cap(&db, "kS"),
|
||||
key_exit: get_str_cap(&db, "@9"),
|
||||
key_f0: get_str_cap(&db, "k0"),
|
||||
key_f1: get_str_cap(&db, "k1"),
|
||||
key_f10: get_str_cap(&db, "k;"),
|
||||
key_f11: get_str_cap(&db, "F1"),
|
||||
key_f12: get_str_cap(&db, "F2"),
|
||||
key_f13: get_str_cap(&db, "F3"),
|
||||
key_f14: get_str_cap(&db, "F4"),
|
||||
key_f15: get_str_cap(&db, "F5"),
|
||||
key_f16: get_str_cap(&db, "F6"),
|
||||
key_f17: get_str_cap(&db, "F7"),
|
||||
key_f18: get_str_cap(&db, "F8"),
|
||||
key_f19: get_str_cap(&db, "F9"),
|
||||
key_f2: get_str_cap(&db, "k2"),
|
||||
key_f20: get_str_cap(&db, "FA"),
|
||||
// Note key_f21 through key_f63 are available but no actual keyboard supports them.
|
||||
key_f3: get_str_cap("k3"),
|
||||
key_f4: get_str_cap("k4"),
|
||||
key_f5: get_str_cap("k5"),
|
||||
key_f6: get_str_cap("k6"),
|
||||
key_f7: get_str_cap("k7"),
|
||||
key_f8: get_str_cap("k8"),
|
||||
key_f9: get_str_cap("k9"),
|
||||
key_find: get_str_cap("@0"),
|
||||
key_help: get_str_cap("%1"),
|
||||
key_home: get_str_cap("kh"),
|
||||
key_ic: get_str_cap("kI"),
|
||||
key_il: get_str_cap("kA"),
|
||||
key_left: get_str_cap("kl"),
|
||||
key_ll: get_str_cap("kH"),
|
||||
key_mark: get_str_cap("%2"),
|
||||
key_message: get_str_cap("%3"),
|
||||
key_move: get_str_cap("%4"),
|
||||
key_next: get_str_cap("%5"),
|
||||
key_npage: get_str_cap("kN"),
|
||||
key_open: get_str_cap("%6"),
|
||||
key_options: get_str_cap("%7"),
|
||||
key_ppage: get_str_cap("kP"),
|
||||
key_previous: get_str_cap("%8"),
|
||||
key_print: get_str_cap("%9"),
|
||||
key_redo: get_str_cap("%0"),
|
||||
key_reference: get_str_cap("&1"),
|
||||
key_refresh: get_str_cap("&2"),
|
||||
key_replace: get_str_cap("&3"),
|
||||
key_restart: get_str_cap("&4"),
|
||||
key_resume: get_str_cap("&5"),
|
||||
key_right: get_str_cap("kr"),
|
||||
key_save: get_str_cap("&6"),
|
||||
key_sbeg: get_str_cap("&9"),
|
||||
key_scancel: get_str_cap("&0"),
|
||||
key_scommand: get_str_cap("*1"),
|
||||
key_scopy: get_str_cap("*2"),
|
||||
key_screate: get_str_cap("*3"),
|
||||
key_sdc: get_str_cap("*4"),
|
||||
key_sdl: get_str_cap("*5"),
|
||||
key_select: get_str_cap("*6"),
|
||||
key_send: get_str_cap("*7"),
|
||||
key_seol: get_str_cap("*8"),
|
||||
key_sexit: get_str_cap("*9"),
|
||||
key_sf: get_str_cap("kF"),
|
||||
key_sfind: get_str_cap("*0"),
|
||||
key_shelp: get_str_cap("#1"),
|
||||
key_shome: get_str_cap("#2"),
|
||||
key_sic: get_str_cap("#3"),
|
||||
key_sleft: get_str_cap("#4"),
|
||||
key_smessage: get_str_cap("%a"),
|
||||
key_smove: get_str_cap("%b"),
|
||||
key_snext: get_str_cap("%c"),
|
||||
key_soptions: get_str_cap("%d"),
|
||||
key_sprevious: get_str_cap("%e"),
|
||||
key_sprint: get_str_cap("%f"),
|
||||
key_sr: get_str_cap("kR"),
|
||||
key_sredo: get_str_cap("%g"),
|
||||
key_sreplace: get_str_cap("%h"),
|
||||
key_sright: get_str_cap("%i"),
|
||||
key_srsume: get_str_cap("%j"),
|
||||
key_ssave: get_str_cap("!1"),
|
||||
key_ssuspend: get_str_cap("!2"),
|
||||
key_stab: get_str_cap("kT"),
|
||||
key_sundo: get_str_cap("!3"),
|
||||
key_suspend: get_str_cap("&7"),
|
||||
key_undo: get_str_cap("&8"),
|
||||
key_up: get_str_cap("ku"),
|
||||
key_f3: get_str_cap(&db, "k3"),
|
||||
key_f4: get_str_cap(&db, "k4"),
|
||||
key_f5: get_str_cap(&db, "k5"),
|
||||
key_f6: get_str_cap(&db, "k6"),
|
||||
key_f7: get_str_cap(&db, "k7"),
|
||||
key_f8: get_str_cap(&db, "k8"),
|
||||
key_f9: get_str_cap(&db, "k9"),
|
||||
key_find: get_str_cap(&db, "@0"),
|
||||
key_help: get_str_cap(&db, "%1"),
|
||||
key_home: get_str_cap(&db, "kh"),
|
||||
key_ic: get_str_cap(&db, "kI"),
|
||||
key_il: get_str_cap(&db, "kA"),
|
||||
key_left: get_str_cap(&db, "kl"),
|
||||
key_ll: get_str_cap(&db, "kH"),
|
||||
key_mark: get_str_cap(&db, "%2"),
|
||||
key_message: get_str_cap(&db, "%3"),
|
||||
key_move: get_str_cap(&db, "%4"),
|
||||
key_next: get_str_cap(&db, "%5"),
|
||||
key_npage: get_str_cap(&db, "kN"),
|
||||
key_open: get_str_cap(&db, "%6"),
|
||||
key_options: get_str_cap(&db, "%7"),
|
||||
key_ppage: get_str_cap(&db, "kP"),
|
||||
key_previous: get_str_cap(&db, "%8"),
|
||||
key_print: get_str_cap(&db, "%9"),
|
||||
key_redo: get_str_cap(&db, "%0"),
|
||||
key_reference: get_str_cap(&db, "&1"),
|
||||
key_refresh: get_str_cap(&db, "&2"),
|
||||
key_replace: get_str_cap(&db, "&3"),
|
||||
key_restart: get_str_cap(&db, "&4"),
|
||||
key_resume: get_str_cap(&db, "&5"),
|
||||
key_right: get_str_cap(&db, "kr"),
|
||||
key_save: get_str_cap(&db, "&6"),
|
||||
key_sbeg: get_str_cap(&db, "&9"),
|
||||
key_scancel: get_str_cap(&db, "&0"),
|
||||
key_scommand: get_str_cap(&db, "*1"),
|
||||
key_scopy: get_str_cap(&db, "*2"),
|
||||
key_screate: get_str_cap(&db, "*3"),
|
||||
key_sdc: get_str_cap(&db, "*4"),
|
||||
key_sdl: get_str_cap(&db, "*5"),
|
||||
key_select: get_str_cap(&db, "*6"),
|
||||
key_send: get_str_cap(&db, "*7"),
|
||||
key_seol: get_str_cap(&db, "*8"),
|
||||
key_sexit: get_str_cap(&db, "*9"),
|
||||
key_sf: get_str_cap(&db, "kF"),
|
||||
key_sfind: get_str_cap(&db, "*0"),
|
||||
key_shelp: get_str_cap(&db, "#1"),
|
||||
key_shome: get_str_cap(&db, "#2"),
|
||||
key_sic: get_str_cap(&db, "#3"),
|
||||
key_sleft: get_str_cap(&db, "#4"),
|
||||
key_smessage: get_str_cap(&db, "%a"),
|
||||
key_smove: get_str_cap(&db, "%b"),
|
||||
key_snext: get_str_cap(&db, "%c"),
|
||||
key_soptions: get_str_cap(&db, "%d"),
|
||||
key_sprevious: get_str_cap(&db, "%e"),
|
||||
key_sprint: get_str_cap(&db, "%f"),
|
||||
key_sr: get_str_cap(&db, "kR"),
|
||||
key_sredo: get_str_cap(&db, "%g"),
|
||||
key_sreplace: get_str_cap(&db, "%h"),
|
||||
key_sright: get_str_cap(&db, "%i"),
|
||||
key_srsume: get_str_cap(&db, "%j"),
|
||||
key_ssave: get_str_cap(&db, "!1"),
|
||||
key_ssuspend: get_str_cap(&db, "!2"),
|
||||
key_stab: get_str_cap(&db, "kT"),
|
||||
key_sundo: get_str_cap(&db, "!3"),
|
||||
key_suspend: get_str_cap(&db, "&7"),
|
||||
key_undo: get_str_cap(&db, "&8"),
|
||||
key_up: get_str_cap(&db, "ku"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -441,7 +362,7 @@ impl Term {
|
|||
/// error output to stderr in case of failure.
|
||||
///
|
||||
/// Any existing references from `curses::term()` will be invalidated by this call!
|
||||
pub fn setup<F>(term: Option<&CStr>, fd: i32, configure: F) -> Option<Arc<Term>>
|
||||
pub fn setup<F>(term: Option<&CStr>, configure: F) -> Option<Arc<Term>>
|
||||
where
|
||||
F: Fn(&mut Term),
|
||||
{
|
||||
|
@ -449,25 +370,19 @@ where
|
|||
// curses itself. We might split this to another lock in the future.
|
||||
let mut global_term = TERM.lock().expect("Mutex poisoned!");
|
||||
|
||||
let result = unsafe {
|
||||
// If cur_term is already initialized for a different $TERM value, calling setupterm() again
|
||||
// will leak memory. Call del_curterm() first to free previously allocated resources.
|
||||
let _ = sys::del_curterm(get_curterm());
|
||||
|
||||
let mut err = 0;
|
||||
if let Some(term) = term {
|
||||
sys::setupterm(term.as_ptr(), fd, &mut err)
|
||||
} else {
|
||||
sys::setupterm(core::ptr::null(), fd, &mut err)
|
||||
}
|
||||
let res = if let Some(term) = term {
|
||||
terminfo::Database::from_name(term.to_str().unwrap())
|
||||
} else {
|
||||
// For historical reasons getting "None" means to get it from the environment.
|
||||
terminfo::Database::from_env()
|
||||
};
|
||||
|
||||
// Safely store the new Term instance or replace the old one. We have the lock so it's safe to
|
||||
// drop the old TERM value and have its refcount decremented - no one will be cloning it.
|
||||
if result == sys::OK {
|
||||
if let Ok(result) = res {
|
||||
// Create a new `Term` instance, prepopulate the capabilities we care about, and allow the
|
||||
// caller to override any as needed.
|
||||
let mut term = Term::new();
|
||||
let mut term = Term::new(result);
|
||||
(configure)(&mut term);
|
||||
|
||||
let term = Arc::new(term);
|
||||
|
@ -479,90 +394,43 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Resets the curses `cur_term` TERMINAL pointer. Subsequent calls to [`curses::term()`](term())
|
||||
/// will return `None`.
|
||||
pub fn reset() {
|
||||
let mut term = TERM.lock().expect("Mutex poisoned!");
|
||||
if term.is_some() {
|
||||
unsafe {
|
||||
// Ignore the result of del_curterm() as the only documented error is that
|
||||
// `cur_term` was already null.
|
||||
let _ = sys::del_curterm(get_curterm());
|
||||
let _ = sys::set_curterm(core::ptr::null());
|
||||
}
|
||||
*term = None;
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a nonempty String capability from termcap, or None if missing or empty.
|
||||
/// Panics if the given code string does not contain exactly two bytes.
|
||||
fn get_str_cap(code: &str) -> Option<CString> {
|
||||
let code = to_cstr_code(code);
|
||||
// termcap spec says nul is not allowed in terminal sequences and must be encoded;
|
||||
// so the terminating NUL is the end of the string.
|
||||
let tstr = unsafe { sys::tgetstr(code.as_ptr(), core::ptr::null_mut()) };
|
||||
let result = try_ptr_to_cstr(tstr);
|
||||
// Paranoia: do not return empty strings.
|
||||
if let Some(s) = &result {
|
||||
if s.as_bytes().is_empty() {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
result
|
||||
fn get_str_cap(db: &terminfo::Database, code: &str) -> Option<CString> {
|
||||
db.raw(code).map(|cap| match cap {
|
||||
terminfo::Value::True => "1".to_string().as_bytes().to_cstring(),
|
||||
terminfo::Value::Number(n) => n.to_string().as_bytes().to_cstring(),
|
||||
terminfo::Value::String(s) => s.clone().to_cstring(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Return a number capability from termcap, or None if missing.
|
||||
/// Panics if the given code string does not contain exactly two bytes.
|
||||
fn get_num_cap(code: &str) -> Option<usize> {
|
||||
let code = to_cstr_code(code);
|
||||
match unsafe { sys::tgetnum(code.as_ptr()) } {
|
||||
-1 => None,
|
||||
n => Some(usize::try_from(n).unwrap()),
|
||||
fn get_num_cap(db: &terminfo::Database, code: &str) -> Option<usize> {
|
||||
match db.raw(code) {
|
||||
Some(terminfo::Value::Number(n)) if *n >= 0 => Some(*n as usize),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a flag capability from termcap, or false if missing.
|
||||
/// Panics if the given code string does not contain exactly two bytes.
|
||||
fn get_flag_cap(code: &str) -> bool {
|
||||
let code = to_cstr_code(code);
|
||||
unsafe { sys::tgetflag(code.as_ptr()) != 0 }
|
||||
}
|
||||
|
||||
/// `code` is the two-digit termcap code. See termcap(5) for a reference.
|
||||
/// Panics if anything other than a two-ascii-character `code` is passed into the function.
|
||||
const fn to_cstr_code(code: &str) -> [libc::c_char; 3] {
|
||||
use libc::c_char;
|
||||
let code = code.as_bytes();
|
||||
if code.len() != 2 {
|
||||
panic!("Invalid termcap code provided");
|
||||
}
|
||||
[code[0] as c_char, code[1] as c_char, b'\0' as c_char]
|
||||
fn get_flag_cap(db: &terminfo::Database, code: &str) -> bool {
|
||||
db.raw(code)
|
||||
.map(|cap| matches!(cap, terminfo::Value::True))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Covers over tparm().
|
||||
pub fn tparm0(cap: &CStr) -> Option<CString> {
|
||||
// Take the lock because tparm races with del_curterm, etc.
|
||||
let _term: std::sync::MutexGuard<Option<Arc<Term>>> = TERM.lock().unwrap();
|
||||
assert!(!cap.to_bytes().is_empty());
|
||||
let cap_ptr = cap.as_ptr() as *mut libc::c_char;
|
||||
// Safety: we're trusting tparm here.
|
||||
unsafe { try_ptr_to_cstr(tparm(cap_ptr)) }
|
||||
let cap = cap.to_bytes();
|
||||
terminfo::expand!(cap).ok().map(|x| x.to_cstring())
|
||||
}
|
||||
|
||||
/// Covers over tparm().
|
||||
pub fn tparm1(cap: &CStr, param1: i32) -> Option<CString> {
|
||||
// Take the lock because tparm races with del_curterm, etc.
|
||||
let _term: std::sync::MutexGuard<Option<Arc<Term>>> = TERM.lock().unwrap();
|
||||
assert!(!cap.to_bytes().is_empty());
|
||||
let cap_ptr = cap.as_ptr() as *mut libc::c_char;
|
||||
// Safety: we're trusting tparm here.
|
||||
unsafe { try_ptr_to_cstr(tparm(cap_ptr, param1 as libc::c_int)) }
|
||||
}
|
||||
|
||||
/// Wrapper over tputs.
|
||||
/// The caller is responsible for synchronization.
|
||||
pub fn tputs(str: &CStr, affcnt: libc::c_int, putc: sys::putc_t) -> libc::c_int {
|
||||
let str_ptr = str.as_ptr() as *mut libc::c_char;
|
||||
// Safety: we're trusting tputs here.
|
||||
unsafe { sys::tputs(str_ptr, affcnt, putc) }
|
||||
let cap = cap.to_bytes();
|
||||
terminfo::expand!(cap; param1).ok().map(|x| x.to_cstring())
|
||||
}
|
||||
|
|
|
@ -506,10 +506,8 @@ fn initialize_curses_using_fallbacks(vars: &EnvStack) {
|
|||
// `term` here is one of our hard-coded strings above; we can unwrap because we can
|
||||
// guarantee it doesn't contain any interior NULs.
|
||||
let term_cstr = CString::new(term).unwrap();
|
||||
let success = curses::setup(Some(&term_cstr), libc::STDOUT_FILENO, |term| {
|
||||
apply_term_hacks(vars, term)
|
||||
})
|
||||
.is_some();
|
||||
let success =
|
||||
curses::setup(Some(&term_cstr), |term| apply_term_hacks(vars, term)).is_some();
|
||||
if is_interactive_session() {
|
||||
if success {
|
||||
FLOG!(warning, wgettext!("Using fallback terminal type"), term);
|
||||
|
@ -640,11 +638,7 @@ fn init_curses(vars: &EnvStack) {
|
|||
}
|
||||
}
|
||||
|
||||
if curses::setup(None, libc::STDOUT_FILENO, |term| {
|
||||
apply_term_hacks(vars, term)
|
||||
})
|
||||
.is_none()
|
||||
{
|
||||
if curses::setup(None, |term| apply_term_hacks(vars, term)).is_none() {
|
||||
if is_interactive_session() {
|
||||
let term = vars.get_unless_empty(L!("TERM")).map(|v| v.as_string());
|
||||
FLOG!(warning, wgettext!("Could not set up terminal."));
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/resource.h>
|
||||
#include <term.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define UNUSED(x) (void)(x)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Generic output functions.
|
||||
use crate::color::RgbColor;
|
||||
use crate::common::{self, assert_is_locked, wcs2string_appending};
|
||||
use crate::common::{self, wcs2string_appending};
|
||||
use crate::curses::{self, tparm1, Term};
|
||||
use crate::env::EnvVar;
|
||||
use crate::wchar::prelude::*;
|
||||
|
@ -10,7 +10,6 @@ use std::ffi::CStr;
|
|||
use std::io::{Result, Write};
|
||||
use std::os::fd::RawFd;
|
||||
use std::sync::atomic::{AtomicU8, Ordering};
|
||||
use std::sync::Mutex;
|
||||
|
||||
bitflags! {
|
||||
#[derive(Copy, Clone, Default)]
|
||||
|
@ -423,35 +422,13 @@ impl Write for Outputter {
|
|||
}
|
||||
}
|
||||
|
||||
// tputs accepts a function pointer that receives an int only.
|
||||
// Use the following lock to redirect it to the proper outputter.
|
||||
// Note we can't use an owning Mutex because the tputs_writer must access it and Mutex is not
|
||||
// recursive.
|
||||
static TPUTS_RECEIVER_LOCK: Mutex<()> = Mutex::new(());
|
||||
static mut TPUTS_RECEIVER: *mut Outputter = std::ptr::null_mut();
|
||||
|
||||
extern "C" fn tputs_writer(b: curses::TputsArg) -> libc::c_int {
|
||||
// Safety: we hold the lock.
|
||||
assert_is_locked!(&TPUTS_RECEIVER_LOCK);
|
||||
let receiver = unsafe { TPUTS_RECEIVER.as_mut().expect("null TPUTS_RECEIVER") };
|
||||
receiver.push(b as u8);
|
||||
0
|
||||
}
|
||||
|
||||
impl Outputter {
|
||||
/// Emit a terminfo string, like tputs.
|
||||
/// affcnt (number of lines affected) is assumed to be 1, i.e. not applicable.
|
||||
pub fn tputs(&mut self, str: &CStr) {
|
||||
let affcnt = 1;
|
||||
// Acquire the lock, set the receiver, and call tputs.
|
||||
let _guard = TPUTS_RECEIVER_LOCK.lock().unwrap();
|
||||
// Safety: we hold the lock.
|
||||
let saved_recv = unsafe { TPUTS_RECEIVER };
|
||||
unsafe { TPUTS_RECEIVER = self as *mut Outputter };
|
||||
self.begin_buffering();
|
||||
let _ = curses::tputs(str, affcnt, tputs_writer);
|
||||
let _ = self.write(str.to_bytes());
|
||||
self.end_buffering();
|
||||
unsafe { TPUTS_RECEIVER = saved_recv };
|
||||
}
|
||||
|
||||
/// Convenience cover over tputs, in recognition of the fact that our Term has Optional fields.
|
||||
|
|
|
@ -214,25 +214,12 @@ pub fn spawn<F: FnOnce() + Send + 'static>(callback: F) -> bool {
|
|||
/// more ergonomic to call it in general and also makes it possible to call it via ffi at all.
|
||||
pub fn asan_maybe_exit(code: i32) {
|
||||
if cfg!(feature = "asan") {
|
||||
asan_before_exit();
|
||||
unsafe {
|
||||
libc::exit(code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// When running under ASAN, free up some allocations that would normally have been left for the OS
|
||||
/// to reclaim to avoid some false positive LSAN reports.
|
||||
///
|
||||
/// This function is always defined but is a no-op if not running under ASAN. This is to make it
|
||||
/// more ergonomic to call it in general and also makes it possible to call it via ffi at all.
|
||||
pub fn asan_before_exit() {
|
||||
if cfg!(feature = "asan") && !is_forked_child() {
|
||||
// Free ncurses terminal state
|
||||
crate::curses::reset();
|
||||
}
|
||||
}
|
||||
|
||||
/// Data shared between the thread pool [`ThreadPool`] and worker threads [`WorkerThread`].
|
||||
#[derive(Default)]
|
||||
struct ThreadPoolProtected {
|
||||
|
|
Loading…
Reference in New Issue
Block a user