Create language syntax struct, and select highlighting by file extension
This commit is contained in:
parent
13eb9fd7b4
commit
f25252a39e
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -36,6 +36,7 @@ dependencies = [
|
|||||||
name = "rs-kilo"
|
name = "rs-kilo"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
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)",
|
"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)",
|
"nix 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
@ -7,6 +7,7 @@ edition = "2018"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
bitflags = "1.1.0"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
# Rust wrappers for C/POSIX headers
|
# Rust wrappers for C/POSIX headers
|
||||||
nix = "0.15.0"
|
nix = "0.15.0"
|
||||||
|
146
src/editor.rs
146
src/editor.rs
@ -10,9 +10,44 @@ use std::time::{Duration, Instant};
|
|||||||
|
|
||||||
use self::EditorKey::*;
|
use self::EditorKey::*;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// Defines
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
const KILO_TAB_STOP: usize = 4;
|
const KILO_TAB_STOP: usize = 4;
|
||||||
const KILO_QUIT_TIMES: u8 = 3;
|
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)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
pub enum Highlight {
|
pub enum Highlight {
|
||||||
Normal,
|
Normal,
|
||||||
@ -53,6 +88,7 @@ pub struct Editor {
|
|||||||
filename: String,
|
filename: String,
|
||||||
status_message: String,
|
status_message: String,
|
||||||
status_message_time: Instant,
|
status_message_time: Instant,
|
||||||
|
syntax: Option<EditorSyntax>,
|
||||||
|
|
||||||
// Properties not present in C version
|
// Properties not present in C version
|
||||||
output_buffer: String,
|
output_buffer: String,
|
||||||
@ -108,6 +144,7 @@ impl Default for Editor {
|
|||||||
filename: String::new(),
|
filename: String::new(),
|
||||||
status_message: String::new(),
|
status_message: String::new(),
|
||||||
status_message_time: Instant::now(),
|
status_message_time: Instant::now(),
|
||||||
|
syntax: None,
|
||||||
|
|
||||||
output_buffer: String::new(),
|
output_buffer: String::new(),
|
||||||
quit_times: KILO_QUIT_TIMES,
|
quit_times: KILO_QUIT_TIMES,
|
||||||
@ -310,22 +347,19 @@ impl Editor {
|
|||||||
// Syntax Highlighting
|
// 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) {
|
fn update_syntax(&mut self, index: usize) {
|
||||||
let row = &mut self.rows[index];
|
let row = &mut self.rows[index];
|
||||||
row.highlight = vec![Highlight::Normal; row.render.len()];
|
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 prev_separator = false;
|
||||||
|
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
@ -338,6 +372,10 @@ impl Editor {
|
|||||||
Highlight::Normal
|
Highlight::Normal
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if current_syntax
|
||||||
|
.flags
|
||||||
|
.contains(EditorSyntaxFlags::HIGHLIGHT_NUMBERS)
|
||||||
|
{
|
||||||
if (c.is_ascii_digit() && (prev_separator || prev_highlight == Highlight::Number))
|
if (c.is_ascii_digit() && (prev_separator || prev_highlight == Highlight::Number))
|
||||||
|| (c == '.' && prev_highlight == Highlight::Number)
|
|| (c == '.' && prev_highlight == Highlight::Number)
|
||||||
{
|
{
|
||||||
@ -346,13 +384,14 @@ impl Editor {
|
|||||||
prev_separator = false;
|
prev_separator = false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
prev_separator = Self::is_separator(c);
|
prev_separator = is_separator(c);
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn syntax_to_color(&self, syntax_type: Highlight) -> i32 {
|
fn syntax_to_color(syntax_type: Highlight) -> i32 {
|
||||||
use Highlight::*;
|
use Highlight::*;
|
||||||
|
|
||||||
match syntax_type {
|
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
|
// Input
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
@ -654,7 +719,7 @@ impl Editor {
|
|||||||
}
|
}
|
||||||
self.append_out_char(ch);
|
self.append_out_char(ch);
|
||||||
} else {
|
} 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 {
|
if color != current_color {
|
||||||
current_color = color;
|
current_color = color;
|
||||||
let code = format!("\x1b[{}m", color);
|
let code = format!("\x1b[{}m", color);
|
||||||
@ -683,7 +748,11 @@ impl Editor {
|
|||||||
let modified = if self.dirty > 0 { "(modified}" } else { "" };
|
let modified = if self.dirty > 0 { "(modified}" } else { "" };
|
||||||
|
|
||||||
let mut left_message = format!("{:.80} - {} lines {}", filename, self.rows.len(), modified);
|
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();
|
let mut len = left_message.len();
|
||||||
if len > self.screen_cols {
|
if len > self.screen_cols {
|
||||||
len = self.screen_cols;
|
len = self.screen_cols;
|
||||||
@ -959,6 +1028,9 @@ impl Editor {
|
|||||||
/// Open a file for display
|
/// Open a file for display
|
||||||
pub fn open(&mut self, filename: &str) -> io::Result<()> {
|
pub fn open(&mut self, filename: &str) -> io::Result<()> {
|
||||||
self.filename = filename.to_owned();
|
self.filename = filename.to_owned();
|
||||||
|
|
||||||
|
self.select_syntax_highlight();
|
||||||
|
|
||||||
let file = File::open(&self.filename)?;
|
let file = File::open(&self.filename)?;
|
||||||
let buf_reader = BufReader::new(file);
|
let buf_reader = BufReader::new(file);
|
||||||
|
|
||||||
@ -980,6 +1052,7 @@ impl Editor {
|
|||||||
self.set_status_message("Save aborted");
|
self.set_status_message("Save aborted");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
self.select_syntax_highlight();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut file = File::create(&self.filename)?;
|
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]));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -65,8 +65,6 @@ pub fn enable_raw_mode() {
|
|||||||
raw.local_flags
|
raw.local_flags
|
||||||
.remove(LocalFlags::ECHO | LocalFlags::ICANON | LocalFlags::IEXTEN | LocalFlags::ISIG);
|
.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::VMIN as usize] = 0;
|
||||||
raw.control_chars[SpecialCharacterIndices::VTIME as usize] = 1;
|
raw.control_chars[SpecialCharacterIndices::VTIME as usize] = 1;
|
||||||
|
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
#[macro_use]
|
||||||
|
extern crate bitflags;
|
||||||
|
|
||||||
mod editor;
|
mod editor;
|
||||||
mod helpers;
|
mod helpers;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user