use crate::fuzzy_matcher::MatchIndices;
use crate::{MatchEngine, MatchEngineFactory, MatchRange, MatchResult, SkimItem};
use std::fmt::{Display, Error, Formatter};
pub struct SplitMatchEngine {
before_engine: Box<dyn MatchEngine>,
after_engine: Box<dyn MatchEngine>,
delimiter: char,
}
impl SplitMatchEngine {
pub fn new(before_engine: Box<dyn MatchEngine>, after_engine: Box<dyn MatchEngine>, delimiter: char) -> Self {
Self {
before_engine,
after_engine,
delimiter,
}
}
}
impl MatchEngine for SplitMatchEngine {
fn match_item(&self, item: &dyn SkimItem) -> Option<MatchResult> {
let text = item.text();
let delimiter_char_idx = text.chars().position(|c| c == self.delimiter)?;
let delimiter_byte_pos = text.char_indices().nth(delimiter_char_idx).map(|(i, _)| i)?;
let text_before = &text[..delimiter_byte_pos];
let text_after = &text[delimiter_byte_pos + self.delimiter.len_utf8()..];
let before_item: &dyn SkimItem = &StringItem(text_before.to_string());
let after_item: &dyn SkimItem = &StringItem(text_after.to_string());
let before_result = self.before_engine.match_item(before_item)?;
let after_result = self.after_engine.match_item(after_item)?;
let rank = before_result.rank;
let mut combined_indices: MatchIndices = match before_result.matched_range {
MatchRange::Chars(indices) => indices,
MatchRange::ByteRange(start, end) => {
text_before
.char_indices()
.enumerate()
.filter(|(_, (byte_idx, _))| *byte_idx >= start && *byte_idx < end)
.map(|(char_idx, _)| char_idx)
.collect()
}
};
let offset = delimiter_char_idx + 1;
let after_indices: MatchIndices = match after_result.matched_range {
MatchRange::Chars(indices) => indices.into_iter().map(|i| i + offset).collect(),
MatchRange::ByteRange(start, end) => {
text_after
.char_indices()
.enumerate()
.filter(|(_, (byte_idx, _))| *byte_idx >= start && *byte_idx < end)
.map(|(char_idx, _)| char_idx + offset)
.collect()
}
};
combined_indices.extend(after_indices);
combined_indices.sort();
combined_indices.dedup();
Some(MatchResult {
rank,
matched_range: MatchRange::Chars(combined_indices),
})
}
}
impl Display for SplitMatchEngine {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(
f,
"(Split[{}]: {} | {})",
self.delimiter, self.before_engine, self.after_engine
)
}
}
struct StringItem(String);
impl SkimItem for StringItem {
fn text(&self) -> std::borrow::Cow<'_, str> {
std::borrow::Cow::Borrowed(&self.0)
}
}
pub struct SplitMatchEngineFactory {
inner: Box<dyn MatchEngineFactory>,
delimiter: char,
}
impl SplitMatchEngineFactory {
pub fn new(inner: impl MatchEngineFactory + 'static, delimiter: char) -> Self {
Self {
inner: Box::new(inner),
delimiter,
}
}
}
impl MatchEngineFactory for SplitMatchEngineFactory {
fn create_engine_with_case(&self, query: &str, case: crate::CaseMatching) -> Box<dyn MatchEngine> {
if let Some(delimiter_pos) = query.find(self.delimiter) {
let query_before = &query[..delimiter_pos];
let query_after = &query[delimiter_pos + self.delimiter.len_utf8()..];
let before_engine = self.inner.create_engine_with_case(query_before, case);
let after_engine = self.inner.create_engine_with_case(query_after, case);
Box::new(SplitMatchEngine::new(before_engine, after_engine, self.delimiter))
} else {
self.inner.create_engine_with_case(query, case)
}
}
}