#![warn(
future_incompatible,
missing_copy_implementations,
missing_debug_implementations,
missing_docs,
rust_2018_compatibility,
rust_2018_idioms,
trivial_casts,
trivial_numeric_casts,
unstable_features,
unused_import_braces,
unused_qualifications,
unused_results,
)]
use std::cell::RefCell;
use std::cell::RefMut;
use std::ffi::CStr;
use std::ffi::CString;
use std::fmt::Debug;
use std::fmt::Error;
use std::fmt::Formatter;
use std::mem::replace;
use std::mem::MaybeUninit;
use std::ptr::null;
use std::sync::Mutex;
use std::sync::MutexGuard;
use std::sync::Once;
use std::sync::TryLockError;
use libc::c_char;
use libc::c_int;
use libc::c_void;
use libc::calloc;
use libc::free;
use uid::Id as IdT;
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
struct T(());
type Id = IdT<T>;
#[allow(non_camel_case_types)]
type rl_voidfunc_t = extern "C" fn();
#[allow(non_camel_case_types)]
type rl_vintfunc_t = extern "C" fn(c_int);
#[allow(non_camel_case_types)]
type rl_vcpfunc_t = unsafe extern "C" fn(*mut c_char);
#[allow(non_camel_case_types)]
type rl_hook_func_t = extern "C" fn() -> c_int;
extern "C" {
static mut rl_line_buffer: *mut c_char;
static mut rl_line_buffer_len: c_int;
static mut rl_point: c_int;
static mut rl_end: c_int;
static mut rl_executing_keyseq: *mut c_char;
static mut rl_key_sequence_length: c_int;
static mut rl_input_available_hook: *mut rl_hook_func_t;
static mut rl_catch_signals: c_int;
static mut rl_catch_sigwinch: c_int;
static mut rl_redisplay_function: *mut rl_voidfunc_t;
static mut rl_prep_term_function: *mut rl_vintfunc_t;
static mut rl_deprep_term_function: *mut rl_voidfunc_t;
fn rl_callback_handler_install(prompt: *const c_char, handler: *mut rl_vcpfunc_t);
fn rl_stuff_char(c: c_int) -> c_int;
fn rl_callback_read_char();
fn rl_replace_line(text: *const c_char, clear_undo: c_int);
fn rl_save_state(state: *mut readline_state) -> c_int;
fn rl_restore_state(state: *const readline_state) -> c_int;
}
fn load_state(state: *mut readline_state) {
let result = unsafe { rl_save_state(state) };
assert_eq!(result, 0);
}
#[repr(C, align(8))]
#[derive(Clone)]
struct readline_state([u8; 512]);
impl readline_state {
fn load(&mut self) {
load_state(self)
}
fn save(&self) {
let result = unsafe { rl_restore_state(self) };
assert_eq!(result, 0);
}
}
impl Debug for readline_state {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
f.debug_struct("readline_state").finish()
}
}
trait Locked {
fn is_locked(&self) -> bool;
}
impl<T> Locked for Mutex<T> {
#[allow(clippy::match_like_matches_macro)]
fn is_locked(&self) -> bool {
self.try_lock().err().map_or(false, |x| {
match x {
TryLockError::WouldBlock => true,
_ => false,
}
})
}
}
struct ReadlineGuard<'data, 'slf> {
_guard: MutexGuard<'data, Id>,
state: RefMut<'slf, readline_state>,
}
impl<'data, 'slf> Drop for ReadlineGuard<'data, 'slf> {
fn drop(&mut self) {
self.state.load()
}
}
type Key = [u8];
#[derive(Debug)]
pub struct Readline {
id: Id,
state: RefCell<readline_state>,
}
impl Readline {
extern "C" fn initialize_term(_: c_int) {}
extern "C" fn uninitialize_term() {}
extern "C" fn display() {}
extern "C" fn input_available() -> c_int {
0
}
extern "C" fn handle_line(line: *mut c_char) {
debug_assert!(Self::mutex().is_locked());
if line.is_null() {
let _ = replace(Self::line(), Some(CString::new("").unwrap()));
} else {
unsafe {
let _ = replace(Self::line(), Some(CStr::from_ptr(line).into()));
free(line as *mut c_void);
}
}
}
pub fn new() -> Self {
let rl = Self {
id: Id::new(),
state: RefCell::new(Self::initial().clone()),
};
{
let mut guard = rl.activate();
unsafe {
debug_assert!(rl_line_buffer.is_null());
debug_assert!(rl_executing_keyseq.is_null());
rl_line_buffer = calloc(1, 64) as *mut c_char;
rl_line_buffer_len = 64;
rl_executing_keyseq = calloc(1, 16) as *mut c_char;
rl_key_sequence_length = 16;
assert!(!rl_line_buffer.is_null(), "failed to allocate rl_line_buffer");
assert!(!rl_executing_keyseq.is_null(), "failed to allocate rl_executing_keyseq");
}
guard.state.load();
guard.state.save();
}
rl
}
fn initial() -> &'static readline_state {
static mut STATE: MaybeUninit<readline_state> = MaybeUninit::uninit();
static ONCE: Once = Once::new();
ONCE.call_once(|| unsafe {
rl_catch_signals = 0;
rl_catch_sigwinch = 0;
rl_input_available_hook = Self::input_available as *mut rl_hook_func_t;
rl_redisplay_function = Self::display as *mut rl_voidfunc_t;
rl_prep_term_function = Self::initialize_term as *mut rl_vintfunc_t;
rl_deprep_term_function = Self::uninitialize_term as *mut rl_voidfunc_t;
rl_callback_handler_install(null::<c_char>(), Self::handle_line as *mut rl_vcpfunc_t);
free(rl_line_buffer as *mut c_void);
free(rl_executing_keyseq as *mut c_void);
rl_line_buffer = null::<c_char>() as *mut c_char;
rl_executing_keyseq = null::<c_char>() as *mut c_char;
load_state(STATE.as_mut_ptr());
});
unsafe { &*STATE.as_ptr() }
}
fn mutex() -> &'static Mutex<Id> {
static mut MUTEX: Option<Mutex<Id>> = None;
static ONCE: Once = Once::new();
ONCE.call_once(|| unsafe { MUTEX = Some(Mutex::new(Id::new())) });
match unsafe { &MUTEX } {
Some(mutex) => mutex,
None => unreachable!(),
}
}
fn line() -> &'static mut Option<CString> {
static mut LINE: Option<CString> = None;
debug_assert!(Self::mutex().is_locked());
unsafe { &mut LINE }
}
fn activate<'slf, 'data: 'slf>(&'slf self) -> ReadlineGuard<'data, 'slf> {
let mut guard = Self::mutex().lock().unwrap();
let state = self.state.borrow_mut();
if *guard != self.id {
state.save();
*guard = self.id;
}
ReadlineGuard {
_guard: guard,
state,
}
}
pub fn feed(&mut self, key: impl AsRef<Key>) -> Option<CString> {
if key.as_ref().is_empty() {
return None
}
let _guard = self.activate();
for &b in key.as_ref() {
let result = unsafe { rl_stuff_char(c_int::from(b)) };
assert_ne!(result, 0, "libreadline's input buffer overflowed");
}
unsafe { rl_callback_read_char(); }
Self::line().take()
}
pub fn reset<S>(&mut self, line: S, cursor: usize, clear_undo: bool)
where
S: AsRef<CStr>,
{
let s = line.as_ref();
assert!(cursor <= s.to_bytes().len(), "invalid cursor position");
let _guard = self.activate();
unsafe {
rl_replace_line(s.as_ptr(), clear_undo.into());
rl_point = cursor as c_int;
}
}
pub fn peek<F, R>(&self, peeker: F) -> R
where
F: FnOnce(&CStr, usize) -> R,
{
let _guard = self.activate();
let (s, pos, len) = unsafe {
debug_assert!(rl_end >= 0);
debug_assert!(rl_point >= 0);
let buf = rl_line_buffer;
let len = rl_end as usize;
let pos = rl_point as usize;
(CStr::from_ptr(buf), pos, len)
};
debug_assert_eq!(s.to_bytes().len(), len);
peeker(s, pos)
}
}
impl Default for Readline {
fn default() -> Self {
Self::new()
}
}
impl Drop for Readline {
fn drop(&mut self) {
let _guard = self.activate();
unsafe {
free(rl_executing_keyseq as *mut c_void);
free(rl_line_buffer as *mut c_void);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::mem::align_of;
#[test]
fn is_locked() {
let mutex = Mutex::<u64>::new(42);
assert!(!mutex.is_locked());
{
let _guard = mutex.lock().unwrap();
assert!(mutex.is_locked());
}
assert!(!mutex.is_locked());
}
#[test]
fn alignment() {
assert_eq!(align_of::<readline_state>(), 8);
}
#[test]
fn empty_input() {
let mut rl = Readline::new();
assert!(rl.feed(b"").is_none())
}
#[test]
fn empty_line_input() {
let mut rl = Readline::new();
assert_eq!(rl.feed(b"\n").unwrap(), CString::new("").unwrap())
}
#[test]
fn multiple_inputs() {
let mut rl = Readline::new();
assert!(rl.feed(b"first").is_none());
assert_eq!(rl.feed(b"\n").unwrap(), CString::new("first").unwrap());
assert!(rl.feed(b"second").is_none());
assert_eq!(rl.feed(b"\n").unwrap(), CString::new("second").unwrap());
}
#[test]
fn cursor() {
let mut rl = Readline::new();
assert_eq!(rl.feed(b"a"), None);
assert_eq!(rl.peek(|s, p| (s.to_owned(), p)), (CString::new("a").unwrap(), 1));
assert_eq!(rl.feed(b"b"), None);
assert_eq!(rl.peek(|s, p| (s.to_owned(), p)), (CString::new("ab").unwrap(), 2));
assert_eq!(rl.feed(b"c"), None);
assert_eq!(rl.peek(|s, p| (s.to_owned(), p)), (CString::new("abc").unwrap(), 3));
}
#[test]
fn reset() {
let mut rl = Readline::new();
assert_eq!(rl.feed(b"xyz"), None);
assert_eq!(rl.peek(|s, p| (s.to_owned(), p)), (CString::new("xyz").unwrap(), 3));
rl.reset(&CString::new("abc").unwrap(), 1, true);
assert_eq!(rl.peek(|s, p| (s.to_owned(), p)), (CString::new("abc").unwrap(), 1));
assert_eq!(rl.feed(b"x"), None);
assert_eq!(rl.peek(|s, p| (s.to_owned(), p)), (CString::new("axbc").unwrap(), 2));
assert_eq!(rl.feed(b"\n").unwrap(), CString::new("axbc").unwrap());
rl.reset(&CString::new("123").unwrap(), 3, true);
assert_eq!(rl.peek(|s, p| (s.to_owned(), p)), (CString::new("123").unwrap(), 3));
assert_eq!(rl.feed(b"y"), None);
assert_eq!(rl.peek(|s, p| (s.to_owned(), p)), (CString::new("123y").unwrap(), 4));
}
#[test]
#[should_panic(expected = "invalid cursor position")]
fn reset_panic() {
Readline::new().reset(&CString::new("abc").unwrap(), 4, true);
}
}