Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/span/src/map.rs')
| -rw-r--r-- | crates/span/src/map.rs | 131 |
1 files changed, 131 insertions, 0 deletions
diff --git a/crates/span/src/map.rs b/crates/span/src/map.rs new file mode 100644 index 0000000000..d69df91b63 --- /dev/null +++ b/crates/span/src/map.rs @@ -0,0 +1,131 @@ +//! A map that maps a span to every position in a file. Usually maps a span to some range of positions. +//! Allows bidirectional lookup. + +use std::hash::Hash; + +use stdx::{always, itertools::Itertools}; +use syntax::{TextRange, TextSize}; +use vfs::FileId; + +use crate::{ErasedFileAstId, Span, SpanAnchor, SyntaxContextId, ROOT_ERASED_FILE_AST_ID}; + +/// Maps absolute text ranges for the corresponding file to the relevant span data. +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +pub struct SpanMap<S> { + spans: Vec<(TextSize, S)>, + // FIXME: Should be + // spans: Vec<(TextSize, crate::SyntaxContextId)>, +} + +impl<S: Copy> SpanMap<S> { + /// Creates a new empty [`SpanMap`]. + pub fn empty() -> Self { + Self { spans: Vec::new() } + } + + /// Finalizes the [`SpanMap`], shrinking its backing storage and validating that the offsets are + /// in order. + pub fn finish(&mut self) { + always!( + self.spans.iter().tuple_windows().all(|(a, b)| a.0 < b.0), + "spans are not in order" + ); + self.spans.shrink_to_fit(); + } + + /// Pushes a new span onto the [`SpanMap`]. + pub fn push(&mut self, offset: TextSize, span: S) { + if cfg!(debug_assertions) { + if let Some(&(last_offset, _)) = self.spans.last() { + assert!( + last_offset < offset, + "last_offset({last_offset:?}) must be smaller than offset({offset:?})" + ); + } + } + self.spans.push((offset, span)); + } + + /// Returns all [`TextRange`]s that correspond to the given span. + /// + /// Note this does a linear search through the entire backing vector. + pub fn ranges_with_span(&self, span: S) -> impl Iterator<Item = TextRange> + '_ + where + S: Eq, + { + // FIXME: This should ignore the syntax context! + self.spans.iter().enumerate().filter_map(move |(idx, &(end, s))| { + if s != span { + return None; + } + let start = idx.checked_sub(1).map_or(TextSize::new(0), |prev| self.spans[prev].0); + Some(TextRange::new(start, end)) + }) + } + + /// Returns the span at the given position. + pub fn span_at(&self, offset: TextSize) -> S { + let entry = self.spans.partition_point(|&(it, _)| it <= offset); + self.spans[entry].1 + } + + /// Returns the spans associated with the given range. + /// In other words, this will return all spans that correspond to all offsets within the given range. + pub fn spans_for_range(&self, range: TextRange) -> impl Iterator<Item = S> + '_ { + let (start, end) = (range.start(), range.end()); + let start_entry = self.spans.partition_point(|&(it, _)| it <= start); + let end_entry = self.spans[start_entry..].partition_point(|&(it, _)| it <= end); // FIXME: this might be wrong? + (&self.spans[start_entry..][..end_entry]).iter().map(|&(_, s)| s) + } + + pub fn iter(&self) -> impl Iterator<Item = (TextSize, S)> + '_ { + self.spans.iter().copied() + } +} + +#[derive(PartialEq, Eq, Hash, Debug)] +pub struct RealSpanMap { + file_id: FileId, + /// Invariant: Sorted vec over TextSize + // FIXME: SortedVec<(TextSize, ErasedFileAstId)>? + pairs: Box<[(TextSize, ErasedFileAstId)]>, + end: TextSize, +} + +impl RealSpanMap { + /// Creates a real file span map that returns absolute ranges (relative ranges to the root ast id). + pub fn absolute(file_id: FileId) -> Self { + RealSpanMap { + file_id, + pairs: Box::from([(TextSize::new(0), ROOT_ERASED_FILE_AST_ID)]), + end: TextSize::new(!0), + } + } + + pub fn from_file( + file_id: FileId, + pairs: Box<[(TextSize, ErasedFileAstId)]>, + end: TextSize, + ) -> Self { + Self { file_id, pairs, end } + } + + pub fn span_for_range(&self, range: TextRange) -> Span { + assert!( + range.end() <= self.end, + "range {range:?} goes beyond the end of the file {:?}", + self.end + ); + let start = range.start(); + let idx = self + .pairs + .binary_search_by(|&(it, _)| it.cmp(&start).then(std::cmp::Ordering::Less)) + .unwrap_err(); + let (offset, ast_id) = self.pairs[idx - 1]; + Span { + range: range - offset, + anchor: SpanAnchor { file_id: self.file_id, ast_id }, + ctx: SyntaxContextId::ROOT, + } + } +} |