Massive refactor of highlighting logic

This commit is contained in:
Timothy Warren 2021-03-16 11:39:13 -04:00
parent aa849373ad
commit d5b880dd9e
2 changed files with 135 additions and 105 deletions

View File

@ -62,4 +62,4 @@ impl HighlightingOptions {
pub fn comments(self) -> bool { pub fn comments(self) -> bool {
self.comments self.comments
} }
} }

View File

@ -190,134 +190,164 @@ impl Row {
None None
} }
pub fn highlight(&mut self, opts: HighlightingOptions, word: Option<&str>) { fn highlight_match(&mut self, word: Option<&str>) {
let mut highlighting = Vec::new();
let chars: Vec<char> = self.string.chars().collect();
let mut matches = Vec::new();
let mut search_index = 0;
if let Some(word) = word { if let Some(word) = word {
while let Some(search_match) = self.find(word, search_index, SearchDirection::Forward) { if word.is_empty() {
matches.push(search_match); return;
}
let mut index = 0;
while let Some(search_match) = self.find(word, index, SearchDirection::Forward) {
if let Some(next_index) = search_match.checked_add(word[..].graphemes(true).count()) if let Some(next_index) = search_match.checked_add(word[..].graphemes(true).count())
{ {
search_index = next_index #[allow(clippy::indexing_slicing)]
for i in index.saturating_add(search_match)..next_index {
self.highlighting[i] = highlighting::Type::Match;
}
index = next_index;
} else { } else {
break; break;
} }
} }
} }
}
let mut prev_is_separator = true; fn highlight_char(
let mut in_string = false; &mut self,
let mut index = 0; index: &mut usize,
while let Some(c) = chars.get(index) { opts: HighlightingOptions,
if let Some(word) = word { c: char,
if matches.contains(&index) { chars: &[char],
for _ in word[..].graphemes(true) { ) -> bool {
index += 1; if opts.characters() && c == '\'' {
highlighting.push(highlighting::Type::Match); if let Some(next_char) = chars.get(index.saturating_add(1)) {
let closing_index = if *next_char == '\\' {
index.saturating_add(3)
} else {
index.saturating_add(2)
};
if let Some(closing_char) = chars.get(closing_index) {
if *closing_char == '\'' {
for _ in 0..=closing_index.saturating_sub(*index) {
self.highlighting.push(highlighting::Type::Character);
*index += 1;
}
return true;
}
}
}
}
false
}
fn highlight_comment(
&mut self,
index: &mut usize,
opts: HighlightingOptions,
c: char,
chars: &[char],
) -> bool {
if opts.comments() && c == '/' && *index < chars.len() {
if let Some(next_char) = chars.get(index.saturating_add(1)) {
if *next_char == '/' {
for _ in *index..chars.len() {
self.highlighting.push(highlighting::Type::Comment);
*index += 1;
} }
continue; return true;
}
};
}
false
}
fn highlight_string(
&mut self,
index: &mut usize,
opts: HighlightingOptions,
c: char,
chars: &[char],
) -> bool {
if opts.strings() && c == '"' {
loop {
self.highlighting.push(highlighting::Type::String);
*index += 1;
if let Some(next_char) = chars.get(*index) {
if *next_char == '"' {
break;
}
} else {
break;
} }
} }
let previous_highlight = if index > 0 { self.highlighting.push(highlighting::Type::String);
highlighting *index += 1;
.get(index - 1) return true;
.unwrap_or(&highlighting::Type::None) }
} else {
&highlighting::Type::None
};
if opts.characters() && !in_string && *c == '\'' { false
prev_is_separator = true; }
if let Some(next_char) = chars.get(index.saturating_add(1)) { fn highlight_number(
let closing_index = if *next_char == '\\' { &mut self,
index.saturating_add(3) index: &mut usize,
} else { opts: HighlightingOptions,
index.saturating_add(2) c: char,
}; chars: &[char],
) -> bool {
if let Some(closing_char) = chars.get(closing_index) { if opts.numbers() && c.is_ascii_digit() {
if *closing_char == '\'' { if *index > 0 {
for _ in 0..=closing_index.saturating_sub(index) { #[allow(clippy::indexing_slicing, clippy::integer_arithmetic)]
highlighting.push(highlighting::Type::Character); let prev_char = chars[*index - 1];
index += 1; if !prev_char.is_ascii_punctuation() && !prev_char.is_ascii_whitespace() {
} return false;
continue;
}
}
} }
}
highlighting.push(highlighting::Type::None); loop {
index += 1; self.highlighting.push(highlighting::Type::Number);
*index += 1;
if let Some(next_char) = chars.get(*index) {
if *next_char != '.' && !next_char.is_ascii_digit() {
break;
}
} else {
break;
}
}
return true;
}
false
}
pub fn highlight(&mut self, opts: HighlightingOptions, word: Option<&str>) {
self.highlighting = Vec::new();
let chars: Vec<char> = self.string.chars().collect();
let mut index = 0;
while let Some(c) = chars.get(index) {
if self.highlight_char(&mut index, opts, *c, &chars)
|| self.highlight_comment(&mut index, opts, *c, &chars)
|| self.highlight_string(&mut index, opts, *c, &chars)
|| self.highlight_number(&mut index, opts, *c, &chars)
{
continue; continue;
} }
if opts.strings() { self.highlighting.push(highlighting::Type::None);
if in_string {
highlighting.push(highlighting::Type::String);
// Don't let an escaped character stop string highlighting
if *c == '\\' && index < self.len().saturating_sub(1) {
highlighting.push(highlighting::Type::String);
index += 2;
continue;
}
if *c == '"' {
in_string = false;
prev_is_separator = true;
} else {
prev_is_separator = false;
}
index += 1;
continue;
} else if prev_is_separator && *c == '"' {
highlighting.push(highlighting::Type::String);
in_string = true;
prev_is_separator = true;
index += 1;
continue;
}
}
if opts.comments() && *c == '/' {
if let Some(next_char) = chars.get(index.saturating_add(1)) {
if *next_char == '/' {
for _ in index..chars.len() {
highlighting.push(highlighting::Type::Comment);
}
break;
}
};
}
if opts.numbers() {
if (c.is_ascii_digit()
&& (prev_is_separator || *previous_highlight == highlighting::Type::Number))
|| (*c == '.' && *previous_highlight == highlighting::Type::Number)
{
highlighting.push(highlighting::Type::Number)
} else {
highlighting.push(highlighting::Type::None)
}
} else {
highlighting.push(highlighting::Type::None)
}
prev_is_separator = c.is_ascii_punctuation() || c.is_ascii_whitespace();
index += 1; index += 1;
} }
self.highlighting = highlighting; self.highlight_match(word);
} }
} }