blob: ef84888b83b47203aa8575eb0e7bda6b278a693a [file] [log] [blame]
//! Maps between ranges in documents.
use std::cmp::Ordering;
use stdx::equal_range_by;
use syntax::{TextRange, TextSize};
#[derive(Default)]
pub struct RangeMapper {
buf: String,
ranges: Vec<(TextRange, Option<TextRange>)>,
}
impl RangeMapper {
pub fn add(&mut self, text: &str, source_range: TextRange) {
let len = TextSize::of(text);
assert_eq!(len, source_range.len());
self.add_impl(text, Some(source_range.start()));
}
pub fn add_unmapped(&mut self, text: &str) {
self.add_impl(text, None);
}
fn add_impl(&mut self, text: &str, source: Option<TextSize>) {
let len = TextSize::of(text);
let target_range = TextRange::at(TextSize::of(&self.buf), len);
self.ranges.push((target_range, source.map(|it| TextRange::at(it, len))));
self.buf.push_str(text);
}
pub fn take_text(&mut self) -> String {
std::mem::take(&mut self.buf)
}
pub fn map_range_up(&self, range: TextRange) -> impl Iterator<Item = TextRange> + '_ {
equal_range_by(&self.ranges, |&(r, _)| {
if range.is_empty() && r.contains(range.start()) {
Ordering::Equal
} else {
TextRange::ordering(r, range)
}
})
.filter_map(move |i| {
let (target_range, source_range) = self.ranges[i];
let intersection = target_range.intersect(range).unwrap();
let source_range = source_range?;
Some(intersection - target_range.start() + source_range.start())
})
}
pub fn map_offset_down(&self, offset: TextSize) -> Option<TextSize> {
// Using a binary search here is a bit complicated because of the `None` entries.
// But the number of lines in fixtures is usually low.
let (target_range, source_range) =
self.ranges.iter().find_map(|&(target_range, source_range)| {
let source_range = source_range?;
if !source_range.contains(offset) {
return None;
}
Some((target_range, source_range))
})?;
Some(offset - source_range.start() + target_range.start())
}
}