Minor code cleanup, make get_cursor_position actually work correctly

This commit is contained in:
Timothy Warren 2019-09-19 12:19:20 -04:00
parent f8700c8e93
commit a94368c965
3 changed files with 108 additions and 108 deletions

View File

@ -287,11 +287,11 @@ impl Editor {
'\r' => return Some(Enter), '\r' => return Some(Enter),
ch => { ch => {
if ch.is_ascii_control() { 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, None => return None,
} }
@ -417,67 +417,11 @@ impl Editor {
return Some(input[0]); return Some(input[0]);
} }
fn get_cursor_position(&mut self) -> TermSize { /// Get terminal size in rows and columns
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 };
}
fn get_window_size(&mut self) -> TermSize { fn get_window_size(&mut self) -> TermSize {
match get_term_size() { match get_term_size() {
Some(size) => size, Some(size) => size,
None => get_cursor_position(),
None => {
print!("\x1b[999C\x1b[999B");
return self.get_cursor_position();
}
} }
} }
@ -861,30 +805,27 @@ impl Editor {
self.cursor_x = self.rows[self.cursor_y].chars.len(); self.cursor_x = self.rows[self.cursor_y].chars.len();
} }
} }
Ctrl(c) => { Ctrl(c) => match c {
match c { 'f' => self.find(),
'f' => self.find(), 's' => {
// 'h' => self._del_or_backspace(Backspace), // Save success/error message handled by save method
's' => { match self.save() {
// Save success/error message handled by save method Ok(_) => (),
match self.save() { Err(_) => (),
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(_) => (), Function(_) => (),
OtherKey(c) => { OtherKey(c) => {
@ -1150,6 +1091,7 @@ impl Editor {
// Row Operations // Row Operations
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
/// Convert cursor x position to the rendered x position
fn row_cx_to_rx(&mut self, index: usize, cx: usize) -> usize { fn row_cx_to_rx(&mut self, index: usize, cx: usize) -> usize {
let mut rx: usize = 0; let mut rx: usize = 0;
@ -1168,6 +1110,7 @@ impl Editor {
rx rx
} }
/// Convert rendered x position to cursor x position
fn row_rx_to_cx(&mut self, index: usize, rx: usize) -> usize { fn row_rx_to_cx(&mut self, index: usize, rx: usize) -> usize {
let mut current_rx: usize = 0; let mut current_rx: usize = 0;
let mut cx: usize = 0; let mut cx: usize = 0;
@ -1569,7 +1512,7 @@ fn get_syntax_db() -> Vec<Syntax> {
SyntaxFlags::HIGHLIGHT_NUMBERS | SyntaxFlags::HIGHLIGHT_STRINGS, SyntaxFlags::HIGHLIGHT_NUMBERS | SyntaxFlags::HIGHLIGHT_STRINGS,
), ),
Syntax::new( Syntax::new(
"JavaScript", "JavaScript/TypeScript",
vec![".js", ".mjs", ".jsx", ".ts", ".tsx"], vec![".js", ".mjs", ".jsx", ".ts", ".tsx"],
vec![ vec![
"instanceof", "instanceof",
@ -1672,22 +1615,6 @@ fn highlight_range(vec: &mut Vec<Highlight>, range: Range<usize>, 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<O, E>(res: Result<O, E>) -> O
where
E: std::fmt::Debug,
{
match res {
Ok(value) => value,
Err(e) => {
print!("\x1bc");
disable_raw_mode();
panic!("{:?}", e);
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -1727,8 +1654,15 @@ mod tests {
} }
#[test] #[test]
fn ctrl_key_functions() { fn ctrl_to_letter_() {
let a = ctrl_to_letter(ctrl_a); let a = ctrl_to_letter('\x01');
assert_eq!(a, 'a', "ctrl_to_letter gives letter from ctrl chord"); 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');
}
} }

View File

@ -23,7 +23,10 @@ lazy_static! {
} }
fn main() -> Result<(), Error> { fn main() -> Result<(), Error> {
let args: Vec<String> = 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 // Disable canonical/"cooked" terminal mode
enable_raw_mode(); enable_raw_mode();
@ -32,6 +35,7 @@ fn main() -> Result<(), Error> {
let mut editor = Editor::new(); let mut editor = Editor::new();
// Open the file if specified, from the command line // Open the file if specified, from the command line
let args: Vec<String> = env::args().collect();
if args.len() >= 2 { if args.len() >= 2 {
editor.open(&args[1])?; editor.open(&args[1])?;
} }

View File

@ -7,6 +7,9 @@ use nix::sys::termios;
use nix::sys::termios::{ use nix::sys::termios::{
ControlFlags, InputFlags, LocalFlags, OutputFlags, SpecialCharacterIndices, 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::os::unix::io::RawFd;
use std::sync::Arc; 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 /// Put terminal into raw mode so there is full control of terminal output
pub fn enable_raw_mode() { 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); let mut raw = get_termios(STDOUT_FILENO);
raw.input_flags.remove( raw.input_flags.remove(
@ -99,3 +97,67 @@ pub fn get_term_size() -> Option<TermSize> {
None 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<O, E>(res: Result<O, E>) -> O
where
E: std::fmt::Debug,
{
match res {
Ok(value) => value,
Err(e) => {
print!("\x1bc");
disable_raw_mode();
panic!("{:?}", e);
}
}
}