From a94368c9658a5a270fdda6f015d2ab98dc4f5323 Mon Sep 17 00:00:00 2001 From: Timothy Warren Date: Thu, 19 Sep 2019 12:19:20 -0400 Subject: [PATCH] Minor code cleanup, make get_cursor_position actually work correctly --- src/editor.rs | 138 +++++++++++----------------------------- src/main.rs | 6 +- src/terminal_helpers.rs | 72 +++++++++++++++++++-- 3 files changed, 108 insertions(+), 108 deletions(-) diff --git a/src/editor.rs b/src/editor.rs index d20da9e..d3f94b9 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -287,11 +287,11 @@ impl Editor { '\r' => return Some(Enter), ch => { if ch.is_ascii_control() { - return Some(Ctrl(ctrl_to_letter(ch))) + return Some(Ctrl(ctrl_to_letter(ch))); } - return Some(OtherKey(ch)) - }, + return Some(OtherKey(ch)); + } }, None => return None, } @@ -417,67 +417,11 @@ impl Editor { return Some(input[0]); } - fn get_cursor_position(&mut self) -> TermSize { - let mut query = String::new(); - // Move the cursor as far to the bottom right as is practical - query.push_str("\x1b[999C\x1b[999B"); - - // Ask the shell where the cursor is - query.push_str("\x1b[6n"); - - let stdout = io::stdout(); - let mut handle = stdout.lock(); - - // If you can't write to stdout, you might as well just panic - handle.write_all(query.as_bytes()).unwrap(); - - let stdin = io::stdin(); - let stdin = stdin.lock(); - let mut handle = stdin.take(32); - let mut input = String::new(); - let read_res = handle.read_to_string(&mut input); - clean_unwrap(read_res); - - if input.len() < 6 { - panic!( - "Invalid or missing response to cursor location query: {:?}", - input - ); - } - - let mut row_str = String::new(); - let mut col_str = String::new(); - - let mut index = 0; - - for ch in input.chars() { - if ch == ';' { - index += 1; - } else if ch == 'R' { - break; - } else { - if index == 0 { - row_str.push(ch) - } else { - col_str.push(ch) - } - } - } - - let rows = clean_unwrap(row_str.parse()); - let cols = clean_unwrap(row_str.parse()); - - return TermSize { cols, rows }; - } - + /// Get terminal size in rows and columns fn get_window_size(&mut self) -> TermSize { match get_term_size() { Some(size) => size, - - None => { - print!("\x1b[999C\x1b[999B"); - return self.get_cursor_position(); - } + None => get_cursor_position(), } } @@ -861,30 +805,27 @@ impl Editor { self.cursor_x = self.rows[self.cursor_y].chars.len(); } } - Ctrl(c) => { - match c { - 'f' => self.find(), - // 'h' => self._del_or_backspace(Backspace), - 's' => { - // Save success/error message handled by save method - match self.save() { - Ok(_) => (), - Err(_) => (), - } + Ctrl(c) => match c { + 'f' => self.find(), + 's' => { + // Save success/error message handled by save method + match self.save() { + Ok(_) => (), + Err(_) => (), } - 'q' => { - if self.dirty > 0 && self.quit_times > 0 { - self.set_status_message(&format!("WARNING!!! File has unsaved changes. Press Ctrl-Q {} more times to quit.", self.quit_times)); - self.quit_times -= 1; - return Some(OtherKey('\0')); - } - print!("\x1b[2J"); - print!("\x1b[H"); - // Break out of the input loop - return None; - }, - _ => (), } + 'q' => { + if self.dirty > 0 && self.quit_times > 0 { + self.set_status_message(&format!("WARNING!!! File has unsaved changes. Press Ctrl-Q {} more times to quit.", self.quit_times)); + self.quit_times -= 1; + return Some(OtherKey('\0')); + } + print!("\x1b[2J"); + print!("\x1b[H"); + // Break out of the input loop + return None; + } + _ => (), } Function(_) => (), OtherKey(c) => { @@ -1150,6 +1091,7 @@ impl Editor { // Row Operations // ------------------------------------------------------------------------ + /// Convert cursor x position to the rendered x position fn row_cx_to_rx(&mut self, index: usize, cx: usize) -> usize { let mut rx: usize = 0; @@ -1168,6 +1110,7 @@ impl Editor { rx } + /// Convert rendered x position to cursor x position fn row_rx_to_cx(&mut self, index: usize, rx: usize) -> usize { let mut current_rx: usize = 0; let mut cx: usize = 0; @@ -1569,7 +1512,7 @@ fn get_syntax_db() -> Vec { SyntaxFlags::HIGHLIGHT_NUMBERS | SyntaxFlags::HIGHLIGHT_STRINGS, ), Syntax::new( - "JavaScript", + "JavaScript/TypeScript", vec![".js", ".mjs", ".jsx", ".ts", ".tsx"], vec![ "instanceof", @@ -1672,22 +1615,6 @@ fn highlight_range(vec: &mut Vec, range: Range, value: Highlig } } -/// Do the equivalent of a Result::unwrap, but cleanup terminal output -/// first, so it doesn't destroy console output afterwards. -fn clean_unwrap(res: Result) -> O -where - E: std::fmt::Debug, -{ - match res { - Ok(value) => value, - Err(e) => { - print!("\x1bc"); - disable_raw_mode(); - panic!("{:?}", e); - } - } -} - #[cfg(test)] mod tests { use super::*; @@ -1727,8 +1654,15 @@ mod tests { } #[test] - fn ctrl_key_functions() { - let a = ctrl_to_letter(ctrl_a); + fn ctrl_to_letter_() { + let a = ctrl_to_letter('\x01'); assert_eq!(a, 'a', "ctrl_to_letter gives letter from ctrl chord"); } + + #[test] + #[should_panic] + fn ctrl_to_letter_panic() { + // Del code doesn't map to Ctrl+letter combo + ctrl_to_letter('\x7f'); + } } diff --git a/src/main.rs b/src/main.rs index 5facfaa..a6e16c9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,7 +23,10 @@ lazy_static! { } fn main() -> Result<(), Error> { - let args: Vec = env::args().collect(); + // 'Access' the saved termios instance, to make sure it is set + // before you enable raw mode. + let mutex = Arc::clone(&ORIGINAL_TERMIOS); + let _ = mutex.lock().unwrap(); // Disable canonical/"cooked" terminal mode enable_raw_mode(); @@ -32,6 +35,7 @@ fn main() -> Result<(), Error> { let mut editor = Editor::new(); // Open the file if specified, from the command line + let args: Vec = env::args().collect(); if args.len() >= 2 { editor.open(&args[1])?; } diff --git a/src/terminal_helpers.rs b/src/terminal_helpers.rs index e2c291f..3dd799b 100644 --- a/src/terminal_helpers.rs +++ b/src/terminal_helpers.rs @@ -7,6 +7,9 @@ use nix::sys::termios; use nix::sys::termios::{ ControlFlags, InputFlags, LocalFlags, OutputFlags, SpecialCharacterIndices, Termios, }; +use std::io; +use std::io::prelude::*; +use std::io::BufReader; use std::os::unix::io::RawFd; use std::sync::Arc; @@ -25,11 +28,6 @@ pub fn get_termios(fd: RawFd) -> Termios { /// Put terminal into raw mode so there is full control of terminal output pub fn enable_raw_mode() { - // 'Access' the saved termios instance, to make sure it is set - // before you enable raw mode. - let mutex = Arc::clone(&super::ORIGINAL_TERMIOS); - mutex.lock().unwrap(); - let mut raw = get_termios(STDOUT_FILENO); raw.input_flags.remove( @@ -99,3 +97,67 @@ pub fn get_term_size() -> Option { None } } + +/// Get the terminal size, the hard way. +pub fn get_cursor_position() -> TermSize { + // Ask the shell where the cursor is + write!(io::stdout(), "\x1b[999C\x1b[999B").unwrap(); + write!(io::stdout(), "\x1b[6n").unwrap(); + + // Explicitly flush, so that next input should be the terminal response + io::stdout().flush().unwrap(); + + let mut buffer = vec![]; + + let stdin = io::stdin(); + let mut br = BufReader::new(stdin.lock()); + br.read_until('R' as u8, &mut buffer).unwrap(); + + let input = String::from_utf8(buffer).unwrap(); + + // Parse the escape sequence into a location + // The escape sequence looks like so: Esc[y;xR + let mut row_str = String::new(); + let mut col_str = String::new(); + let mut index = 0; + for ch in input.chars() { + if ch == '\x1b' || ch == '[' { + continue; + } + + match ch { + ';' => { + index += 1; + } + 'R' => break, + _ => { + if index == 0 { + row_str.push(ch) + } else { + col_str.push(ch) + } + } + } + } + + let rows = clean_unwrap(row_str.parse()); + let cols = clean_unwrap(col_str.parse()); + + return TermSize { cols, rows }; +} + +/// Do the equivalent of a Result::unwrap, but cleanup terminal output +/// first, so it doesn't destroy console output afterwards. +pub fn clean_unwrap(res: Result) -> O +where + E: std::fmt::Debug, +{ + match res { + Ok(value) => value, + Err(e) => { + print!("\x1bc"); + disable_raw_mode(); + panic!("{:?}", e); + } + } +}