From e6d3995e4cfb4e2e769938ae332399b5264536d8 Mon Sep 17 00:00:00 2001 From: Timothy Warren Date: Fri, 6 Sep 2019 13:24:29 -0400 Subject: [PATCH] Ugly progress commit --- Cargo.lock | 7 +++ Cargo.toml | 1 + README.md | 3 +- src/editor.rs | 83 +++++++++++++++++++------ src/main.rs | 37 ++++++----- src/{helpers.rs => terminal_helpers.rs} | 26 +++----- 6 files changed, 105 insertions(+), 52 deletions(-) rename src/{helpers.rs => terminal_helpers.rs} (82%) diff --git a/Cargo.lock b/Cargo.lock index 923f83d..82b0e03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,6 +15,11 @@ name = "cfg-if" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "libc" version = "0.2.62" @@ -37,6 +42,7 @@ name = "rs-kilo" version = "0.1.0" dependencies = [ "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", "nix 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -50,6 +56,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd" "checksum cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)" = "b548a4ee81fccb95919d4e22cfea83c7693ebfd78f0495493178db20b3139da7" "checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" +"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" "checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba" "checksum nix 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3b2e0b4f3320ed72aaedb9a5ac838690a8047c7b275da22711fddff4f8a14229" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" diff --git a/Cargo.toml b/Cargo.toml index 4eaff0c..00f9896 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ edition = "2018" [dependencies] bitflags = "1.1.0" +lazy_static = "1.4.0" libc = "0.2" # Rust wrappers for C/POSIX headers nix = "0.15.0" diff --git a/README.md b/README.md index bb3596b..c30a5b8 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,8 @@ tutorial with a Rust implementation. uses `impl`s on a shared `Editor` struct, the prefix is redundant * Any C equivalent functionality based on memory allocating/deallocating, or other manual book-keeping is instead implemented in a more idiomatic Rust fashion. - +* Row structs are referenced by their index in the Editor struct, rather than as a direct reference in method calls. +This generally simplifies dealing with the rules of Rust (borrow checker). ### Known issues: * The cursor is invisible :( \ No newline at end of file diff --git a/src/editor.rs b/src/editor.rs index 6ee5bc1..deeb303 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -1,5 +1,5 @@ //! Editor functionality -use crate::helpers::*; +use crate::terminal_helpers::*; use std::cmp::PartialEq; use std::fs::File; @@ -208,7 +208,12 @@ impl Default for Editor { dirty: 0, filename: String::new(), status_message: String::new(), + + // This is the only reason I had to implement this method + // manually, instead of it being derived. Apparently an + // `Instant` struct has no default status_message_time: Instant::now(), + syntax: None, output_buffer: String::new(), @@ -255,7 +260,8 @@ impl Editor { Ok(_) => (), Err(e) => { if e.kind() != io::ErrorKind::UnexpectedEof { - panic!(e); + let error = format!("{:?}", e); + self.set_status_message(&error); } } } @@ -289,7 +295,8 @@ impl Editor { Ok(_) => (), Err(e) => { if e.kind() != io::ErrorKind::UnexpectedEof { - panic!(e); + let error = format!("{:?}", e); + self.set_status_message(&error); } } } @@ -403,6 +410,10 @@ impl Editor { fn get_window_size(&mut self) -> TermSize { match get_term_size() { Some(size) => size, + + // I could have implemented this, but I felt that parsing + // an escape code from stdin was of minimal value, + // when the ioctrl method works on any computer I've tried None => unimplemented!("The easy way usually works"), } } @@ -413,19 +424,19 @@ impl Editor { fn update_syntax(&mut self, index: usize) { let rows = &mut self.rows; - let prev_row = if index > 0 { // I shouldn't have to clone this, but the lifetime is // different than the `row` variable above, so it // can't be a immutable borrow. It also can't be a // mutable borrow, because it would be considered a - // second mutable borrow + // second mutable borrow...so a clone it is Some((&mut rows[index - 1]).clone()) } else { None }; - let row = &mut rows[index]; + + // Reset the highlighting of the row row.highlight = vec![Highlight::Normal; row.render.len()]; if self.syntax.is_none() { @@ -444,7 +455,7 @@ impl Editor { let mcs = ¤t_syntax.multiline_comment_start; let mce = ¤t_syntax.multiline_comment_end; - let mut prev_separator = false; + let mut prev_separator = true; let mut in_string = false; let mut str_start = '\0'; let mut in_comment = prev_row.map_or(false, |row| row.highlight_comment_start); @@ -574,7 +585,6 @@ impl Editor { if &row.render[search_range.clone()] == keyword && is_separator(next_char) { highlight_range(&mut row.highlight, search_range, Highlight::Keyword1); i += keyword.len(); - break; } } @@ -592,7 +602,6 @@ impl Editor { if &row.render[search_range.clone()] == keyword && is_separator(next_char) { highlight_range(&mut row.highlight, search_range, Highlight::Keyword2); i += keyword.len(); - break; } } } @@ -927,16 +936,16 @@ impl Editor { // Center welcome message let mut padding = (self.screen_cols - welcome.len()) / 2; if padding > 0 { - self.append_out("~"); + self.append_out_char('~'); padding -= 1; } for _ in 0..padding { - self.append_out(" "); + self.append_out_char(' '); } self.append_out(&welcome); } else { - self.append_out("~"); + self.append_out_char('~'); } } else { let output = self.rows[file_row].render.clone(); @@ -1045,8 +1054,8 @@ impl Editor { self.draw_message_bar(); // Move cursor to state position - let y = (self.cursor_y - self.row_offset) + 1; - let x = (self.render_x - self.col_offset) + 1; + let y = self.cursor_y - self.row_offset + 1; + let x = self.render_x - self.col_offset + 1; let cursor_code = format!("\x1b[{y};{x}H", y = y, x = x); self.append_out(&cursor_code); @@ -1265,8 +1274,8 @@ impl Editor { for row in &self.rows { // When the file is opened, newlines are stripped // make sure to add them back when saving! - let row_chars = row.chars.clone() + "\n"; - output.push_str(&row_chars) + output += &row.chars; + output.push('\n'); } output @@ -1281,7 +1290,7 @@ impl Editor { let file = File::open(&self.filename)?; let buf_reader = BufReader::new(file); - let lines = buf_reader.lines().map(|l| l.unwrap()); + let lines = buf_reader.lines().map(|l| clean_unwrap(l)); for line in lines { self.insert_row(self.rows.len(), &line); @@ -1299,6 +1308,7 @@ impl Editor { self.set_status_message("Save aborted"); return Ok(()); } + self.select_syntax_highlight(); } @@ -1411,6 +1421,7 @@ impl Editor { // Functions // ------------------------------------------------------------------------ +/// Get the language highlighting config fn get_syntax_db() -> Vec { vec![ Syntax::new( @@ -1421,7 +1432,8 @@ fn get_syntax_db() -> Vec { "union", "class", "else", "enum", "for", "case", "if", ], vec![ - "unsigned", "double", "signed", "float", "long", "char", "int", "void", + "#include", "unsigned", "#define", "#ifndef", "double", "signed", "#endif", + "#ifdef", "float", "#error", "#undef", "long", "char", "int", "void", "#if", ], "//", "/*", @@ -1504,6 +1516,20 @@ fn get_syntax_db() -> Vec { ] } +/// Convert Ctrl+letter chords to their +/// ASCII table equivalents +pub fn ctrl_key(c: char) -> char { + let key = c as u8; + + if !c.is_ascii() { + panic!("CTRL_KEY only accepts ASCII characters"); + } + + // Intentionally "overflow" u8 to wrap around to the + // beginning of the ASCII table. Ctrl+a is 1, Ctrl+b is 2, etc. + (key & 0x1f) as char +} + /// Determine whether a character is one which separates tokens /// in the language to highlight fn is_separator(input_char: char) -> bool { @@ -1522,7 +1548,8 @@ fn is_separator(input_char: char) -> bool { false } -/// Get a range for a slice of a string or vector. +/// Get a range for a slice of a string or vector, checking the length of the +/// string or vector to prevent panics on invalid access. /// /// If `start` to `start + search_len`, is within the size of the search target (`haystack_len`) /// that range is returned. Otherwise, the range is from `start` to `haystack_len`. @@ -1543,12 +1570,28 @@ 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) => { + disable_raw_mode(); + println!("\r\n"); + panic!("{:?}", e); + } + } +} + #[cfg(test)] mod tests { use super::*; #[test] - fn select_syntax_hightlight_selects_language() { + fn select_syntax_highlight_selects_language() { let langs = get_syntax_db(); let mut editor = Editor::new(); editor.filename = String::from("foo.c"); diff --git a/src/main.rs b/src/main.rs index 9583f14..2fe4c9d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,22 +1,30 @@ #[macro_use] extern crate bitflags; +#[macro_use] +extern crate lazy_static; + mod editor; -mod helpers; +mod terminal_helpers; use crate::editor::Editor; -use crate::helpers::*; +use crate::terminal_helpers::*; use libc::STDIN_FILENO; +use nix::sys::termios::Termios; use std::env; use std::io::Error; use std::result::Result; +use std::sync::{Arc, Mutex}; + +// Much ugliness to get and keep a reference to the original terminal settings +lazy_static! { + // Save terminal flags on start + pub static ref ORIGINAL_TERMIOS: Arc> = Arc::new(Mutex::new(get_termios(STDIN_FILENO))); +} fn main() -> Result<(), Error> { let args: Vec = env::args().collect(); - // Save original terminal flags - let original_termios = get_termios(STDIN_FILENO); - // Disable canonical/"cooked" terminal mode enable_raw_mode(); @@ -36,19 +44,20 @@ fn main() -> Result<(), Error> { loop { editor.refresh_screen(); - let key = editor.process_keypress(); - if key.is_none() { - break; + match editor.process_keypress() { + Some(_key) => { + /* match key { + editor::EditorKey::OtherKey('\0') => (), + _ => println!("{:?}\r\n", key) + }*/ + () + } + None => break, } - - /*match key.unwrap() { - editor::EditorKey::OtherKey('\0') => (), - _ => println!("{:?}\r\n", key) - } */ } // Restore previous terminal flags before exit - disable_raw_mode(&original_termios); + disable_raw_mode(); Ok(()) } diff --git a/src/helpers.rs b/src/terminal_helpers.rs similarity index 82% rename from src/helpers.rs rename to src/terminal_helpers.rs index d85ade1..7495fde 100644 --- a/src/helpers.rs +++ b/src/terminal_helpers.rs @@ -1,3 +1,6 @@ +//! # Helpers +//! +//! Various functions calling C wrappers to get/set terminal functionality use libc::ioctl; use libc::{c_ushort, STDIN_FILENO, STDOUT_FILENO, TIOCGWINSZ}; use nix::sys::termios; @@ -5,6 +8,7 @@ use nix::sys::termios::{ ControlFlags, InputFlags, LocalFlags, OutputFlags, SpecialCharacterIndices, Termios, }; use std::os::unix::io::RawFd; +use std::sync::Arc; #[derive(Debug)] pub struct TermSize { @@ -25,21 +29,6 @@ struct UnixTermSize { y: c_ushort, } -/// Convert Ctrl+letter chords to their -/// ASCII table equivalents -#[inline] -pub fn ctrl_key(c: char) -> char { - let key = c as u8; - - if !c.is_ascii() { - panic!("CTRL_KEY only accepts ASCII characters"); - } - - // Intentionally "overflow" u8 to wrap around to the - // beginning of the ASCII table. Ctrl+a is 1, Ctrl+b is 2, etc. - (key & 0x1f) as char -} - /// Get a `Termios` struct, for getting/setting terminal flags pub fn get_termios(fd: RawFd) -> Termios { termios::tcgetattr(fd).unwrap() @@ -73,8 +62,11 @@ pub fn enable_raw_mode() { } /// Restore terminal to "cooked"/canonical mode -pub fn disable_raw_mode(original: &Termios) { - termios::tcsetattr(STDIN_FILENO, termios::SetArg::TCSAFLUSH, original).unwrap(); +pub fn disable_raw_mode() { + let mutex = Arc::clone(&super::ORIGINAL_TERMIOS); + let termios = mutex.lock().unwrap(); + + termios::tcsetattr(STDIN_FILENO, termios::SetArg::TCSAFLUSH, &termios).unwrap(); } /// Attempt to get the size of the terminal (in rows and columns) from an `ioctl` call