Markdown parser fork with extended syntax for personal use.
at hack 210 lines 6.5 kB view raw
1//! Deal with positions in a file. 2//! 3//! * Convert between byte indices and unist points. 4//! * Convert between byte indices into a string which is built up of several 5//! slices in a whole document, and byte indices into that whole document. 6 7use crate::unist::Point; 8use alloc::{vec, vec::Vec}; 9 10/// Each stop represents a new slice, which contains the byte index into the 11/// corresponding string where the slice starts (`0`), and the byte index into 12/// the whole document where that slice starts (`1`). 13pub type Stop = (usize, usize); 14 15#[derive(Debug)] 16pub struct Location { 17 /// List, where each index is a line number (0-based), and each value is 18 /// the byte index *after* where the line ends. 19 indices: Vec<usize>, 20} 21 22impl Location { 23 /// Get an index for the given `bytes`. 24 /// 25 /// Port of <https://github.com/vfile/vfile-location/blob/main/index.js> 26 #[must_use] 27 pub fn new(bytes: &[u8]) -> Self { 28 let mut index = 0; 29 let mut location_index = Self { indices: vec![] }; 30 31 while index < bytes.len() { 32 if bytes[index] == b'\r' { 33 if index + 1 < bytes.len() && bytes[index + 1] == b'\n' { 34 location_index.indices.push(index + 2); 35 index += 1; 36 } else { 37 location_index.indices.push(index + 1); 38 } 39 } else if bytes[index] == b'\n' { 40 location_index.indices.push(index + 1); 41 } 42 43 index += 1; 44 } 45 46 location_index.indices.push(index + 1); 47 location_index 48 } 49 50 /// Get the line and column-based `point` for `offset` in the bound indices. 51 /// 52 /// Returns `None` when given out of bounds input. 53 /// 54 /// Port of <https://github.com/vfile/vfile-location/blob/main/index.js> 55 #[must_use] 56 pub fn to_point(&self, offset: usize) -> Option<Point> { 57 let mut index = 0; 58 59 if let Some(end) = self.indices.last() { 60 if offset < *end { 61 while index < self.indices.len() { 62 if self.indices[index] > offset { 63 break; 64 } 65 66 index += 1; 67 } 68 69 let previous = if index > 0 { 70 self.indices[index - 1] 71 } else { 72 0 73 }; 74 return Some(Point::new(index + 1, offset + 1 - previous, offset)); 75 } 76 } 77 78 None 79 } 80 81 /// Like `to_point`, but takes a relative offset from a certain string 82 /// instead of an absolute offset into the whole document. 83 /// 84 /// The relative offset is made absolute based on `stops`, which represent 85 /// where that certain string is in the whole document. 86 #[must_use] 87 pub fn relative_to_point(&self, stops: &[Stop], relative: usize) -> Option<Point> { 88 Location::relative_to_absolute(stops, relative).and_then(|absolute| self.to_point(absolute)) 89 } 90 91 /// Turn a relative offset into an absolute offset. 92 #[must_use] 93 pub fn relative_to_absolute(stops: &[Stop], relative: usize) -> Option<usize> { 94 let mut index = 0; 95 96 while index < stops.len() && stops[index].0 <= relative { 97 index += 1; 98 } 99 100 // There are no points: that only occurs if there was an empty string. 101 if index == 0 { 102 None 103 } else { 104 let (stop_relative, stop_absolute) = &stops[index - 1]; 105 Some(stop_absolute + (relative - stop_relative)) 106 } 107 } 108} 109 110#[cfg(test)] 111mod tests { 112 use super::*; 113 114 #[test] 115 fn test_location_lf() { 116 let location = Location::new("ab\nc".as_bytes()); 117 assert_eq!( 118 location.to_point(0), // `a` 119 Some(Point::new(1, 1, 0)), 120 "should support some points (1)" 121 ); 122 assert_eq!( 123 location.to_point(1), // `b` 124 Some(Point::new(1, 2, 1)), 125 "should support some points (2)" 126 ); 127 assert_eq!( 128 location.to_point(2), // `\n` 129 Some(Point::new(1, 3, 2)), 130 "should support some points (3)" 131 ); 132 assert_eq!( 133 location.to_point(3), // `c` 134 Some(Point::new(2, 1, 3)), 135 "should support some points (4)" 136 ); 137 assert_eq!( 138 location.to_point(4), // EOF 139 // Still gets a point, so that we can represent positions of things 140 // that end at the last character, the `c` end at `2:2`. 141 Some(Point::new(2, 2, 4)), 142 "should support some points (5)" 143 ); 144 assert_eq!( 145 location.to_point(5), // Out of bounds 146 None, 147 "should support some points (6)" 148 ); 149 } 150 151 #[test] 152 fn test_location_cr() { 153 let location = Location::new("a\rb".as_bytes()); 154 assert_eq!( 155 location.to_point(0), // `a` 156 Some(Point::new(1, 1, 0)), 157 "should support some points (1)" 158 ); 159 assert_eq!( 160 location.to_point(1), // `\r` 161 Some(Point::new(1, 2, 1)), 162 "should support some points (2)" 163 ); 164 assert_eq!( 165 location.to_point(2), // `b` 166 Some(Point::new(2, 1, 2)), 167 "should support some points (3)" 168 ); 169 } 170 171 #[test] 172 fn test_location_cr_lf() { 173 let location = Location::new("a\r\nb".as_bytes()); 174 assert_eq!( 175 location.to_point(0), // `a` 176 Some(Point::new(1, 1, 0)), 177 "should support some points (1)" 178 ); 179 assert_eq!( 180 location.to_point(1), // `\r` 181 Some(Point::new(1, 2, 1)), 182 "should support some points (2)" 183 ); 184 assert_eq!( 185 location.to_point(2), // `\n` 186 Some(Point::new(1, 3, 2)), 187 "should support some points (3)" 188 ); 189 assert_eq!( 190 location.to_point(3), // `b` 191 Some(Point::new(2, 1, 3)), 192 "should support some points (4)" 193 ); 194 } 195 #[test] 196 fn test_empty() { 197 let location = Location::new("".as_bytes()); 198 assert_eq!(location.to_point(0), Some(Point::new(1, 1, 0)), "to_point"); 199 assert_eq!( 200 location.relative_to_point(&[], 0), 201 None, 202 "relative_to_point" 203 ); 204 assert_eq!( 205 Location::relative_to_absolute(&[], 0), 206 None, 207 "relative_to_absolute" 208 ); 209 } 210}