summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRory Dudley2024-09-30 21:55:32 -0600
committerRory Dudley2024-09-30 23:01:41 -0600
commit350be1b8869ce9d3a572348104c30f804d44641e (patch)
tree75c68761ccd21a3395609f0fb1e2e0a440b441c8
parent243a8c53ae03713b3d6b4f5bf82859f9c93be6ed (diff)
downloaddwarvish-350be1b8869ce9d3a572348104c30f804d44641e.tar.gz
Replace io::stdin().read_line() with custom function
Added the termios crate to facilitate the changing of certain terminal options. It is a wrapper around the termios C library, so 'man 3 termios' for more details. Added the custom getchar() function, with retrieves characters from STDIN as they are typed by the user (as opposed to waiting for a newline, like io::stdin().read_line()). This is necessary, since keys like <tab> and <up> have special functionality, which needs to be acted on before command submission. Added the custom getline() function, which uses getchar() to read characters as they are typed. The getline() function contains the logic for the various key presses. For most characters, we simply push the byte to a buffer, and print it out to the screen (since getline() assumes ECHO is off). Signed-off-by: Rory Dudley <rory@netc.lu>
-rw-r--r--Cargo.lock18
-rw-r--r--Cargo.toml1
-rw-r--r--src/buffer.rs59
-rw-r--r--src/main.rs31
4 files changed, 97 insertions, 12 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 31da831..cb0161a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -8,13 +8,14 @@ version = "0.0.1"
dependencies = [
"libc",
"signal-hook",
+ "termios",
]
[[package]]
name = "libc"
-version = "0.2.153"
+version = "0.2.158"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
+checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
[[package]]
name = "signal-hook"
@@ -28,9 +29,18 @@ dependencies = [
[[package]]
name = "signal-hook-registry"
-version = "1.4.1"
+version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
+checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "termios"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b"
dependencies = [
"libc",
]
diff --git a/Cargo.toml b/Cargo.toml
index 8baea37..93c090a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,3 +15,4 @@ path = "src/main.rs"
[dependencies]
libc = "0.2.153"
signal-hook = "0.3.17"
+termios = "0.3.3"
diff --git a/src/buffer.rs b/src/buffer.rs
new file mode 100644
index 0000000..48e2d85
--- /dev/null
+++ b/src/buffer.rs
@@ -0,0 +1,59 @@
+use std::io::{self, Read, Write};
+use std::sync::{Arc, Mutex};
+
+// STDIN is file descriptor (fd) 0 on Linux and other UN*X-likes
+pub const STDIN: i32 = 0;
+
+/// Retrieve a single byte of input
+///
+/// Requires some setup beforehand (see beginning of repl())
+fn getchar() -> u8 {
+ let mut b = [0; 1];
+ io::stdout().lock().flush().unwrap();
+ io::stdin().read_exact(&mut b).unwrap();
+ b[0]
+}
+
+/// Handle user input at the repl prompt
+///
+/// This is required instead of io::stdin().read_line(), because certain
+/// keys like `<tab>` and `<up>` have special functions (cycle through
+/// autocomplete options, and history, respectively). It leverages
+/// [getchar] to read each character as the user inputs it. This also
+/// means special cases for handling backspace, newlines, etc. Assumes
+/// that (ICANON and ECHO) are off. See the beginning of [crate::repl]
+/// for more details.
+pub fn getline(buffer: &mut Arc<Mutex<Vec<u8>>>) -> usize {
+ loop {
+ let c = getchar();
+ match c {
+ // enter/return
+ b'\n' => break,
+
+ // tab
+ b'\t' => {
+ print!(" ");
+ buffer.lock().unwrap().push(b' ');
+ }
+
+ // ctrl-d
+ 4 => return 0,
+
+ // backspace
+ 127 => {
+ buffer.lock().unwrap().pop();
+ print!("\u{8} \u{8}");
+ }
+
+ // everything else
+ _ => {
+ print!("{}", c as char);
+ buffer.lock().unwrap().push(c);
+ }
+ }
+ }
+
+ println!();
+ buffer.lock().unwrap().push(b'\n');
+ buffer.lock().unwrap().len()
+}
diff --git a/src/main.rs b/src/main.rs
index 98b49ed..d5147ff 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,11 +1,14 @@
use std::env;
use std::io::{self, Write};
use std::sync::{Arc, Mutex};
+mod buffer;
mod path;
mod poem;
use poem::{read::Readable, recite::Reciteable, Poem};
mod compose;
+use buffer::{getline, STDIN};
use compose::Environment;
+use termios::{tcsetattr, Termios, ECHO, ICANON, TCSANOW};
/// Starts the main shell loop
///
@@ -22,12 +25,22 @@ use compose::Environment;
/// repl(&mut away, &mut env);
/// }
/// ```
-fn repl(away: &mut Arc<Mutex<bool>>, env: &mut Environment) {
+fn repl(away: &mut Arc<Mutex<bool>>, buffer: &mut Arc<Mutex<Vec<u8>>>, env: &mut Environment) {
+ // Setup termios flags
+ let mut termios = Termios::from_fd(STDIN).unwrap();
+ termios.c_lflag &= !(ICANON | ECHO);
+
// Initial path refresh on startup
env.bins = path::refresh();
// Main shell loop
loop {
+ // Reset terminal using proper termios flags
+ tcsetattr(STDIN, TCSANOW, &mut termios).unwrap();
+
+ // Clear the buffer
+ buffer.lock().unwrap().clear();
+
// Get the prompt
let prompt = match env::var("PS1") {
Ok(val) => val,
@@ -42,10 +55,7 @@ fn repl(away: &mut Arc<Mutex<bool>>, env: &mut Environment) {
*away.lock().unwrap() = false;
// Wait for user input
- let mut poetry = String::new();
- let bytes = io::stdin()
- .read_line(&mut poetry)
- .expect("dwvsh: error: unable to evaluate the input string");
+ let bytes = getline(buffer);
// Check if we've reached EOF (i.e. <C-d>)
if bytes == 0 {
@@ -53,8 +63,10 @@ fn repl(away: &mut Arc<Mutex<bool>>, env: &mut Environment) {
break;
}
- // Trim the input
- let poetry = String::from(poetry.trim());
+ // Convert buffer to a string and trim it
+ let poetry = String::from_utf8_lossy(&buffer.lock().unwrap())
+ .trim()
+ .to_string();
// Skip parsing if there is no poetry
if poetry.is_empty() {
@@ -132,9 +144,12 @@ fn main() {
// Handle signals
let mut away = Arc::new(Mutex::new(true));
+ let mut buffer: Arc<Mutex<Vec<u8>>> = Arc::new(Mutex::new(vec![]));
unsafe {
let away = Arc::clone(&away);
+ let buffer = Arc::clone(&buffer);
signal_hook::low_level::register(signal_hook::consts::SIGINT, move || {
+ buffer.lock().unwrap().clear();
if *away.lock().unwrap() {
println!();
} else {
@@ -153,5 +168,5 @@ fn main() {
options(&mut env);
// Begin evaluating commands
- repl(&mut away, &mut env);
+ repl(&mut away, &mut buffer, &mut env);
}