summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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);
}