Finish tutorial, much refactoring
This commit is contained in:
parent
ca51d8f1f5
commit
389a526f41
445
src/editor.rs
445
src/editor.rs
@ -6,9 +6,10 @@ use std::fs::File;
|
|||||||
use std::io;
|
use std::io;
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
|
use std::ops::Range;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use self::EditorKey::*;
|
use self::KeyCode::*;
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// Defines
|
// Defines
|
||||||
@ -25,75 +26,82 @@ const KILO_QUIT_TIMES: u8 = 3;
|
|||||||
// bit flag alternative
|
// bit flag alternative
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct EditorSyntaxFlags: u32 {
|
pub struct SyntaxFlags: u32 {
|
||||||
const HIGHLIGHT_NUMBERS = 0b00000001;
|
const HIGHLIGHT_NUMBERS = 0b00000001;
|
||||||
const HIGHLIGHT_STRINGS = 0b00000010;
|
const HIGHLIGHT_STRINGS = 0b00000010;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Configuration for language syntax highlighting
|
||||||
#[derive(Clone, Debug, Default, PartialEq)]
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
pub struct EditorSyntax {
|
pub struct Syntax {
|
||||||
|
/// Language name, to be shown in status bar
|
||||||
file_type: String,
|
file_type: String,
|
||||||
|
|
||||||
|
/// File extensions
|
||||||
file_match: Vec<&'static str>,
|
file_match: Vec<&'static str>,
|
||||||
|
|
||||||
|
/// Keywords
|
||||||
keywords1: Vec<&'static str>,
|
keywords1: Vec<&'static str>,
|
||||||
|
|
||||||
|
/// Type and/or secondary keywords
|
||||||
keywords2: Vec<&'static str>,
|
keywords2: Vec<&'static str>,
|
||||||
|
|
||||||
|
/// How does a single line comment start?
|
||||||
singleline_comment_start: String,
|
singleline_comment_start: String,
|
||||||
|
|
||||||
|
/// How does a multline comment start?
|
||||||
multiline_comment_start: String,
|
multiline_comment_start: String,
|
||||||
|
|
||||||
|
/// How does a multiline comment end?
|
||||||
multiline_comment_end: String,
|
multiline_comment_end: String,
|
||||||
flags: EditorSyntaxFlags,
|
|
||||||
}
|
/// Options for what to highlight
|
||||||
|
flags: SyntaxFlags,
|
||||||
impl EditorSyntax {
|
|
||||||
pub fn new(
|
|
||||||
file_type: &str,
|
|
||||||
file_match: Vec<&'static str>,
|
|
||||||
keywords1: Vec<&'static str>,
|
|
||||||
keywords2: Vec<&'static str>,
|
|
||||||
single_line_comment_start: &str,
|
|
||||||
multi_line_comment_start: &str,
|
|
||||||
multi_line_comment_end: &str,
|
|
||||||
flags: EditorSyntaxFlags,
|
|
||||||
) -> Self {
|
|
||||||
EditorSyntax {
|
|
||||||
file_type: file_type.to_owned(),
|
|
||||||
file_match,
|
|
||||||
keywords1,
|
|
||||||
keywords2,
|
|
||||||
singleline_comment_start: single_line_comment_start.to_owned(),
|
|
||||||
multiline_comment_start: multi_line_comment_start.to_owned(),
|
|
||||||
multiline_comment_end: multi_line_comment_end.to_owned(),
|
|
||||||
flags,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Syntax highlighting token types
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
pub enum Highlight {
|
pub enum Highlight {
|
||||||
|
/// No highlighting
|
||||||
Normal,
|
Normal,
|
||||||
|
|
||||||
|
/// Single line comments
|
||||||
LineComment,
|
LineComment,
|
||||||
|
|
||||||
|
/// Multiple line comments
|
||||||
MultiLineComment,
|
MultiLineComment,
|
||||||
|
|
||||||
|
/// Language keywords
|
||||||
Keyword1,
|
Keyword1,
|
||||||
|
|
||||||
|
/// Language types/ secondary keywords
|
||||||
Keyword2,
|
Keyword2,
|
||||||
|
|
||||||
|
/// Single-line strings
|
||||||
String,
|
String,
|
||||||
|
|
||||||
|
/// Numbers
|
||||||
Number,
|
Number,
|
||||||
|
|
||||||
|
/// Search results
|
||||||
SearchMatch,
|
SearchMatch,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A representation of a line in the editor
|
/// A representation of a line in the editor
|
||||||
#[derive(Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct EditorRow {
|
pub struct Row {
|
||||||
|
/// The 'raw' representation of the original characters
|
||||||
chars: String,
|
chars: String,
|
||||||
|
|
||||||
|
/// The display characters for the editor
|
||||||
render: String,
|
render: String,
|
||||||
|
|
||||||
|
/// The highlighting type for each character
|
||||||
highlight: Vec<Highlight>,
|
highlight: Vec<Highlight>,
|
||||||
}
|
|
||||||
|
|
||||||
impl EditorRow {
|
/// Are we currently highlighting a multi-line comment?
|
||||||
pub fn new(chars: &str) -> Self {
|
highlight_comment_start: bool,
|
||||||
let mut instance = EditorRow::default();
|
|
||||||
instance.chars = chars.to_owned();
|
|
||||||
|
|
||||||
instance
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Main structure for the editor
|
/// Main structure for the editor
|
||||||
@ -107,12 +115,12 @@ pub struct Editor {
|
|||||||
row_offset: usize,
|
row_offset: usize,
|
||||||
screen_cols: usize,
|
screen_cols: usize,
|
||||||
screen_rows: usize,
|
screen_rows: usize,
|
||||||
rows: Vec<EditorRow>,
|
rows: Vec<Row>,
|
||||||
dirty: u64,
|
dirty: u64,
|
||||||
filename: String,
|
filename: String,
|
||||||
status_message: String,
|
status_message: String,
|
||||||
status_message_time: Instant,
|
status_message_time: Instant,
|
||||||
syntax: Option<EditorSyntax>,
|
syntax: Option<Syntax>,
|
||||||
|
|
||||||
// Properties not present in C version
|
// Properties not present in C version
|
||||||
output_buffer: String,
|
output_buffer: String,
|
||||||
@ -125,7 +133,7 @@ pub struct Editor {
|
|||||||
|
|
||||||
/// Keycode mapping enum
|
/// Keycode mapping enum
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
pub enum EditorKey<T> {
|
pub enum KeyCode<T> {
|
||||||
Enter,
|
Enter,
|
||||||
Escape,
|
Escape,
|
||||||
Backspace,
|
Backspace,
|
||||||
@ -144,11 +152,44 @@ pub enum EditorKey<T> {
|
|||||||
OtherKey(T),
|
OtherKey(T),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EditorKey<char> {
|
impl Syntax {
|
||||||
|
pub fn new(
|
||||||
|
file_type: &str,
|
||||||
|
file_match: Vec<&'static str>,
|
||||||
|
keywords1: Vec<&'static str>,
|
||||||
|
keywords2: Vec<&'static str>,
|
||||||
|
single_line_comment_start: &str,
|
||||||
|
multi_line_comment_start: &str,
|
||||||
|
multi_line_comment_end: &str,
|
||||||
|
flags: SyntaxFlags,
|
||||||
|
) -> Self {
|
||||||
|
Syntax {
|
||||||
|
file_type: file_type.to_owned(),
|
||||||
|
file_match,
|
||||||
|
keywords1,
|
||||||
|
keywords2,
|
||||||
|
singleline_comment_start: single_line_comment_start.to_owned(),
|
||||||
|
multiline_comment_start: multi_line_comment_start.to_owned(),
|
||||||
|
multiline_comment_end: multi_line_comment_end.to_owned(),
|
||||||
|
flags,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Row {
|
||||||
|
pub fn new(chars: &str) -> Self {
|
||||||
|
let mut instance = Row::default();
|
||||||
|
instance.chars = chars.to_owned();
|
||||||
|
|
||||||
|
instance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyCode<char> {
|
||||||
pub fn unwrap(self) -> char {
|
pub fn unwrap(self) -> char {
|
||||||
match self {
|
match self {
|
||||||
self::OtherKey(val) => val,
|
self::OtherKey(val) => val,
|
||||||
_ => panic!("called `EditorKey::unwrap()` on a `None` value"),
|
_ => panic!("called `KeyCode::unwrap()` on a `None` value"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -201,7 +242,7 @@ impl Editor {
|
|||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
/// Convert stdin to specific keypresses
|
/// Convert stdin to specific keypresses
|
||||||
fn read_key(&mut self) -> Option<EditorKey<char>> {
|
fn read_key(&mut self) -> Option<KeyCode<char>> {
|
||||||
// --------------------------------------------------------------------
|
// --------------------------------------------------------------------
|
||||||
// Match single character
|
// Match single character
|
||||||
// --------------------------------------------------------------------
|
// --------------------------------------------------------------------
|
||||||
@ -225,19 +266,18 @@ impl Editor {
|
|||||||
let first_str = first_str.unwrap();
|
let first_str = first_str.unwrap();
|
||||||
|
|
||||||
// Read the first character, if it isn't escape, just return it
|
// Read the first character, if it isn't escape, just return it
|
||||||
let mut chars = first_str.chars();
|
let mut chs = first_str.chars();
|
||||||
let char = chars.next();
|
let ch = chs.next();
|
||||||
if char.is_none() {
|
match ch {
|
||||||
return None;
|
Some(ch) => match ch {
|
||||||
}
|
|
||||||
let char = char.unwrap();
|
|
||||||
match char {
|
|
||||||
'\0' => return None,
|
'\0' => return None,
|
||||||
'\x1b' => (),
|
'\x1b' => (),
|
||||||
'\x08' => return Some(Backspace),
|
'\x08' => return Some(Backspace),
|
||||||
'\x7f' => return Some(Backspace),
|
'\x7f' => return Some(Backspace),
|
||||||
'\r' => return Some(Enter),
|
'\r' => return Some(Enter),
|
||||||
c => return Some(OtherKey(c)),
|
_ => return Some(OtherKey(ch)),
|
||||||
|
},
|
||||||
|
None => return None,
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------------------------
|
// --------------------------------------------------------------------
|
||||||
@ -261,18 +301,18 @@ impl Editor {
|
|||||||
}
|
}
|
||||||
let seq_str = seq_str.unwrap();
|
let seq_str = seq_str.unwrap();
|
||||||
|
|
||||||
let mut input: Vec<EditorKey<char>> = vec![];
|
let mut input: Vec<KeyCode<char>> = vec![];
|
||||||
|
|
||||||
for char in seq_str.chars() {
|
for ch in seq_str.chars() {
|
||||||
// Since the fixed array is always filled, there
|
// Since the fixed array is always filled, there
|
||||||
// will be null characters. Ignore these.
|
// will be null characters. Ignore these.
|
||||||
if char == '\0' {
|
if ch == '\0' {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
input.push(match char {
|
input.push(match ch {
|
||||||
'\x1b' => Escape,
|
'\x1b' => Escape,
|
||||||
_ => OtherKey(char),
|
_ => OtherKey(ch),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -372,7 +412,20 @@ impl Editor {
|
|||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
fn update_syntax(&mut self, index: usize) {
|
fn update_syntax(&mut self, index: usize) {
|
||||||
let row = &mut self.rows[index];
|
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
|
||||||
|
Some((&mut rows[index - 1]).clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let row = &mut rows[index];
|
||||||
row.highlight = vec![Highlight::Normal; row.render.len()];
|
row.highlight = vec![Highlight::Normal; row.render.len()];
|
||||||
|
|
||||||
if self.syntax.is_none() {
|
if self.syntax.is_none() {
|
||||||
@ -394,7 +447,7 @@ impl Editor {
|
|||||||
let mut prev_separator = false;
|
let mut prev_separator = false;
|
||||||
let mut in_string = false;
|
let mut in_string = false;
|
||||||
let mut str_start = '\0';
|
let mut str_start = '\0';
|
||||||
let mut in_comment = false;
|
let mut in_comment = prev_row.map_or(false, |row| row.highlight_comment_start);
|
||||||
|
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
let bytes = row.render.clone().into_bytes();
|
let bytes = row.render.clone().into_bytes();
|
||||||
@ -407,37 +460,33 @@ impl Editor {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Single line comments
|
// Single line comments
|
||||||
if scs.len() > 0 && !in_string {
|
if scs.len() > 0 && !in_string && !in_comment {
|
||||||
let comment = row.render.find(scs);
|
let range = get_slice_range(i as usize, scs.len(), row.render.len());
|
||||||
if comment.is_some() {
|
if &row.render[range] == scs {
|
||||||
// Pretty simple, highlight from the match to the end of the line
|
// Pretty simple, highlight from the match to the end of the line
|
||||||
let comment_start = comment.unwrap();
|
highlight_range(
|
||||||
for x in comment_start..row.render.len() {
|
&mut row.highlight,
|
||||||
row.highlight[x] = Highlight::LineComment;
|
i as usize..row.render.len(),
|
||||||
}
|
Highlight::LineComment,
|
||||||
|
);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Multi-line comments
|
// Multi-line comments
|
||||||
if mcs.len() > 0 && mce.len() > 0 && !in_string {
|
if mcs.len() > 0 && mce.len() > 0 && !in_string {
|
||||||
let mce_slice_range = if i as usize + mce.len() >= row.render.len() {
|
let mce_slice_range = get_slice_range(i as usize, mce.len(), row.render.len());
|
||||||
i as usize..row.render.len()
|
let mcs_slice_range = get_slice_range(i as usize, mcs.len(), row.render.len());
|
||||||
} else {
|
|
||||||
i as usize..(i as usize + mce.len())
|
|
||||||
};
|
|
||||||
let mcs_slice_range = if i as usize + mcs.len() >= row.render.len() {
|
|
||||||
i as usize..row.render.len()
|
|
||||||
} else {
|
|
||||||
i as usize..(i as usize + mcs.len())
|
|
||||||
};
|
|
||||||
if in_comment {
|
if in_comment {
|
||||||
row.highlight[i as usize] = Highlight::MultiLineComment;
|
row.highlight[i as usize] = Highlight::MultiLineComment;
|
||||||
|
|
||||||
// End of a comment
|
// End of a comment
|
||||||
if &row.render[mce_slice_range.clone()] == mce {
|
if &row.render[mce_slice_range.clone()] == mce {
|
||||||
for x in mce_slice_range {
|
highlight_range(
|
||||||
row.highlight[x] = Highlight::MultiLineComment;
|
&mut row.highlight,
|
||||||
}
|
mce_slice_range,
|
||||||
|
Highlight::MultiLineComment,
|
||||||
|
);
|
||||||
|
|
||||||
i += mce.len();
|
i += mce.len();
|
||||||
in_comment = false;
|
in_comment = false;
|
||||||
@ -449,9 +498,11 @@ impl Editor {
|
|||||||
}
|
}
|
||||||
} else if &row.render[mcs_slice_range.clone()] == mcs {
|
} else if &row.render[mcs_slice_range.clone()] == mcs {
|
||||||
// Start of a multi-line comment
|
// Start of a multi-line comment
|
||||||
for x in mcs_slice_range {
|
highlight_range(
|
||||||
row.highlight[x] = Highlight::MultiLineComment;
|
&mut row.highlight,
|
||||||
}
|
mcs_slice_range,
|
||||||
|
Highlight::MultiLineComment,
|
||||||
|
);
|
||||||
|
|
||||||
i += mcs.len();
|
i += mcs.len();
|
||||||
in_comment = true;
|
in_comment = true;
|
||||||
@ -462,7 +513,7 @@ impl Editor {
|
|||||||
// Strings
|
// Strings
|
||||||
if current_syntax
|
if current_syntax
|
||||||
.flags
|
.flags
|
||||||
.contains(EditorSyntaxFlags::HIGHLIGHT_STRINGS)
|
.contains(SyntaxFlags::HIGHLIGHT_STRINGS)
|
||||||
{
|
{
|
||||||
if in_string {
|
if in_string {
|
||||||
row.highlight[i as usize] = Highlight::String;
|
row.highlight[i as usize] = Highlight::String;
|
||||||
@ -495,7 +546,7 @@ impl Editor {
|
|||||||
// Numbers
|
// Numbers
|
||||||
if current_syntax
|
if current_syntax
|
||||||
.flags
|
.flags
|
||||||
.contains(EditorSyntaxFlags::HIGHLIGHT_NUMBERS)
|
.contains(SyntaxFlags::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)
|
||||||
@ -510,9 +561,9 @@ impl Editor {
|
|||||||
// Keywords
|
// Keywords
|
||||||
if prev_separator {
|
if prev_separator {
|
||||||
for &keyword in keywords1 {
|
for &keyword in keywords1 {
|
||||||
let matches = row.render.match_indices(keyword);
|
let search_range = get_slice_range(i as usize, keyword.len(), row.render.len());
|
||||||
for (start, _) in matches {
|
|
||||||
let next_char_offset = start + keyword.len() + 1;
|
let next_char_offset = i as usize + keyword.len() + 1;
|
||||||
let is_end_of_line = next_char_offset >= row.render.len();
|
let is_end_of_line = next_char_offset >= row.render.len();
|
||||||
let next_char = if is_end_of_line {
|
let next_char = if is_end_of_line {
|
||||||
'\0'
|
'\0'
|
||||||
@ -520,22 +571,17 @@ impl Editor {
|
|||||||
bytes[next_char_offset] as char
|
bytes[next_char_offset] as char
|
||||||
};
|
};
|
||||||
|
|
||||||
if is_separator(next_char) {
|
if &row.render[search_range.clone()] == keyword && is_separator(next_char) {
|
||||||
let end = start + keyword.len();
|
highlight_range(&mut row.highlight, search_range, Highlight::Keyword1);
|
||||||
for x in start..end {
|
|
||||||
row.highlight[x] = Highlight::Keyword1;
|
|
||||||
}
|
|
||||||
i += keyword.len();
|
i += keyword.len();
|
||||||
prev_separator = false;
|
break;
|
||||||
continue 'outer;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for &keyword in keywords2 {
|
for &keyword in keywords2 {
|
||||||
let matches = row.render.match_indices(keyword);
|
let search_range = get_slice_range(i as usize, keyword.len(), row.render.len());
|
||||||
for (start, _) in matches {
|
|
||||||
let next_char_offset = start + keyword.len() + 1;
|
let next_char_offset = i as usize + keyword.len() + 1;
|
||||||
let is_end_of_line = next_char_offset >= row.render.len();
|
let is_end_of_line = next_char_offset >= row.render.len();
|
||||||
let next_char = if is_end_of_line {
|
let next_char = if is_end_of_line {
|
||||||
'\0'
|
'\0'
|
||||||
@ -543,15 +589,10 @@ impl Editor {
|
|||||||
bytes[next_char_offset] as char
|
bytes[next_char_offset] as char
|
||||||
};
|
};
|
||||||
|
|
||||||
if is_separator(next_char) {
|
if &row.render[search_range.clone()] == keyword && is_separator(next_char) {
|
||||||
let end = start + keyword.len();
|
highlight_range(&mut row.highlight, search_range, Highlight::Keyword2);
|
||||||
for x in start..end {
|
|
||||||
row.highlight[x] = Highlight::Keyword2;
|
|
||||||
}
|
|
||||||
i += keyword.len();
|
i += keyword.len();
|
||||||
prev_separator = false;
|
break;
|
||||||
continue 'outer;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -559,6 +600,15 @@ impl Editor {
|
|||||||
prev_separator = is_separator(c);
|
prev_separator = is_separator(c);
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let changed = row.highlight_comment_start != in_comment;
|
||||||
|
row.highlight_comment_start = in_comment;
|
||||||
|
|
||||||
|
// If a multi-line comment is opened or closed,
|
||||||
|
// update the next row as well.
|
||||||
|
if changed && index + 1 < self.rows.len() {
|
||||||
|
self.update_syntax(index + 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn syntax_to_color(syntax_type: Highlight) -> i32 {
|
fn syntax_to_color(syntax_type: Highlight) -> i32 {
|
||||||
@ -615,7 +665,7 @@ impl Editor {
|
|||||||
fn prompt(
|
fn prompt(
|
||||||
&mut self,
|
&mut self,
|
||||||
prompt: &str,
|
prompt: &str,
|
||||||
cb: Option<&mut dyn Fn(&mut Self, &str, EditorKey<char>)>,
|
cb: Option<&mut dyn Fn(&mut Self, &str, KeyCode<char>)>,
|
||||||
) -> String {
|
) -> String {
|
||||||
let mut buffer = String::new();
|
let mut buffer = String::new();
|
||||||
let default_cb = &mut Self::_noop_prompt_cb;
|
let default_cb = &mut Self::_noop_prompt_cb;
|
||||||
@ -666,9 +716,9 @@ impl Editor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _noop_prompt_cb(&mut self, _: &str, _: EditorKey<char>) {}
|
fn _noop_prompt_cb(&mut self, _: &str, _: KeyCode<char>) {}
|
||||||
|
|
||||||
fn move_cursor(&mut self, key: &EditorKey<char>) {
|
fn move_cursor(&mut self, key: &KeyCode<char>) {
|
||||||
let row = self.rows.get(self.cursor_y);
|
let row = self.rows.get(self.cursor_y);
|
||||||
match key {
|
match key {
|
||||||
ArrowLeft => {
|
ArrowLeft => {
|
||||||
@ -718,7 +768,7 @@ impl Editor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Route user input to the appropriate handler method
|
/// Route user input to the appropriate handler method
|
||||||
pub fn process_keypress(&mut self) -> Option<EditorKey<char>> {
|
pub fn process_keypress(&mut self) -> Option<KeyCode<char>> {
|
||||||
let key = self.read_key();
|
let key = self.read_key();
|
||||||
if key.is_some() {
|
if key.is_some() {
|
||||||
let char = key.unwrap();
|
let char = key.unwrap();
|
||||||
@ -787,14 +837,14 @@ impl Editor {
|
|||||||
Some(OtherKey('\0'))
|
Some(OtherKey('\0'))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _del_or_backspace(&mut self, key: EditorKey<char>) {
|
fn _del_or_backspace(&mut self, key: KeyCode<char>) {
|
||||||
if key == DeleteKey {
|
if key == DeleteKey {
|
||||||
self.move_cursor(&ArrowRight);
|
self.move_cursor(&ArrowRight);
|
||||||
}
|
}
|
||||||
self.delete_char();
|
self.delete_char();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _page_up_or_down(&mut self, key: EditorKey<char>) {
|
fn _page_up_or_down(&mut self, key: KeyCode<char>) {
|
||||||
let mut times = self.screen_rows;
|
let mut times = self.screen_rows;
|
||||||
|
|
||||||
// Update the cursor position
|
// Update the cursor position
|
||||||
@ -908,7 +958,6 @@ impl Editor {
|
|||||||
let code = format!("\x1b[{}m", current_color);
|
let code = format!("\x1b[{}m", current_color);
|
||||||
self.append_out(&code);
|
self.append_out(&code);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if self.rows[file_row].highlight[x] == Highlight::Normal {
|
} else if self.rows[file_row].highlight[x] == Highlight::Normal {
|
||||||
if current_color != -1 {
|
if current_color != -1 {
|
||||||
self.append_out("\x1b[39m");
|
self.append_out("\x1b[39m");
|
||||||
@ -1076,7 +1125,7 @@ impl Editor {
|
|||||||
cx
|
cx
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert tab characters to spaces for display
|
/// Convert file characters to their display equivalents
|
||||||
fn update_row(&mut self, index: usize) {
|
fn update_row(&mut self, index: usize) {
|
||||||
let row = &mut self.rows[index];
|
let row = &mut self.rows[index];
|
||||||
let str = row.chars.clone();
|
let str = row.chars.clone();
|
||||||
@ -1085,17 +1134,18 @@ impl Editor {
|
|||||||
let str = str.replace('\t', " ");
|
let str = str.replace('\t', " ");
|
||||||
row.render = str;
|
row.render = str;
|
||||||
|
|
||||||
|
// Syntax highlighting
|
||||||
self.update_syntax(index);
|
self.update_syntax(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_row(&mut self, at: usize, row: &str) {
|
fn insert_row(&mut self, at: usize, content: &str) {
|
||||||
if at > self.rows.len() {
|
if at > self.rows.len() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let row = EditorRow::new(row);
|
let row = Row::new(content);
|
||||||
self.rows.insert(at, row);
|
|
||||||
|
|
||||||
|
self.rows.insert(at, row);
|
||||||
self.update_row(at);
|
self.update_row(at);
|
||||||
|
|
||||||
self.dirty += 1;
|
self.dirty += 1;
|
||||||
@ -1275,7 +1325,7 @@ impl Editor {
|
|||||||
// Find
|
// Find
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
fn find_callback(&mut self, query: &str, key: EditorKey<char>) {
|
fn find_callback(&mut self, query: &str, key: KeyCode<char>) {
|
||||||
if !self.search_last_hightlight.is_empty() {
|
if !self.search_last_hightlight.is_empty() {
|
||||||
self.rows[self.search_last_line].highlight = self.search_last_hightlight.clone();
|
self.rows[self.search_last_line].highlight = self.search_last_hightlight.clone();
|
||||||
self.search_last_hightlight.clear();
|
self.search_last_hightlight.clear();
|
||||||
@ -1325,9 +1375,11 @@ impl Editor {
|
|||||||
|
|
||||||
// Highlight matching search result
|
// Highlight matching search result
|
||||||
let len = start + query.len();
|
let len = start + query.len();
|
||||||
for x in start..len {
|
highlight_range(
|
||||||
self.rows[current as usize].highlight[x] = Highlight::SearchMatch;
|
&mut self.rows[current as usize].highlight,
|
||||||
}
|
start..len,
|
||||||
|
Highlight::SearchMatch,
|
||||||
|
);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -1359,95 +1411,101 @@ impl Editor {
|
|||||||
// Functions
|
// Functions
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
fn get_syntax_db() -> Vec<EditorSyntax> {
|
fn get_syntax_db() -> Vec<Syntax> {
|
||||||
vec![
|
vec![
|
||||||
EditorSyntax::new(
|
Syntax::new(
|
||||||
"c",
|
"c",
|
||||||
vec![".c", ".h", ".cpp"],
|
vec![".c", ".h", ".cpp"],
|
||||||
vec![
|
vec![
|
||||||
"switch", "if", "while", "for", "break", "continue", "return", "else", "struct",
|
"continue", "typedef", "switch", "return", "static", "while", "break", "struct",
|
||||||
"union", "typedef", "static", "enum", "class", "case",
|
"union", "class", "else", "enum", "for", "case", "if",
|
||||||
|
],
|
||||||
|
vec![
|
||||||
|
"unsigned", "double", "signed", "float", "long", "char", "int", "void",
|
||||||
],
|
],
|
||||||
vec!["int", "long", "double", "float", "char", "unsigned", "signed", "void"],
|
|
||||||
"//",
|
"//",
|
||||||
"/*",
|
"/*",
|
||||||
"*/",
|
"*/",
|
||||||
EditorSyntaxFlags::HIGHLIGHT_NUMBERS | EditorSyntaxFlags::HIGHLIGHT_STRINGS,
|
SyntaxFlags::HIGHLIGHT_NUMBERS | SyntaxFlags::HIGHLIGHT_STRINGS,
|
||||||
),
|
),
|
||||||
EditorSyntax::new(
|
Syntax::new(
|
||||||
"rust",
|
"rust",
|
||||||
vec![".rs"],
|
vec![".rs"],
|
||||||
vec![
|
vec![
|
||||||
"as", "break", "const", "continue", "crate", "else", "enum", "extern", "false",
|
"continue", "return", "static", "struct", "unsafe", "break", "const", "crate",
|
||||||
"fn", "for", "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut",
|
"extern", "match", "super", "trait", "where", "else", "enum", "false", "impl",
|
||||||
"pub", "ref", "return", "self", "static", "struct", "super", "trait", "true",
|
"loop", "move", "self", "type", "while", "for", "let", "mod", "pub", "ref", "true",
|
||||||
"type", "unsafe", "use", "where", "while",
|
"use", "mut", "as", "fn", "if", "in",
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
"dyn",
|
|
||||||
"Self",
|
|
||||||
"Copy",
|
|
||||||
"Send",
|
|
||||||
"Sync",
|
|
||||||
"Sized",
|
|
||||||
"Sync",
|
|
||||||
"Unpin",
|
|
||||||
"Drop",
|
|
||||||
"Fn",
|
|
||||||
"FnMut",
|
|
||||||
"FnOnce",
|
|
||||||
"Box",
|
|
||||||
"ToOwned",
|
|
||||||
"Clone",
|
|
||||||
"PartialEq",
|
|
||||||
"PartialOrd",
|
|
||||||
"Eq",
|
|
||||||
"Ord",
|
|
||||||
"AsRef",
|
|
||||||
"AsMut",
|
|
||||||
"Into",
|
|
||||||
"From",
|
|
||||||
"Default",
|
|
||||||
"Iterator",
|
|
||||||
"Extend",
|
|
||||||
"IntoIterator",
|
|
||||||
"DoubleEndedIterator",
|
"DoubleEndedIterator",
|
||||||
"ExactSizeIterator",
|
"ExactSizeIterator",
|
||||||
"Option",
|
"IntoIterator",
|
||||||
"Some",
|
"PartialOrd",
|
||||||
"None",
|
"PartialEq",
|
||||||
"Ok",
|
"Iterator",
|
||||||
"Err",
|
|
||||||
"String",
|
|
||||||
"ToString",
|
"ToString",
|
||||||
"Vec",
|
"Default",
|
||||||
"str",
|
"ToOwned",
|
||||||
"char",
|
"Extend",
|
||||||
"u8",
|
"FnOnce",
|
||||||
"i8",
|
"Option",
|
||||||
"u16",
|
"String",
|
||||||
"i16",
|
"AsMut",
|
||||||
"u32",
|
"AsRef",
|
||||||
"i32",
|
"Clone",
|
||||||
"usize",
|
"Debug",
|
||||||
"isize",
|
"FnMut",
|
||||||
"u64",
|
"Sized",
|
||||||
"i64",
|
"Unpin",
|
||||||
"u128",
|
|
||||||
"i128",
|
|
||||||
"bool",
|
|
||||||
"array",
|
"array",
|
||||||
|
"isize",
|
||||||
|
"usize",
|
||||||
|
"&str",
|
||||||
|
"Copy",
|
||||||
|
"Drop",
|
||||||
|
"From",
|
||||||
|
"Into",
|
||||||
|
"None",
|
||||||
|
"Self",
|
||||||
|
"Send",
|
||||||
|
"Some",
|
||||||
|
"Sync",
|
||||||
|
"Sync",
|
||||||
|
"bool",
|
||||||
|
"char",
|
||||||
|
"i128",
|
||||||
|
"u128",
|
||||||
|
"Box",
|
||||||
|
"Err",
|
||||||
|
"Ord",
|
||||||
|
"Vec",
|
||||||
|
"dyn",
|
||||||
"f32",
|
"f32",
|
||||||
"f64"
|
"f64",
|
||||||
|
"i16",
|
||||||
|
"i32",
|
||||||
|
"i64",
|
||||||
|
"str",
|
||||||
|
"u16",
|
||||||
|
"u32",
|
||||||
|
"u64",
|
||||||
|
"Eq",
|
||||||
|
"Fn",
|
||||||
|
"Ok",
|
||||||
|
"i8",
|
||||||
|
"u8",
|
||||||
],
|
],
|
||||||
"//",
|
"//",
|
||||||
"/*",
|
"/*",
|
||||||
"*/",
|
"*/",
|
||||||
EditorSyntaxFlags::HIGHLIGHT_NUMBERS | EditorSyntaxFlags::HIGHLIGHT_STRINGS,
|
SyntaxFlags::HIGHLIGHT_NUMBERS | SyntaxFlags::HIGHLIGHT_STRINGS,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Determine whether a character is one which separates tokens
|
||||||
|
/// in the language to highlight
|
||||||
fn is_separator(input_char: char) -> bool {
|
fn is_separator(input_char: char) -> bool {
|
||||||
if input_char.is_ascii_whitespace() || input_char == '\0' {
|
if input_char.is_ascii_whitespace() || input_char == '\0' {
|
||||||
return true;
|
return true;
|
||||||
@ -1464,6 +1522,27 @@ fn is_separator(input_char: char) -> bool {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a range for a slice of a string or vector.
|
||||||
|
///
|
||||||
|
/// 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`.
|
||||||
|
fn get_slice_range(start: usize, needle_len: usize, haystack_len: usize) -> Range<usize> {
|
||||||
|
let search_len = start + needle_len;
|
||||||
|
if search_len >= haystack_len {
|
||||||
|
start..haystack_len
|
||||||
|
} else {
|
||||||
|
start..search_len
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the highlighting type for the specified range
|
||||||
|
/// Kind of similar to the C memset calls
|
||||||
|
fn highlight_range(vec: &mut Vec<Highlight>, range: Range<usize>, value: Highlight) {
|
||||||
|
for x in range {
|
||||||
|
vec[x] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
Loading…
Reference in New Issue
Block a user