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),
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<Syntax> {
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<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)]
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');
}
}

View File

@ -23,7 +23,10 @@ lazy_static! {
}
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
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<String> = env::args().collect();
if args.len() >= 2 {
editor.open(&args[1])?;
}

View File

@ -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<TermSize> {
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);
}
}
}