use super::error::TargetError;
use crate::snip::Target;
use crate::Rope;
impl Target {
pub fn resolve(&self, rope: &Rope) -> Result<usize, TargetError> {
match self {
Target::Literal(s) => resolve_literal(rope, s),
#[cfg(feature = "regex")]
Target::Pattern(pattern) => resolve_pattern(rope, pattern),
Target::Line(n) => resolve_line(rope, *n),
Target::Char(n) => resolve_char(rope, *n),
Target::Position { line, col } => resolve_position(rope, *line, *col),
}
}
pub fn resolve_range(&self, rope: &Rope) -> Result<(usize, usize), TargetError> {
match self {
Target::Literal(s) => {
if s.is_empty() {
return Ok((0, 0));
}
let mut global_char_idx = 0;
let needle_chars: Vec<char> = s.chars().collect();
let needle_len = needle_chars.len();
for chunk in rope.chunks() {
let chunk_chars: Vec<char> = chunk.chars().collect();
let mut i = 0;
while i + needle_len <= chunk_chars.len() {
if chunk_chars[i..i + needle_len] == needle_chars[..] {
let start = global_char_idx + i;
let end = start + needle_len;
return Ok((start, end));
}
i += 1;
}
global_char_idx += chunk_chars.len();
}
Err(TargetError::NotFound)
}
Target::Line(line_idx) => {
if *line_idx >= rope.len_lines() {
return Err(TargetError::InvalidPosition {
line: *line_idx,
col: None,
});
}
let start = rope.line_to_char(*line_idx);
let end = if *line_idx + 1 < rope.len_lines() {
rope.line_to_char(*line_idx + 1)
} else {
rope.len_chars()
};
Ok((start, end))
}
Target::Char(n) => {
if *n >= rope.len_chars() {
Err(TargetError::OutOfBounds)
} else {
Ok((*n, *n + 1))
}
}
Target::Position { line, col } => {
let start = resolve_position(rope, *line, *col)?;
Ok((start, start))
}
#[cfg(feature = "regex")]
Target::Pattern(pattern) => {
use regex_cursor::{Input as RegexInput, RopeyCursor};
let regex = regex_cursor::engines::meta::Regex::new(pattern)
.map_err(|e| TargetError::InvalidPattern(e.to_string()))?;
let cursor = RopeyCursor::new(rope.slice(..));
let input = RegexInput::new(cursor);
if let Some(m) = regex.find(input) {
Ok((m.start(), m.end()))
} else {
Err(TargetError::NotFound)
}
}
}
}
}
fn resolve_literal(rope: &Rope, needle: &str) -> Result<usize, TargetError> {
if needle.is_empty() {
return Ok(0);
}
let needle_chars: Vec<char> = needle.chars().collect();
let mut char_idx = 0;
let mut chars_iter = rope.chars();
while let Some(c) = chars_iter.next() {
if c == needle_chars[0] {
let start_idx = char_idx;
let mut match_idx = 1;
let mut lookahead = chars_iter.clone();
while match_idx < needle_chars.len() {
match lookahead.next() {
Some(ch) if ch == needle_chars[match_idx] => {
match_idx += 1;
}
_ => break,
}
}
if match_idx == needle_chars.len() {
return Ok(start_idx);
}
}
char_idx += 1;
}
Err(TargetError::NotFound)
}
#[cfg(feature = "regex")]
fn resolve_pattern(rope: &Rope, pattern: &str) -> Result<usize, TargetError> {
use regex_cursor::{Input as RegexInput, RopeyCursor};
let regex = regex_cursor::engines::meta::Regex::new(pattern)
.map_err(|e| TargetError::InvalidPattern(e.to_string()))?;
let cursor = RopeyCursor::new(rope.slice(..));
let input = RegexInput::new(cursor);
regex
.find(input)
.map(|m| m.start())
.ok_or(TargetError::NotFound)
}
fn resolve_line(rope: &Rope, line: usize) -> Result<usize, TargetError> {
if line >= rope.len_lines() {
return Err(TargetError::InvalidPosition { line, col: None });
}
Ok(rope.line_to_char(line))
}
fn resolve_char(rope: &Rope, char_idx: usize) -> Result<usize, TargetError> {
if char_idx >= rope.len_chars() {
return Err(TargetError::OutOfBounds);
}
Ok(char_idx)
}
fn resolve_position(rope: &Rope, line: usize, col: usize) -> Result<usize, TargetError> {
let line_idx = line.saturating_sub(1);
let col_idx = col.saturating_sub(1);
if line_idx >= rope.len_lines() {
return Err(TargetError::InvalidPosition {
line,
col: Some(col),
});
}
let line_start = rope.line_to_char(line_idx);
let line_end = if line_idx + 1 < rope.len_lines() {
rope.line_to_char(line_idx + 1)
} else {
rope.len_chars()
};
let line_len = line_end - line_start;
if col_idx >= line_len {
return Err(TargetError::InvalidPosition {
line,
col: Some(col),
});
}
Ok(line_start + col_idx)
}
#[cfg(test)]
#[path = "../../tests/target_matching.rs"]
mod target_matching;