textlane_core/models/
lines.rs1use crate::models::position::{Point, Position, UnistPoint, UnistPosition};
2
3#[derive(Clone, Debug)]
5pub struct Lines<'a> {
6 source: &'a str,
7 starts: Vec<usize>,
8 ascii: bool,
9 checkpoints: Vec<usize>,
11}
12
13const BLOCK: usize = 256;
14
15impl<'a> Lines<'a> {
16 pub fn new(source: &'a str) -> Self {
17 let bytes = source.as_bytes();
18 let mut starts = vec![0];
19 let mut index = 0;
20 while index < bytes.len() {
21 match bytes[index] {
22 b'\n' => starts.push(index + 1),
23 b'\r' if bytes.get(index + 1) != Some(&b'\n') => starts.push(index + 1),
24 _ => {}
25 }
26 index += 1;
27 }
28 let ascii = source.is_ascii();
29 let checkpoints = match ascii {
30 true => vec![],
31 false => {
32 let mut total = 0;
33 let mut checkpoints = Vec::with_capacity(bytes.len() / BLOCK + 1);
34 for block in bytes.chunks(BLOCK) {
35 checkpoints.push(total);
36 total += count_byte_units(block);
37 }
38 checkpoints
39 }
40 };
41 Self {
42 source,
43 starts,
44 ascii,
45 checkpoints,
46 }
47 }
48
49 pub fn to_unist_point(&self, point: Point) -> UnistPoint {
51 let offset = point.offset().min(self.source.len());
52 let line = self.starts.partition_point(|start| *start <= offset);
53 let start = self.starts[line - 1];
54 let extra = usize::from(point.virtual_spaces() > 0) + (point.offset() - offset);
57 UnistPoint {
58 line,
59 column: self.count_units_to(offset) - self.count_units_to(start) + 1 + extra,
60 offset: Some(self.count_units_to(offset) + extra),
61 }
62 }
63
64 pub fn to_unist_position(&self, position: &Position<'_>) -> UnistPosition {
66 match position {
67 Position::Span { start, end } => UnistPosition {
68 start: self.to_unist_point(*start),
69 end: self.to_unist_point(*end),
70 },
71 Position::Unist(position) => (*position).clone(),
72 }
73 }
74
75 fn count_units_to(&self, to: usize) -> usize {
77 if self.ascii {
78 return to;
79 }
80 let block = to / BLOCK;
81 let before = self.checkpoints.get(block).copied().unwrap_or_default();
82 let bytes = self.source.as_bytes();
83 before + count_byte_units(bytes.get(block * BLOCK..to).unwrap_or_default())
84 }
85}
86
87fn count_byte_units(bytes: &[u8]) -> usize {
89 bytes
90 .iter()
91 .map(|byte| match byte {
92 0x80..=0xBF => 0,
93 0xF0..=0xFF => 2,
94 _ => 1,
95 })
96 .sum()
97}
98
99#[cfg(test)]
100#[path = "lines_unit.rs"]
101mod unit;