2020-07-09 21:45:22 +00:00
|
|
|
|
use std::cmp::min;
|
|
|
|
|
use std::mem;
|
|
|
|
|
use std::ops::RangeInclusive;
|
|
|
|
|
|
|
|
|
|
use regex_automata::{dense, DenseDFA, Error as RegexError, DFA};
|
|
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
|
use crate::grid::{BidirectionalIterator, Dimensions, GridIterator, Indexed};
|
2020-07-09 21:45:22 +00:00
|
|
|
|
use crate::index::{Boundary, Column, Direction, Point, Side};
|
|
|
|
|
use crate::term::cell::{Cell, Flags};
|
|
|
|
|
use crate::term::Term;
|
|
|
|
|
|
|
|
|
|
/// Used to match equal brackets, when performing a bracket-pair selection.
|
|
|
|
|
const BRACKET_PAIRS: [(char, char); 4] = [('(', ')'), ('[', ']'), ('{', '}'), ('<', '>')];
|
|
|
|
|
|
|
|
|
|
pub type Match = RangeInclusive<Point<usize>>;
|
|
|
|
|
|
|
|
|
|
/// Terminal regex search state.
|
2021-03-01 19:50:39 +00:00
|
|
|
|
#[derive(Clone, Debug)]
|
2020-07-09 21:45:22 +00:00
|
|
|
|
pub struct RegexSearch {
|
|
|
|
|
/// Locate end of match searching right.
|
|
|
|
|
right_fdfa: DenseDFA<Vec<usize>, usize>,
|
|
|
|
|
/// Locate start of match searching right.
|
|
|
|
|
right_rdfa: DenseDFA<Vec<usize>, usize>,
|
|
|
|
|
|
|
|
|
|
/// Locate start of match searching left.
|
|
|
|
|
left_fdfa: DenseDFA<Vec<usize>, usize>,
|
|
|
|
|
/// Locate end of match searching left.
|
|
|
|
|
left_rdfa: DenseDFA<Vec<usize>, usize>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl RegexSearch {
|
2020-07-15 21:27:32 +00:00
|
|
|
|
/// Build the forward and backward search DFAs.
|
2020-07-09 21:45:22 +00:00
|
|
|
|
pub fn new(search: &str) -> Result<RegexSearch, RegexError> {
|
|
|
|
|
// Check case info for smart case
|
|
|
|
|
let has_uppercase = search.chars().any(|c| c.is_uppercase());
|
|
|
|
|
|
|
|
|
|
// Create Regex DFAs for all search directions.
|
|
|
|
|
let mut builder = dense::Builder::new();
|
|
|
|
|
let builder = builder.case_insensitive(!has_uppercase);
|
|
|
|
|
|
|
|
|
|
let left_fdfa = builder.clone().reverse(true).build(search)?;
|
|
|
|
|
let left_rdfa = builder.clone().anchored(true).longest_match(true).build(search)?;
|
|
|
|
|
|
|
|
|
|
let right_fdfa = builder.clone().build(search)?;
|
|
|
|
|
let right_rdfa = builder.anchored(true).longest_match(true).reverse(true).build(search)?;
|
|
|
|
|
|
|
|
|
|
Ok(RegexSearch { right_fdfa, right_rdfa, left_fdfa, left_rdfa })
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<T> Term<T> {
|
|
|
|
|
/// Get next search match in the specified direction.
|
|
|
|
|
pub fn search_next(
|
|
|
|
|
&self,
|
2021-01-24 21:45:36 +00:00
|
|
|
|
dfas: &RegexSearch,
|
2020-07-09 21:45:22 +00:00
|
|
|
|
mut origin: Point<usize>,
|
|
|
|
|
direction: Direction,
|
|
|
|
|
side: Side,
|
|
|
|
|
mut max_lines: Option<usize>,
|
|
|
|
|
) -> Option<Match> {
|
|
|
|
|
origin = self.expand_wide(origin, direction);
|
|
|
|
|
|
|
|
|
|
max_lines = max_lines.filter(|max_lines| max_lines + 1 < self.total_lines());
|
|
|
|
|
|
|
|
|
|
match direction {
|
2021-01-24 21:45:36 +00:00
|
|
|
|
Direction::Right => self.next_match_right(dfas, origin, side, max_lines),
|
|
|
|
|
Direction::Left => self.next_match_left(dfas, origin, side, max_lines),
|
2020-07-09 21:45:22 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Find the next match to the right of the origin.
|
|
|
|
|
fn next_match_right(
|
|
|
|
|
&self,
|
2021-01-24 21:45:36 +00:00
|
|
|
|
dfas: &RegexSearch,
|
2020-07-09 21:45:22 +00:00
|
|
|
|
origin: Point<usize>,
|
|
|
|
|
side: Side,
|
|
|
|
|
max_lines: Option<usize>,
|
|
|
|
|
) -> Option<Match> {
|
|
|
|
|
let start = self.line_search_left(origin);
|
|
|
|
|
let mut end = start;
|
|
|
|
|
|
|
|
|
|
// Limit maximum number of lines searched.
|
|
|
|
|
let total_lines = self.total_lines();
|
|
|
|
|
end = match max_lines {
|
|
|
|
|
Some(max_lines) => {
|
|
|
|
|
let line = (start.line + total_lines - max_lines) % total_lines;
|
|
|
|
|
Point::new(line, self.cols() - 1)
|
|
|
|
|
},
|
|
|
|
|
_ => end.sub_absolute(self, Boundary::Wrap, 1),
|
|
|
|
|
};
|
|
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let mut regex_iter = RegexIter::new(start, end, Direction::Right, &self, dfas).peekable();
|
2020-07-09 21:45:22 +00:00
|
|
|
|
|
|
|
|
|
// Check if there's any match at all.
|
|
|
|
|
let first_match = regex_iter.peek()?.clone();
|
|
|
|
|
|
|
|
|
|
let regex_match = regex_iter
|
|
|
|
|
.find(|regex_match| {
|
|
|
|
|
let match_point = Self::match_side(®ex_match, side);
|
|
|
|
|
|
|
|
|
|
// If the match's point is beyond the origin, we're done.
|
|
|
|
|
match_point.line > start.line
|
|
|
|
|
|| match_point.line < origin.line
|
2021-01-24 21:45:36 +00:00
|
|
|
|
|| (match_point.line == origin.line && match_point.column >= origin.column)
|
2020-07-09 21:45:22 +00:00
|
|
|
|
})
|
|
|
|
|
.unwrap_or(first_match);
|
|
|
|
|
|
|
|
|
|
Some(regex_match)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Find the next match to the left of the origin.
|
|
|
|
|
fn next_match_left(
|
|
|
|
|
&self,
|
2021-01-24 21:45:36 +00:00
|
|
|
|
dfas: &RegexSearch,
|
2020-07-09 21:45:22 +00:00
|
|
|
|
origin: Point<usize>,
|
|
|
|
|
side: Side,
|
|
|
|
|
max_lines: Option<usize>,
|
|
|
|
|
) -> Option<Match> {
|
|
|
|
|
let start = self.line_search_right(origin);
|
|
|
|
|
let mut end = start;
|
|
|
|
|
|
|
|
|
|
// Limit maximum number of lines searched.
|
|
|
|
|
end = match max_lines {
|
|
|
|
|
Some(max_lines) => Point::new((start.line + max_lines) % self.total_lines(), Column(0)),
|
|
|
|
|
_ => end.add_absolute(self, Boundary::Wrap, 1),
|
|
|
|
|
};
|
|
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let mut regex_iter = RegexIter::new(start, end, Direction::Left, &self, dfas).peekable();
|
2020-07-09 21:45:22 +00:00
|
|
|
|
|
|
|
|
|
// Check if there's any match at all.
|
|
|
|
|
let first_match = regex_iter.peek()?.clone();
|
|
|
|
|
|
|
|
|
|
let regex_match = regex_iter
|
|
|
|
|
.find(|regex_match| {
|
|
|
|
|
let match_point = Self::match_side(®ex_match, side);
|
|
|
|
|
|
|
|
|
|
// If the match's point is beyond the origin, we're done.
|
|
|
|
|
match_point.line < start.line
|
|
|
|
|
|| match_point.line > origin.line
|
2021-01-24 21:45:36 +00:00
|
|
|
|
|| (match_point.line == origin.line && match_point.column <= origin.column)
|
2020-07-09 21:45:22 +00:00
|
|
|
|
})
|
|
|
|
|
.unwrap_or(first_match);
|
|
|
|
|
|
|
|
|
|
Some(regex_match)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get the side of a match.
|
|
|
|
|
fn match_side(regex_match: &Match, side: Side) -> Point<usize> {
|
|
|
|
|
match side {
|
|
|
|
|
Side::Right => *regex_match.end(),
|
|
|
|
|
Side::Left => *regex_match.start(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Find the next regex match to the left of the origin point.
|
|
|
|
|
///
|
|
|
|
|
/// The origin is always included in the regex.
|
2021-01-24 21:45:36 +00:00
|
|
|
|
pub fn regex_search_left(
|
|
|
|
|
&self,
|
|
|
|
|
dfas: &RegexSearch,
|
|
|
|
|
start: Point<usize>,
|
|
|
|
|
end: Point<usize>,
|
|
|
|
|
) -> Option<Match> {
|
2020-07-09 21:45:22 +00:00
|
|
|
|
// Find start and end of match.
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let match_start = self.regex_search(start, end, Direction::Left, &dfas.left_fdfa)?;
|
|
|
|
|
let match_end = self.regex_search(match_start, start, Direction::Right, &dfas.left_rdfa)?;
|
2020-07-09 21:45:22 +00:00
|
|
|
|
|
|
|
|
|
Some(match_start..=match_end)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Find the next regex match to the right of the origin point.
|
|
|
|
|
///
|
|
|
|
|
/// The origin is always included in the regex.
|
2021-01-24 21:45:36 +00:00
|
|
|
|
pub fn regex_search_right(
|
|
|
|
|
&self,
|
|
|
|
|
dfas: &RegexSearch,
|
|
|
|
|
start: Point<usize>,
|
|
|
|
|
end: Point<usize>,
|
|
|
|
|
) -> Option<Match> {
|
2020-07-09 21:45:22 +00:00
|
|
|
|
// Find start and end of match.
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let match_end = self.regex_search(start, end, Direction::Right, &dfas.right_fdfa)?;
|
|
|
|
|
let match_start = self.regex_search(match_end, start, Direction::Left, &dfas.right_rdfa)?;
|
2020-07-09 21:45:22 +00:00
|
|
|
|
|
|
|
|
|
Some(match_start..=match_end)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Find the next regex match.
|
|
|
|
|
///
|
|
|
|
|
/// This will always return the side of the first match which is farthest from the start point.
|
|
|
|
|
fn regex_search(
|
|
|
|
|
&self,
|
|
|
|
|
start: Point<usize>,
|
|
|
|
|
end: Point<usize>,
|
|
|
|
|
direction: Direction,
|
|
|
|
|
dfa: &impl DFA,
|
|
|
|
|
) -> Option<Point<usize>> {
|
|
|
|
|
let last_line = self.total_lines() - 1;
|
|
|
|
|
let last_col = self.cols() - 1;
|
|
|
|
|
|
|
|
|
|
// Advance the iterator.
|
|
|
|
|
let next = match direction {
|
|
|
|
|
Direction::Right => GridIterator::next,
|
|
|
|
|
Direction::Left => GridIterator::prev,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let mut iter = self.grid.iter_from(start);
|
|
|
|
|
let mut state = dfa.start_state();
|
2020-11-05 04:45:14 +00:00
|
|
|
|
let mut last_wrapped = false;
|
2020-07-09 21:45:22 +00:00
|
|
|
|
let mut regex_match = None;
|
|
|
|
|
|
2020-11-05 04:45:14 +00:00
|
|
|
|
let mut cell = iter.cell();
|
2020-07-09 21:45:22 +00:00
|
|
|
|
self.skip_fullwidth(&mut iter, &mut cell, direction);
|
2020-11-05 04:45:14 +00:00
|
|
|
|
let mut c = cell.c;
|
|
|
|
|
|
2020-07-09 21:45:22 +00:00
|
|
|
|
let mut point = iter.point();
|
|
|
|
|
|
|
|
|
|
loop {
|
|
|
|
|
// Convert char to array of bytes.
|
|
|
|
|
let mut buf = [0; 4];
|
2020-11-05 04:45:14 +00:00
|
|
|
|
let utf8_len = c.encode_utf8(&mut buf).len();
|
2020-07-09 21:45:22 +00:00
|
|
|
|
|
|
|
|
|
// Pass char to DFA as individual bytes.
|
|
|
|
|
for i in 0..utf8_len {
|
|
|
|
|
// Inverse byte order when going left.
|
|
|
|
|
let byte = match direction {
|
|
|
|
|
Direction::Right => buf[i],
|
|
|
|
|
Direction::Left => buf[utf8_len - i - 1],
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Since we get the state from the DFA, it doesn't need to be checked.
|
|
|
|
|
state = unsafe { dfa.next_state_unchecked(state, byte) };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle regex state changes.
|
|
|
|
|
if dfa.is_match_or_dead_state(state) {
|
|
|
|
|
if dfa.is_dead_state(state) {
|
|
|
|
|
break;
|
|
|
|
|
} else {
|
|
|
|
|
regex_match = Some(point);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Stop once we've reached the target point.
|
|
|
|
|
if point == end {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Advance grid cell iterator.
|
2020-11-05 04:45:14 +00:00
|
|
|
|
let mut cell = match next(&mut iter) {
|
2021-01-24 21:45:36 +00:00
|
|
|
|
Some(Indexed { cell, .. }) => cell,
|
2020-07-09 21:45:22 +00:00
|
|
|
|
None => {
|
|
|
|
|
// Wrap around to other end of the scrollback buffer.
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let start = Point::new(last_line - point.line, last_col - point.column);
|
2020-07-09 21:45:22 +00:00
|
|
|
|
iter = self.grid.iter_from(start);
|
2020-11-05 04:45:14 +00:00
|
|
|
|
iter.cell()
|
2020-07-09 21:45:22 +00:00
|
|
|
|
},
|
|
|
|
|
};
|
2020-11-05 04:45:14 +00:00
|
|
|
|
self.skip_fullwidth(&mut iter, &mut cell, direction);
|
|
|
|
|
let wrapped = cell.flags.contains(Flags::WRAPLINE);
|
|
|
|
|
c = cell.c;
|
|
|
|
|
|
2020-07-09 21:45:22 +00:00
|
|
|
|
let last_point = mem::replace(&mut point, iter.point());
|
|
|
|
|
|
|
|
|
|
// Handle linebreaks.
|
2021-01-24 21:45:36 +00:00
|
|
|
|
if (last_point.column == last_col && point.column == Column(0) && !last_wrapped)
|
|
|
|
|
|| (last_point.column == Column(0) && point.column == last_col && !wrapped)
|
2020-07-09 21:45:22 +00:00
|
|
|
|
{
|
|
|
|
|
match regex_match {
|
|
|
|
|
Some(_) => break,
|
|
|
|
|
None => state = dfa.start_state(),
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-11-05 04:45:14 +00:00
|
|
|
|
|
|
|
|
|
last_wrapped = wrapped;
|
2020-07-09 21:45:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
regex_match
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Advance a grid iterator over fullwidth characters.
|
2020-11-05 04:45:14 +00:00
|
|
|
|
fn skip_fullwidth<'a>(
|
2020-07-09 21:45:22 +00:00
|
|
|
|
&self,
|
2020-11-05 04:45:14 +00:00
|
|
|
|
iter: &'a mut GridIterator<'_, Cell>,
|
|
|
|
|
cell: &mut &'a Cell,
|
2020-07-09 21:45:22 +00:00
|
|
|
|
direction: Direction,
|
|
|
|
|
) {
|
|
|
|
|
match direction {
|
|
|
|
|
Direction::Right if cell.flags.contains(Flags::WIDE_CHAR) => {
|
|
|
|
|
iter.next();
|
|
|
|
|
},
|
|
|
|
|
Direction::Right if cell.flags.contains(Flags::LEADING_WIDE_CHAR_SPACER) => {
|
2021-01-24 21:45:36 +00:00
|
|
|
|
if let Some(Indexed { cell: new_cell, .. }) = iter.next() {
|
2020-11-05 04:45:14 +00:00
|
|
|
|
*cell = new_cell;
|
2020-07-09 21:45:22 +00:00
|
|
|
|
}
|
|
|
|
|
iter.next();
|
|
|
|
|
},
|
|
|
|
|
Direction::Left if cell.flags.contains(Flags::WIDE_CHAR_SPACER) => {
|
2021-01-24 21:45:36 +00:00
|
|
|
|
if let Some(Indexed { cell: new_cell, .. }) = iter.prev() {
|
2020-11-05 04:45:14 +00:00
|
|
|
|
*cell = new_cell;
|
2020-07-09 21:45:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let prev = iter.point().sub_absolute(self, Boundary::Clamp, 1);
|
|
|
|
|
if self.grid[prev].flags.contains(Flags::LEADING_WIDE_CHAR_SPACER) {
|
|
|
|
|
iter.prev();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
_ => (),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Find next matching bracket.
|
|
|
|
|
pub fn bracket_search(&self, point: Point<usize>) -> Option<Point<usize>> {
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let start_char = self.grid[point.line][point.column].c;
|
2020-07-09 21:45:22 +00:00
|
|
|
|
|
|
|
|
|
// Find the matching bracket we're looking for
|
2020-07-15 21:27:32 +00:00
|
|
|
|
let (forward, end_char) = BRACKET_PAIRS.iter().find_map(|(open, close)| {
|
2020-07-09 21:45:22 +00:00
|
|
|
|
if open == &start_char {
|
|
|
|
|
Some((true, *close))
|
|
|
|
|
} else if close == &start_char {
|
|
|
|
|
Some((false, *open))
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
let mut iter = self.grid.iter_from(point);
|
|
|
|
|
|
|
|
|
|
// For every character match that equals the starting bracket, we
|
|
|
|
|
// ignore one bracket of the opposite type.
|
|
|
|
|
let mut skip_pairs = 0;
|
|
|
|
|
|
|
|
|
|
loop {
|
|
|
|
|
// Check the next cell
|
2020-07-15 21:27:32 +00:00
|
|
|
|
let cell = if forward { iter.next() } else { iter.prev() };
|
2020-07-09 21:45:22 +00:00
|
|
|
|
|
|
|
|
|
// Break if there are no more cells
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let cell = match cell {
|
|
|
|
|
Some(cell) => cell,
|
2020-07-09 21:45:22 +00:00
|
|
|
|
None => break,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Check if the bracket matches
|
2021-01-24 21:45:36 +00:00
|
|
|
|
if cell.c == end_char && skip_pairs == 0 {
|
|
|
|
|
return Some(cell.point);
|
|
|
|
|
} else if cell.c == start_char {
|
2020-07-09 21:45:22 +00:00
|
|
|
|
skip_pairs += 1;
|
2021-01-24 21:45:36 +00:00
|
|
|
|
} else if cell.c == end_char {
|
2020-07-09 21:45:22 +00:00
|
|
|
|
skip_pairs -= 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Find left end of semantic block.
|
|
|
|
|
pub fn semantic_search_left(&self, mut point: Point<usize>) -> Point<usize> {
|
|
|
|
|
// Limit the starting point to the last line in the history
|
|
|
|
|
point.line = min(point.line, self.total_lines() - 1);
|
|
|
|
|
|
|
|
|
|
let mut iter = self.grid.iter_from(point);
|
|
|
|
|
let last_col = self.cols() - Column(1);
|
|
|
|
|
|
|
|
|
|
let wide = Flags::WIDE_CHAR | Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER;
|
|
|
|
|
while let Some(cell) = iter.prev() {
|
|
|
|
|
if !cell.flags.intersects(wide) && self.semantic_escape_chars.contains(cell.c) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
|
if cell.point.column == last_col && !cell.flags.contains(Flags::WRAPLINE) {
|
2020-07-09 21:45:22 +00:00
|
|
|
|
break; // cut off if on new line or hit escape char
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
|
point = cell.point;
|
2020-07-09 21:45:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
point
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Find right end of semantic block.
|
|
|
|
|
pub fn semantic_search_right(&self, mut point: Point<usize>) -> Point<usize> {
|
|
|
|
|
// Limit the starting point to the last line in the history
|
|
|
|
|
point.line = min(point.line, self.total_lines() - 1);
|
|
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let wide = Flags::WIDE_CHAR | Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER;
|
2020-07-09 21:45:22 +00:00
|
|
|
|
let last_col = self.cols() - 1;
|
|
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
|
for cell in self.grid.iter_from(point) {
|
2020-07-09 21:45:22 +00:00
|
|
|
|
if !cell.flags.intersects(wide) && self.semantic_escape_chars.contains(cell.c) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
|
point = cell.point;
|
2020-07-09 21:45:22 +00:00
|
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
|
if point.column == last_col && !cell.flags.contains(Flags::WRAPLINE) {
|
2020-07-09 21:45:22 +00:00
|
|
|
|
break; // cut off if on new line or hit escape char
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
point
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Find the beginning of the current line across linewraps.
|
|
|
|
|
pub fn line_search_left(&self, mut point: Point<usize>) -> Point<usize> {
|
|
|
|
|
while point.line + 1 < self.total_lines()
|
|
|
|
|
&& self.grid[point.line + 1][self.cols() - 1].flags.contains(Flags::WRAPLINE)
|
|
|
|
|
{
|
|
|
|
|
point.line += 1;
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
|
point.column = Column(0);
|
2020-07-09 21:45:22 +00:00
|
|
|
|
|
|
|
|
|
point
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Find the end of the current line across linewraps.
|
|
|
|
|
pub fn line_search_right(&self, mut point: Point<usize>) -> Point<usize> {
|
2020-08-12 08:36:24 +00:00
|
|
|
|
while point.line > 0
|
|
|
|
|
&& self.grid[point.line][self.cols() - 1].flags.contains(Flags::WRAPLINE)
|
|
|
|
|
{
|
2020-07-09 21:45:22 +00:00
|
|
|
|
point.line -= 1;
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
|
point.column = self.cols() - 1;
|
2020-07-09 21:45:22 +00:00
|
|
|
|
|
|
|
|
|
point
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Iterator over regex matches.
|
|
|
|
|
pub struct RegexIter<'a, T> {
|
|
|
|
|
point: Point<usize>,
|
|
|
|
|
end: Point<usize>,
|
|
|
|
|
direction: Direction,
|
2021-01-24 21:45:36 +00:00
|
|
|
|
dfas: &'a RegexSearch,
|
2020-07-09 21:45:22 +00:00
|
|
|
|
term: &'a Term<T>,
|
|
|
|
|
done: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a, T> RegexIter<'a, T> {
|
|
|
|
|
pub fn new(
|
|
|
|
|
start: Point<usize>,
|
|
|
|
|
end: Point<usize>,
|
|
|
|
|
direction: Direction,
|
|
|
|
|
term: &'a Term<T>,
|
2021-01-24 21:45:36 +00:00
|
|
|
|
dfas: &'a RegexSearch,
|
2020-07-09 21:45:22 +00:00
|
|
|
|
) -> Self {
|
2021-01-24 21:45:36 +00:00
|
|
|
|
Self { point: start, done: false, end, direction, term, dfas }
|
2020-07-09 21:45:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Skip one cell, advancing the origin point to the next one.
|
|
|
|
|
fn skip(&mut self) {
|
|
|
|
|
self.point = self.term.expand_wide(self.point, self.direction);
|
|
|
|
|
|
|
|
|
|
self.point = match self.direction {
|
|
|
|
|
Direction::Right => self.point.add_absolute(self.term, Boundary::Wrap, 1),
|
|
|
|
|
Direction::Left => self.point.sub_absolute(self.term, Boundary::Wrap, 1),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get the next match in the specified direction.
|
|
|
|
|
fn next_match(&self) -> Option<Match> {
|
|
|
|
|
match self.direction {
|
2021-01-24 21:45:36 +00:00
|
|
|
|
Direction::Right => self.term.regex_search_right(self.dfas, self.point, self.end),
|
|
|
|
|
Direction::Left => self.term.regex_search_left(self.dfas, self.point, self.end),
|
2020-07-09 21:45:22 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a, T> Iterator for RegexIter<'a, T> {
|
|
|
|
|
type Item = Match;
|
|
|
|
|
|
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
2021-02-18 20:27:52 +00:00
|
|
|
|
if self.done {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Since the end itself might be a single cell match, we search one more time.
|
2020-07-09 21:45:22 +00:00
|
|
|
|
if self.point == self.end {
|
|
|
|
|
self.done = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let regex_match = self.next_match()?;
|
|
|
|
|
|
|
|
|
|
self.point = *regex_match.end();
|
2021-02-18 20:27:52 +00:00
|
|
|
|
if self.point == self.end {
|
|
|
|
|
// Stop when the match terminates right on the end limit.
|
|
|
|
|
self.done = true;
|
|
|
|
|
} else {
|
|
|
|
|
// Move the new search origin past the match.
|
|
|
|
|
self.skip();
|
|
|
|
|
}
|
2020-07-09 21:45:22 +00:00
|
|
|
|
|
|
|
|
|
Some(regex_match)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
use crate::index::Column;
|
|
|
|
|
use crate::term::test::mock_term;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn regex_right() {
|
|
|
|
|
#[rustfmt::skip]
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let term = mock_term("\
|
2020-07-09 21:45:22 +00:00
|
|
|
|
testing66\r\n\
|
|
|
|
|
Alacritty\n\
|
|
|
|
|
123\r\n\
|
|
|
|
|
Alacritty\r\n\
|
|
|
|
|
123\
|
|
|
|
|
");
|
|
|
|
|
|
|
|
|
|
// Check regex across wrapped and unwrapped lines.
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let dfas = RegexSearch::new("Ala.*123").unwrap();
|
2020-07-09 21:45:22 +00:00
|
|
|
|
let start = Point::new(3, Column(0));
|
|
|
|
|
let end = Point::new(0, Column(2));
|
|
|
|
|
let match_start = Point::new(3, Column(0));
|
|
|
|
|
let match_end = Point::new(2, Column(2));
|
2021-01-24 21:45:36 +00:00
|
|
|
|
assert_eq!(term.regex_search_right(&dfas, start, end), Some(match_start..=match_end));
|
2020-07-09 21:45:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn regex_left() {
|
|
|
|
|
#[rustfmt::skip]
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let term = mock_term("\
|
2020-07-09 21:45:22 +00:00
|
|
|
|
testing66\r\n\
|
|
|
|
|
Alacritty\n\
|
|
|
|
|
123\r\n\
|
|
|
|
|
Alacritty\r\n\
|
|
|
|
|
123\
|
|
|
|
|
");
|
|
|
|
|
|
|
|
|
|
// Check regex across wrapped and unwrapped lines.
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let dfas = RegexSearch::new("Ala.*123").unwrap();
|
2020-07-09 21:45:22 +00:00
|
|
|
|
let start = Point::new(0, Column(2));
|
|
|
|
|
let end = Point::new(3, Column(0));
|
|
|
|
|
let match_start = Point::new(3, Column(0));
|
|
|
|
|
let match_end = Point::new(2, Column(2));
|
2021-01-24 21:45:36 +00:00
|
|
|
|
assert_eq!(term.regex_search_left(&dfas, start, end), Some(match_start..=match_end));
|
2020-07-09 21:45:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn nested_regex() {
|
|
|
|
|
#[rustfmt::skip]
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let term = mock_term("\
|
2020-07-09 21:45:22 +00:00
|
|
|
|
Ala -> Alacritty -> critty\r\n\
|
|
|
|
|
critty\
|
|
|
|
|
");
|
|
|
|
|
|
|
|
|
|
// Greedy stopped at linebreak.
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let dfas = RegexSearch::new("Ala.*critty").unwrap();
|
2020-07-09 21:45:22 +00:00
|
|
|
|
let start = Point::new(1, Column(0));
|
|
|
|
|
let end = Point::new(1, Column(25));
|
2021-01-24 21:45:36 +00:00
|
|
|
|
assert_eq!(term.regex_search_right(&dfas, start, end), Some(start..=end));
|
2020-07-09 21:45:22 +00:00
|
|
|
|
|
|
|
|
|
// Greedy stopped at dead state.
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let dfas = RegexSearch::new("Ala[^y]*critty").unwrap();
|
2020-07-09 21:45:22 +00:00
|
|
|
|
let start = Point::new(1, Column(0));
|
|
|
|
|
let end = Point::new(1, Column(15));
|
2021-01-24 21:45:36 +00:00
|
|
|
|
assert_eq!(term.regex_search_right(&dfas, start, end), Some(start..=end));
|
2020-07-09 21:45:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn no_match_right() {
|
|
|
|
|
#[rustfmt::skip]
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let term = mock_term("\
|
2020-07-09 21:45:22 +00:00
|
|
|
|
first line\n\
|
|
|
|
|
broken second\r\n\
|
|
|
|
|
third\
|
|
|
|
|
");
|
|
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let dfas = RegexSearch::new("nothing").unwrap();
|
2020-07-09 21:45:22 +00:00
|
|
|
|
let start = Point::new(2, Column(0));
|
|
|
|
|
let end = Point::new(0, Column(4));
|
2021-01-24 21:45:36 +00:00
|
|
|
|
assert_eq!(term.regex_search_right(&dfas, start, end), None);
|
2020-07-09 21:45:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn no_match_left() {
|
|
|
|
|
#[rustfmt::skip]
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let term = mock_term("\
|
2020-07-09 21:45:22 +00:00
|
|
|
|
first line\n\
|
|
|
|
|
broken second\r\n\
|
|
|
|
|
third\
|
|
|
|
|
");
|
|
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let dfas = RegexSearch::new("nothing").unwrap();
|
2020-07-09 21:45:22 +00:00
|
|
|
|
let start = Point::new(0, Column(4));
|
|
|
|
|
let end = Point::new(2, Column(0));
|
2021-01-24 21:45:36 +00:00
|
|
|
|
assert_eq!(term.regex_search_left(&dfas, start, end), None);
|
2020-07-09 21:45:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn include_linebreak_left() {
|
|
|
|
|
#[rustfmt::skip]
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let term = mock_term("\
|
2020-07-09 21:45:22 +00:00
|
|
|
|
testing123\r\n\
|
|
|
|
|
xxx\
|
|
|
|
|
");
|
|
|
|
|
|
|
|
|
|
// Make sure the cell containing the linebreak is not skipped.
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let dfas = RegexSearch::new("te.*123").unwrap();
|
2020-07-09 21:45:22 +00:00
|
|
|
|
let start = Point::new(0, Column(0));
|
|
|
|
|
let end = Point::new(1, Column(0));
|
|
|
|
|
let match_start = Point::new(1, Column(0));
|
|
|
|
|
let match_end = Point::new(1, Column(9));
|
2021-01-24 21:45:36 +00:00
|
|
|
|
assert_eq!(term.regex_search_left(&dfas, start, end), Some(match_start..=match_end));
|
2020-07-09 21:45:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn include_linebreak_right() {
|
|
|
|
|
#[rustfmt::skip]
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let term = mock_term("\
|
2020-07-09 21:45:22 +00:00
|
|
|
|
xxx\r\n\
|
|
|
|
|
testing123\
|
|
|
|
|
");
|
|
|
|
|
|
|
|
|
|
// Make sure the cell containing the linebreak is not skipped.
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let dfas = RegexSearch::new("te.*123").unwrap();
|
2020-07-09 21:45:22 +00:00
|
|
|
|
let start = Point::new(1, Column(2));
|
|
|
|
|
let end = Point::new(0, Column(9));
|
|
|
|
|
let match_start = Point::new(0, Column(0));
|
2021-01-24 21:45:36 +00:00
|
|
|
|
assert_eq!(term.regex_search_right(&dfas, start, end), Some(match_start..=end));
|
2020-07-09 21:45:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn skip_dead_cell() {
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let term = mock_term("alacritty");
|
2020-07-09 21:45:22 +00:00
|
|
|
|
|
|
|
|
|
// Make sure dead state cell is skipped when reversing.
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let dfas = RegexSearch::new("alacrit").unwrap();
|
2020-07-09 21:45:22 +00:00
|
|
|
|
let start = Point::new(0, Column(0));
|
|
|
|
|
let end = Point::new(0, Column(6));
|
2021-01-24 21:45:36 +00:00
|
|
|
|
assert_eq!(term.regex_search_right(&dfas, start, end), Some(start..=end));
|
2020-07-09 21:45:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn reverse_search_dead_recovery() {
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let term = mock_term("zooo lense");
|
2020-07-09 21:45:22 +00:00
|
|
|
|
|
2020-07-15 21:27:32 +00:00
|
|
|
|
// Make sure the reverse DFA operates the same as a forward DFA.
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let dfas = RegexSearch::new("zoo").unwrap();
|
2020-07-09 21:45:22 +00:00
|
|
|
|
let start = Point::new(0, Column(9));
|
|
|
|
|
let end = Point::new(0, Column(0));
|
|
|
|
|
let match_start = Point::new(0, Column(0));
|
|
|
|
|
let match_end = Point::new(0, Column(2));
|
2021-01-24 21:45:36 +00:00
|
|
|
|
assert_eq!(term.regex_search_left(&dfas, start, end), Some(match_start..=match_end));
|
2020-07-09 21:45:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn multibyte_unicode() {
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let term = mock_term("testвосибing");
|
2020-07-09 21:45:22 +00:00
|
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let dfas = RegexSearch::new("te.*ing").unwrap();
|
2020-07-09 21:45:22 +00:00
|
|
|
|
let start = Point::new(0, Column(0));
|
|
|
|
|
let end = Point::new(0, Column(11));
|
2021-01-24 21:45:36 +00:00
|
|
|
|
assert_eq!(term.regex_search_right(&dfas, start, end), Some(start..=end));
|
2020-07-09 21:45:22 +00:00
|
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let dfas = RegexSearch::new("te.*ing").unwrap();
|
2020-07-09 21:45:22 +00:00
|
|
|
|
let start = Point::new(0, Column(11));
|
|
|
|
|
let end = Point::new(0, Column(0));
|
2021-01-24 21:45:36 +00:00
|
|
|
|
assert_eq!(term.regex_search_left(&dfas, start, end), Some(end..=start));
|
2020-07-09 21:45:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn fullwidth() {
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let term = mock_term("a🦇x🦇");
|
2020-07-09 21:45:22 +00:00
|
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let dfas = RegexSearch::new("[^ ]*").unwrap();
|
2020-07-09 21:45:22 +00:00
|
|
|
|
let start = Point::new(0, Column(0));
|
|
|
|
|
let end = Point::new(0, Column(5));
|
2021-01-24 21:45:36 +00:00
|
|
|
|
assert_eq!(term.regex_search_right(&dfas, start, end), Some(start..=end));
|
2020-07-09 21:45:22 +00:00
|
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let dfas = RegexSearch::new("[^ ]*").unwrap();
|
2020-07-09 21:45:22 +00:00
|
|
|
|
let start = Point::new(0, Column(5));
|
|
|
|
|
let end = Point::new(0, Column(0));
|
2021-01-24 21:45:36 +00:00
|
|
|
|
assert_eq!(term.regex_search_left(&dfas, start, end), Some(end..=start));
|
2020-07-09 21:45:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn singlecell_fullwidth() {
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let term = mock_term("🦇");
|
2020-07-09 21:45:22 +00:00
|
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let dfas = RegexSearch::new("🦇").unwrap();
|
2020-07-09 21:45:22 +00:00
|
|
|
|
let start = Point::new(0, Column(0));
|
|
|
|
|
let end = Point::new(0, Column(1));
|
2021-01-24 21:45:36 +00:00
|
|
|
|
assert_eq!(term.regex_search_right(&dfas, start, end), Some(start..=end));
|
2020-07-09 21:45:22 +00:00
|
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let dfas = RegexSearch::new("🦇").unwrap();
|
2020-07-09 21:45:22 +00:00
|
|
|
|
let start = Point::new(0, Column(1));
|
|
|
|
|
let end = Point::new(0, Column(0));
|
2021-01-24 21:45:36 +00:00
|
|
|
|
assert_eq!(term.regex_search_left(&dfas, start, end), Some(end..=start));
|
2020-07-09 21:45:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn wrapping() {
|
|
|
|
|
#[rustfmt::skip]
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let term = mock_term("\
|
2020-07-09 21:45:22 +00:00
|
|
|
|
xxx\r\n\
|
|
|
|
|
xxx\
|
|
|
|
|
");
|
|
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let dfas = RegexSearch::new("xxx").unwrap();
|
2020-07-09 21:45:22 +00:00
|
|
|
|
let start = Point::new(0, Column(2));
|
|
|
|
|
let end = Point::new(1, Column(2));
|
|
|
|
|
let match_start = Point::new(1, Column(0));
|
2021-01-24 21:45:36 +00:00
|
|
|
|
assert_eq!(term.regex_search_right(&dfas, start, end), Some(match_start..=end));
|
2020-07-09 21:45:22 +00:00
|
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let dfas = RegexSearch::new("xxx").unwrap();
|
2020-07-09 21:45:22 +00:00
|
|
|
|
let start = Point::new(1, Column(0));
|
|
|
|
|
let end = Point::new(0, Column(0));
|
|
|
|
|
let match_end = Point::new(0, Column(2));
|
2021-01-24 21:45:36 +00:00
|
|
|
|
assert_eq!(term.regex_search_left(&dfas, start, end), Some(end..=match_end));
|
2020-07-09 21:45:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn wrapping_into_fullwidth() {
|
|
|
|
|
#[rustfmt::skip]
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let term = mock_term("\
|
2020-07-09 21:45:22 +00:00
|
|
|
|
🦇xx\r\n\
|
|
|
|
|
xx🦇\
|
|
|
|
|
");
|
|
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let dfas = RegexSearch::new("🦇x").unwrap();
|
2020-07-09 21:45:22 +00:00
|
|
|
|
let start = Point::new(0, Column(0));
|
|
|
|
|
let end = Point::new(1, Column(3));
|
|
|
|
|
let match_start = Point::new(1, Column(0));
|
|
|
|
|
let match_end = Point::new(1, Column(2));
|
2021-01-24 21:45:36 +00:00
|
|
|
|
assert_eq!(term.regex_search_right(&dfas, start, end), Some(match_start..=match_end));
|
2020-07-09 21:45:22 +00:00
|
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let dfas = RegexSearch::new("x🦇").unwrap();
|
2020-07-09 21:45:22 +00:00
|
|
|
|
let start = Point::new(1, Column(2));
|
|
|
|
|
let end = Point::new(0, Column(0));
|
|
|
|
|
let match_start = Point::new(0, Column(1));
|
|
|
|
|
let match_end = Point::new(0, Column(3));
|
2021-01-24 21:45:36 +00:00
|
|
|
|
assert_eq!(term.regex_search_left(&dfas, start, end), Some(match_start..=match_end));
|
2020-07-09 21:45:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn leading_spacer() {
|
|
|
|
|
#[rustfmt::skip]
|
|
|
|
|
let mut term = mock_term("\
|
|
|
|
|
xxx \n\
|
|
|
|
|
🦇xx\
|
|
|
|
|
");
|
|
|
|
|
term.grid[1][Column(3)].flags.insert(Flags::LEADING_WIDE_CHAR_SPACER);
|
|
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let dfas = RegexSearch::new("🦇x").unwrap();
|
2020-07-09 21:45:22 +00:00
|
|
|
|
let start = Point::new(1, Column(0));
|
|
|
|
|
let end = Point::new(0, Column(3));
|
|
|
|
|
let match_start = Point::new(1, Column(3));
|
|
|
|
|
let match_end = Point::new(0, Column(2));
|
2021-01-24 21:45:36 +00:00
|
|
|
|
assert_eq!(term.regex_search_right(&dfas, start, end), Some(match_start..=match_end));
|
2020-07-09 21:45:22 +00:00
|
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let dfas = RegexSearch::new("🦇x").unwrap();
|
2020-07-09 21:45:22 +00:00
|
|
|
|
let start = Point::new(0, Column(3));
|
|
|
|
|
let end = Point::new(1, Column(0));
|
|
|
|
|
let match_start = Point::new(1, Column(3));
|
|
|
|
|
let match_end = Point::new(0, Column(2));
|
2021-01-24 21:45:36 +00:00
|
|
|
|
assert_eq!(term.regex_search_left(&dfas, start, end), Some(match_start..=match_end));
|
2020-07-09 21:45:22 +00:00
|
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let dfas = RegexSearch::new("x🦇").unwrap();
|
2020-07-09 21:45:22 +00:00
|
|
|
|
let start = Point::new(1, Column(0));
|
|
|
|
|
let end = Point::new(0, Column(3));
|
|
|
|
|
let match_start = Point::new(1, Column(2));
|
|
|
|
|
let match_end = Point::new(0, Column(1));
|
2021-01-24 21:45:36 +00:00
|
|
|
|
assert_eq!(term.regex_search_right(&dfas, start, end), Some(match_start..=match_end));
|
2020-07-09 21:45:22 +00:00
|
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
|
let dfas = RegexSearch::new("x🦇").unwrap();
|
2020-07-09 21:45:22 +00:00
|
|
|
|
let start = Point::new(0, Column(3));
|
|
|
|
|
let end = Point::new(1, Column(0));
|
|
|
|
|
let match_start = Point::new(1, Column(2));
|
|
|
|
|
let match_end = Point::new(0, Column(1));
|
2021-01-24 21:45:36 +00:00
|
|
|
|
assert_eq!(term.regex_search_left(&dfas, start, end), Some(match_start..=match_end));
|
2020-07-09 21:45:22 +00:00
|
|
|
|
}
|
|
|
|
|
}
|