Minor code cleanup, make get_cursor_position actually work correctly
This commit is contained in:
parent
f8700c8e93
commit
a94368c965
138
src/editor.rs
138
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<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');
|
||||
}
|
||||
}
|
||||
|
@ -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])?;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user