Finish tutorial, much refactoring

This commit is contained in:
Timothy Warren 2019-09-05 15:26:04 -04:00
parent ca51d8f1f5
commit 389a526f41

View File

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