diff --git a/Cargo.lock b/Cargo.lock index 711e4a1..923f83d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -36,6 +36,7 @@ dependencies = [ name = "rs-kilo" version = "0.1.0" dependencies = [ + "bitflags 1.1.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)", ] diff --git a/Cargo.toml b/Cargo.toml index 91da894..4eaff0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +bitflags = "1.1.0" libc = "0.2" # Rust wrappers for C/POSIX headers nix = "0.15.0" diff --git a/src/editor.rs b/src/editor.rs index 64aa369..6de6809 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -10,9 +10,44 @@ use std::time::{Duration, Instant}; use self::EditorKey::*; +// ------------------------------------------------------------------------ +// Defines +// ------------------------------------------------------------------------ + const KILO_TAB_STOP: usize = 4; const KILO_QUIT_TIMES: u8 = 3; +// ------------------------------------------------------------------------ +// Data +// ------------------------------------------------------------------------ + +// Use an external package's macro to create a memory-safe +// bit flag alternative +bitflags! { + #[derive(Default)] + pub struct EditorSyntaxFlags: u32 { + const HIGHLIGHT_NUMBERS = 0b00000001; + const HIGHLIGHT_STRINGS = 0b00000010; + } +} + +#[derive(Clone, Debug, Default, PartialEq)] +pub struct EditorSyntax { + file_type: String, + file_match: Vec<&'static str>, + flags: EditorSyntaxFlags, +} + +impl EditorSyntax { + pub fn new(file_type: &str, file_match: Vec<&'static str>, flags: EditorSyntaxFlags) -> Self { + EditorSyntax { + file_type: String::from(file_type), + file_match, + flags, + } + } +} + #[derive(Copy, Clone, Debug, PartialEq)] pub enum Highlight { Normal, @@ -53,6 +88,7 @@ pub struct Editor { filename: String, status_message: String, status_message_time: Instant, + syntax: Option, // Properties not present in C version output_buffer: String, @@ -108,6 +144,7 @@ impl Default for Editor { filename: String::new(), status_message: String::new(), status_message_time: Instant::now(), + syntax: None, output_buffer: String::new(), quit_times: KILO_QUIT_TIMES, @@ -310,22 +347,19 @@ impl Editor { // Syntax Highlighting // ------------------------------------------------------------------------ - fn is_separator(c: char) -> bool { - let separator_chars = ",.()+-/*=~%<>[];"; - - for ch in separator_chars.chars() { - if c == ch { - return true; - } - } - - c.is_ascii_whitespace() || c == '\0' - } - fn update_syntax(&mut self, index: usize) { let row = &mut self.rows[index]; row.highlight = vec![Highlight::Normal; row.render.len()]; + if self.syntax.is_none() { + return; + } + + // This is dumb. This lets you get a reference to the item in + // the option, by turning Option into Option<&T>, + // which can then be unwrapped. + let current_syntax = self.syntax.as_ref().unwrap(); + let mut prev_separator = false; let mut i = 0; @@ -338,21 +372,26 @@ impl Editor { Highlight::Normal }; - if (c.is_ascii_digit() && (prev_separator || prev_highlight == Highlight::Number)) - || (c == '.' && prev_highlight == Highlight::Number) + if current_syntax + .flags + .contains(EditorSyntaxFlags::HIGHLIGHT_NUMBERS) { - row.highlight[i as usize] = Highlight::Number; - i += 1; - prev_separator = false; - continue; + if (c.is_ascii_digit() && (prev_separator || prev_highlight == Highlight::Number)) + || (c == '.' && prev_highlight == Highlight::Number) + { + row.highlight[i as usize] = Highlight::Number; + i += 1; + prev_separator = false; + continue; + } } - prev_separator = Self::is_separator(c); + prev_separator = is_separator(c); i += 1; } } - fn syntax_to_color(&self, syntax_type: Highlight) -> i32 { + fn syntax_to_color(syntax_type: Highlight) -> i32 { use Highlight::*; match syntax_type { @@ -362,6 +401,32 @@ impl Editor { } } + fn select_syntax_highlight(&mut self) { + if self.filename.is_empty() { + return; + } + + let mut parts: Vec<&str> = self.filename.split('.').collect(); + + let file_ext = String::from(".") + + match parts.pop() { + Some(ext) => ext, + None => return, + }; + + let languages = &get_syntax_db(); + + for language in languages { + let file_match = language.file_match.clone(); + for ext in file_match { + if ext == file_ext { + self.syntax = Some(language.clone()); + return; + } + } + } + } + // ------------------------------------------------------------------------ // Input // ------------------------------------------------------------------------ @@ -654,7 +719,7 @@ impl Editor { } self.append_out_char(ch); } else { - let color = self.syntax_to_color(self.rows[file_row].highlight[x]); + let color = Self::syntax_to_color(self.rows[file_row].highlight[x]); if color != current_color { current_color = color; let code = format!("\x1b[{}m", color); @@ -683,7 +748,11 @@ impl Editor { let modified = if self.dirty > 0 { "(modified}" } else { "" }; let mut left_message = format!("{:.80} - {} lines {}", filename, self.rows.len(), modified); - let right_message = format!("{}/{}", self.cursor_y + 1, self.rows.len()); + let file_type = match &self.syntax { + Some(s) => &s.file_type, + None => "no ft", + }; + let right_message = format!("{} | {}/{}", file_type, self.cursor_y + 1, self.rows.len()); let mut len = left_message.len(); if len > self.screen_cols { len = self.screen_cols; @@ -959,6 +1028,9 @@ impl Editor { /// Open a file for display pub fn open(&mut self, filename: &str) -> io::Result<()> { self.filename = filename.to_owned(); + + self.select_syntax_highlight(); + let file = File::open(&self.filename)?; let buf_reader = BufReader::new(file); @@ -980,6 +1052,7 @@ impl Editor { self.set_status_message("Save aborted"); return Ok(()); } + self.select_syntax_highlight(); } let mut file = File::create(&self.filename)?; @@ -1084,3 +1157,44 @@ impl Editor { } } } + +// ------------------------------------------------------------------------ +// Functions +// ------------------------------------------------------------------------ + +fn get_syntax_db() -> Vec { + vec![EditorSyntax::new( + "c", + vec![".c", ".h", ".cpp"], + EditorSyntaxFlags::HIGHLIGHT_NUMBERS, + )] +} + +fn is_separator(c: char) -> bool { + let separator_chars = ",.()+-/*=~%<>[];"; + + for ch in separator_chars.chars() { + if c == ch { + return true; + } + } + + c.is_ascii_whitespace() || c == '\0' +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn select_syntax_hightlight_selects_language() { + let langs = get_syntax_db(); + let mut editor = Editor::new(); + editor.filename = String::from("foo.c"); + + editor.select_syntax_highlight(); + + assert_eq!(editor.syntax.as_ref(), Some(&langs[0])); + } + +} diff --git a/src/helpers.rs b/src/helpers.rs index 40b7aa4..d85ade1 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -65,8 +65,6 @@ pub fn enable_raw_mode() { raw.local_flags .remove(LocalFlags::ECHO | LocalFlags::ICANON | LocalFlags::IEXTEN | LocalFlags::ISIG); - // raw.control_flags.remove(ControlFlags::CSIZE | ControlFlags::PARENB); - raw.control_chars[SpecialCharacterIndices::VMIN as usize] = 0; raw.control_chars[SpecialCharacterIndices::VTIME as usize] = 1; diff --git a/src/main.rs b/src/main.rs index 7d342e7..9583f14 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,6 @@ +#[macro_use] +extern crate bitflags; + mod editor; mod helpers;