Create language syntax struct, and select highlighting by file extension

This commit is contained in:
Timothy Warren 2019-09-04 15:41:06 -04:00
parent 13eb9fd7b4
commit f25252a39e
5 changed files with 141 additions and 24 deletions

1
Cargo.lock generated
View File

@ -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)",
]

View File

@ -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"

View File

@ -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<EditorSyntax>,
// 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<T> 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<EditorSyntax> {
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]));
}
}

View File

@ -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;

View File

@ -1,3 +1,6 @@
#[macro_use]
extern crate bitflags;
mod editor;
mod helpers;