//! 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)>, } 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) { 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 + '_ { 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 { // 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()) } }