use std::{collections::HashMap, sync::Arc};
use crate::{
grammar::{
LineMap,
location_walk::{walk_document_locations_mut, walk_inline_locations_mut},
},
model::{Document, InlineNode, Location, SourceRange},
};
pub(crate) fn remap_document_to_source(
doc: &mut Document,
ranges: &[SourceRange],
input: &str,
line_map: &LineMap,
) {
if ranges.is_empty() {
return;
}
let remapper = Remapper::new(ranges, input, line_map);
walk_document_locations_mut(doc, &mut |loc| remapper.remap_one(loc));
}
pub(crate) fn remap_inlines_to_source(
inlines: &mut [InlineNode<'_>],
ranges: &[SourceRange],
input: &str,
line_map: &LineMap,
) {
if ranges.is_empty() {
return;
}
let remapper = Remapper::new(ranges, input, line_map);
for node in inlines {
walk_inline_locations_mut(node, &mut |loc| remapper.remap_one(loc));
}
}
struct Remapper<'a> {
input: &'a str,
line_map: &'a LineMap,
ranges: &'a [SourceRange],
preproc_start_lines: Vec<u32>,
chains: HashMap<&'a [String], Arc<Vec<String>>>,
boundaries: Vec<usize>,
segment_cover: Vec<Option<usize>>,
}
impl<'a> Remapper<'a> {
fn new(ranges: &'a [SourceRange], input: &'a str, line_map: &'a LineMap) -> Self {
let mut chains: HashMap<&'a [String], Arc<Vec<String>>> = HashMap::new();
for range in ranges {
if !range.file_chain.is_empty() {
chains
.entry(range.file_chain.as_slice())
.or_insert_with(|| Arc::new(range.file_chain.clone()));
}
}
let mut boundaries = Vec::with_capacity(ranges.len() * 2);
for range in ranges {
boundaries.push(range.start_offset);
boundaries.push(range.end_offset);
}
boundaries.sort_unstable();
boundaries.dedup();
let segment_cover = Self::build_segment_cover(ranges, &boundaries);
let preproc_start_lines = ranges
.iter()
.map(|range| line_map.offset_to_position(range.start_offset, input).line)
.collect();
Self {
input,
line_map,
ranges,
preproc_start_lines,
chains,
boundaries,
segment_cover,
}
}
fn build_segment_cover(ranges: &[SourceRange], boundaries: &[usize]) -> Vec<Option<usize>> {
let mut cover = vec![None; boundaries.len().saturating_sub(1)];
for (index, range) in ranges.iter().enumerate() {
let first = boundaries.partition_point(|&b| b < range.start_offset);
let end = boundaries.partition_point(|&b| b < range.end_offset);
if let Some(slots) = cover.get_mut(first..end) {
for slot in slots {
*slot = Some(index);
}
}
}
cover
}
fn map_offset(&self, abs: usize) -> Option<(usize, u32, &'a SourceRange)> {
let range_index = covering_range_index(&self.boundaries, &self.segment_cover, abs)?;
let range = self.ranges.get(range_index)?;
let preproc_start_line = *self.preproc_start_lines.get(range_index)?;
let source_line = self.line_map.source_line_from(
u32::try_from(range.start_line).unwrap_or(u32::MAX),
preproc_start_line,
self.input,
abs,
);
Some((range.source_offset(abs), source_line, range))
}
fn range_file(&self, range: &SourceRange) -> Option<Arc<Vec<String>>> {
self.chains.get(range.file_chain.as_slice()).cloned()
}
fn remap_one(&self, loc: &mut Location) {
let Some((src_start, start_line, start_range)) = self.map_offset(loc.absolute_start) else {
return;
};
let end_probe = loc
.absolute_end
.min(self.input.len().saturating_sub(1))
.max(loc.absolute_start);
let (src_end, end_line, end_range) =
self.map_offset(end_probe)
.unwrap_or((src_start, start_line, start_range));
loc.start.line = start_line;
loc.end.line = end_line.max(start_line);
loc.start.column = shift_column(loc.start.column, start_range.column_shift);
loc.end.column = shift_column(loc.end.column, end_range.column_shift);
let start_file = self.range_file(start_range);
let end_file = if std::ptr::eq(start_range, end_range) {
start_file.clone()
} else {
self.range_file(end_range)
};
loc.start.file = start_file;
loc.end.file = end_file;
if start_range.column_shift == 0 {
loc.absolute_start = src_start;
}
if end_range.column_shift == 0 {
loc.absolute_end = src_end.max(loc.absolute_start);
} else {
loc.absolute_end = loc.absolute_end.max(loc.absolute_start);
}
}
}
fn covering_range_index(
boundaries: &[usize],
segment_cover: &[Option<usize>],
abs: usize,
) -> Option<usize> {
let &first = boundaries.first()?;
let &last = boundaries.last()?;
if abs < first || abs >= last {
return None;
}
let segment = boundaries.partition_point(|&c| c <= abs).checked_sub(1)?;
*segment_cover.get(segment)?
}
fn shift_column(column: u32, column_shift: isize) -> u32 {
if column_shift == 0 {
return column;
}
let shifted = isize::try_from(column).unwrap_or(isize::MAX) - column_shift;
u32::try_from(shifted.max(1)).unwrap_or(1)
}
#[cfg(test)]
mod tests {
use super::*;
fn range(start: usize, end: usize) -> SourceRange {
SourceRange {
start_offset: start,
end_offset: end,
file: None,
file_chain: Vec::new(),
start_line: 1,
source_start_offset: 0,
column_shift: 0,
}
}
#[test]
fn segment_cover_matches_linear_scan() {
let raw = [
range(0, 100), range(10, 30), range(20, 25), range(50, 70), range(60, 110), range(200, 210), range(40, 40), ];
let mut boundaries: Vec<usize> = raw
.iter()
.flat_map(|r| [r.start_offset, r.end_offset])
.collect();
boundaries.sort_unstable();
boundaries.dedup();
let segment_cover = Remapper::build_segment_cover(&raw, &boundaries);
for abs in 0..220 {
let got = covering_range_index(&boundaries, &segment_cover, abs);
let expected = raw
.iter()
.enumerate()
.rev()
.find(|(_, r)| r.contains(abs))
.map(|(index, _)| index);
assert_eq!(got, expected, "offset {abs}");
}
}
}