Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide-db/src/range_mapper.rs')
-rw-r--r--crates/ide-db/src/range_mapper.rs65
1 files changed, 65 insertions, 0 deletions
diff --git a/crates/ide-db/src/range_mapper.rs b/crates/ide-db/src/range_mapper.rs
new file mode 100644
index 0000000000..ef84888b83
--- /dev/null
+++ b/crates/ide-db/src/range_mapper.rs
@@ -0,0 +1,65 @@
+//! 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())
+ }
+}