use colored::Colorize;
use std::cmp;
use rustc_hash::FxHashMap;
#[derive(Debug, Eq, PartialEq)]
pub struct QueryResult {
pub captures: Vec<CaptureResult>,
pub vars: FxHashMap<String, usize>,
function: std::ops::Range<usize>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CaptureResult {
pub range: std::ops::Range<usize>,
pub query_id: usize,
pub capture_idx: u32,
}
impl<'a, 'b> QueryResult {
pub fn new(
captures: Vec<CaptureResult>,
vars: FxHashMap<String, usize>,
function: std::ops::Range<usize>,
) -> QueryResult {
QueryResult {
captures,
vars,
function,
}
}
pub fn start_offset(&self) -> usize {
self.function.start
}
pub fn display(&self, source: &'b str, before: usize, after: usize) -> String {
let mut result = String::new();
let mut header_end = linebreak_index(source, self.function.start, 1, false);
if self.captures.len() > 1 && self.captures[1].range.start > self.function.start {
header_end = cmp::min(header_end, self.captures[1].range.start - 1);
}
result += &source[self.function.start..header_end];
let mut offset = header_end;
let mut sorted = self.captures.clone();
sorted.sort_by(|a, b| a.range.start.cmp(&b.range.start));
let mut clean_ranges: Vec<std::ops::Range<usize>> = Vec::with_capacity(self.captures.len());
for r in sorted.into_iter().skip(1).map(|c| c.range) {
if !clean_ranges.is_empty() && clean_ranges.last().unwrap().contains(&r.start) {
continue;
}
clean_ranges.push(r.clone());
}
for (index, r) in clean_ranges.iter().enumerate() {
if r.start <= offset {
continue;
}
let start = linebreak_index(source, r.start, before, true);
let mut end = linebreak_index(source, r.end, after, false);
if index < clean_ranges.len() - 1 && r.end < clean_ranges[index + 1].start {
end = cmp::min(end, clean_ranges[index + 1].start - 1);
}
end = cmp::min(end, self.function.end);
if start <= offset {
result += &source[offset..r.start];
} else {
result += "..";
result += &source[start..r.start];
}
result += &format!("{}", &source[r.start..r.end].red());
result += &source[r.end..end];
offset = end;
}
if offset < self.function.end {
let last_line = linebreak_index(source, self.function.end, 0, true);
result += "..";
result += &source[last_line..self.function.end];
}
result
}
pub fn value(&self, var: &str, source: &'b str) -> Option<&'b str> {
match self.vars.get(var) {
None => None,
Some(i) => Some(&source[self.captures[*i].range.clone()]),
}
}
pub fn merge(
&self,
other: &QueryResult,
source: &str,
enforce_order: bool,
) -> Option<QueryResult> {
let mut vars = self.vars.clone();
let mut captures = self.captures.clone();
if enforce_order {
if other
.captures
.iter()
.any(|r| self.captures.iter().any(|r2| r.range.start <= r2.range.end))
{
return None;
}
}
captures.extend(other.captures.clone());
for (k, v) in other.vars.iter() {
match self.value(k, source) {
None => {
vars.insert(k.clone(), v + self.captures.len());
}
Some(s) => {
if s != other.value(k, source).unwrap() {
return None;
}
}
}
}
Some(QueryResult::new(captures, vars, self.function.clone()))
}
pub fn chainable(&self, source: &str, other: &QueryResult, other_source: &str) -> bool {
!other.vars.iter().any(|(k, _)| {
if let Some(value) = self.value(k, source) {
value != other.value(k, other_source).unwrap()
} else {
false
}
})
}
pub fn get_capture_result(&self, query_id: usize, capture_idx: u32) -> Option<&CaptureResult> {
self.captures
.iter()
.find(|c| c.capture_idx == capture_idx && c.query_id == query_id)
}
}
pub fn merge_results(
results: &[QueryResult],
sub_results: &[QueryResult],
source: &str,
enforce_order: bool,
) -> Vec<QueryResult> {
results
.iter()
.flat_map(|r| {
sub_results
.iter()
.filter_map(move |s| r.merge(s, source, enforce_order))
})
.collect()
}
fn linebreak_index(source: &str, index: usize, count: usize, backwards: bool) -> usize {
let length = source.len();
let mut f;
let mut b;
let iter: &mut dyn Iterator<Item = (usize, char)> = if !backwards {
f = source[index..length].char_indices();
&mut f
} else {
b = source[..index].char_indices().rev();
&mut b
};
let newline_index = iter.filter(|(_, c)| *c == '\n').nth(count);
match newline_index {
Some((i, _)) if !backwards => cmp::min(length, index + i + 1),
Some((i, _)) => i,
None if !backwards => length,
None => 0,
}
}
#[test]
fn test_linebreak_index() {
let input = "aaa\nbbb\nccc\nd";
let index = input.find('b').unwrap();
assert_eq!(linebreak_index(&input, index, 1, true), 0);
assert_eq!(
linebreak_index(&input, index, 1, false),
input.find('d').unwrap()
);
assert_eq!(linebreak_index(&input, index, 5, false), input.len());
assert_eq!(linebreak_index(&input, index, 4, true), 0);
}