diff --git a/examples/callback-example.rs b/examples/callback-example.rs new file mode 100644 index 0000000..84c7175 --- /dev/null +++ b/examples/callback-example.rs @@ -0,0 +1,35 @@ +extern crate readline; + +use std::process::exit; + +fn eval(line: Option) { + let line = match line { + Some(line) => line, + None => { + println!(""); + exit(0); + } + }; + + if line == "quit" { + exit(0); + } + + // add words that start with 'a' to the history to demonstrate + else if line[0 .. 1] == "a".to_string() { + readline::add_history(line.as_ref()); + } + + println!("Input: '{}'", line); +} + +fn main() { + readline::rl_callback_handler_install("Next: ", eval); + + // simple r"e"pl + loop { + // a real program would interleave this with other async i/o, using + // something like mio + readline::rl_callback_read_char(); + } +} diff --git a/src/lib.rs b/src/lib.rs index e30e8f2..1617918 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,6 +41,12 @@ mod ext_readline { /* readline */ pub fn readline(p: *const c_char) -> *const c_char; + + /* Asynchronous Interface */ + pub fn rl_callback_handler_install(prompt: *const c_char, + lhandler: extern fn(*const c_char)); + pub fn rl_callback_read_char(); + pub fn rl_callback_handler_remove(); } } @@ -96,6 +102,27 @@ pub fn history_expand(input: &str) -> Result, String> { } } +// converts a malloc'd char* returned from readline into a String, and frees the char* +fn c_str_to_string(cstr: *const c_char) -> Option { + if cstr.is_null() { // user pressed Ctrl-D + None + } + else { + unsafe { + let slice = CStr::from_ptr(cstr); + let bytes = slice.to_bytes(); + + // the cstrurn from readline needs to be explicitly freed + // so clone the input first + let line = String::from_utf8_lossy(bytes).into_owned().clone(); + + libc::free(cstr as *mut libc::c_void); + + Some(line) + } + } +} + /// Invoke the external `readline()`. /// /// Returns an `Option` representing whether a `String` was returned @@ -104,21 +131,46 @@ pub fn readline(prompt: &str) -> Option { let cprmt = CString::new(prompt).unwrap().as_ptr(); unsafe { let ret = ext_readline::readline(cprmt); - if ret.is_null() { // user pressed Ctrl-D - None - } - else { - let slice = CStr::from_ptr(ret); - let bytes = slice.to_bytes(); + c_str_to_string(ret) + } +} - // the return from readline needs to be explicitly freed - // so clone the input first - let line = String::from_utf8_lossy(bytes).into_owned().clone(); +static mut _lhandler: Option)> = None; - libc::free(ret as *mut libc::c_void); +extern fn coerced_callback(ret: *const c_char) { + let line = c_str_to_string(ret); - Some(line) - } + unsafe { + _lhandler.unwrap()(line); + } +} + +/// Install a new input handler. +/// +/// This function must be called before `rl_callback_read_char()` or risk +/// panicking. +pub fn rl_callback_handler_install(prompt: &str, lhandler: fn(Option)) { + let cprmt = CString::new(prompt).unwrap().as_ptr(); + unsafe { _lhandler = Some(lhandler); } + unsafe { + ext_readline::rl_callback_handler_install(cprmt, coerced_callback); + } +} + +/// Invokes `rl_callback_read_char()` +/// +/// This function will panic if `rl_callback_handler_install()` has not yet +/// been called. (As there is no handler for it to invoke). +pub fn rl_callback_read_char() { + unsafe { + ext_readline::rl_callback_read_char(); + } +} + +pub fn rl_callback_handler_remove() { + unsafe { _lhandler = None }; + unsafe { + ext_readline::rl_callback_handler_remove(); } }