1use parking_lot::Mutex;
2
3use super::fold_map::{
4 Chunks as InputChunks, Edit as InputEdit, HighlightedChunks as InputHighlightedChunks,
5 OutputOffset as InputOffset, OutputPoint as InputPoint, Snapshot as InputSnapshot,
6};
7use crate::{settings::StyleId, util::Bias};
8use std::{
9 mem,
10 ops::{AddAssign, Range},
11};
12
13pub struct TabMap(Mutex<Snapshot>);
14
15impl TabMap {
16 pub fn new(input: InputSnapshot, tab_size: usize) -> Self {
17 Self(Mutex::new(Snapshot { input, tab_size }))
18 }
19
20 pub fn sync(
21 &self,
22 snapshot: InputSnapshot,
23 input_edits: Vec<InputEdit>,
24 ) -> (Snapshot, Vec<Edit>) {
25 let mut old_snapshot = self.0.lock();
26 let new_snapshot = Snapshot {
27 input: snapshot,
28 tab_size: old_snapshot.tab_size,
29 };
30
31 let mut output_edits = Vec::with_capacity(input_edits.len());
32 for input_edit in input_edits {
33 let old_start = input_edit.old_bytes.start.to_point(&old_snapshot.input);
34 let old_end = input_edit.old_bytes.end.to_point(&old_snapshot.input);
35 let new_start = input_edit.new_bytes.start.to_point(&new_snapshot.input);
36 let new_end = input_edit.new_bytes.end.to_point(&new_snapshot.input);
37 output_edits.push(Edit {
38 old_lines: old_snapshot.to_output_point(old_start)
39 ..old_snapshot.to_output_point(old_end),
40 new_lines: new_snapshot.to_output_point(new_start)
41 ..new_snapshot.to_output_point(new_end),
42 });
43 }
44
45 *old_snapshot = new_snapshot;
46 (old_snapshot.clone(), output_edits)
47 }
48}
49
50#[derive(Clone)]
51pub struct Snapshot {
52 input: InputSnapshot,
53 tab_size: usize,
54}
55
56impl Snapshot {
57 pub fn text_summary(&self) -> TextSummary {
58 // TODO: expand tabs on first and last line, ignoring the longest row.
59 let summary = self.input.text_summary();
60 TextSummary {
61 lines: summary.lines,
62 first_line_chars: summary.first_line_chars,
63 last_line_chars: summary.last_line_chars,
64 longest_row: summary.longest_row,
65 longest_row_chars: summary.longest_row_chars,
66 }
67 }
68
69 pub fn text_summary_for_rows(&self, rows: Range<u32>) -> TextSummary {
70 // TODO: expand tabs on first and last line, ignoring the longest row.
71 let range = InputPoint::new(rows.start, 0)..InputPoint::new(rows.end, 0);
72 let summary = self.input.text_summary_for_range(range);
73 TextSummary {
74 lines: summary.lines,
75 first_line_chars: summary.first_line_chars,
76 last_line_chars: summary.last_line_chars,
77 longest_row: summary.longest_row,
78 longest_row_chars: summary.longest_row_chars,
79 }
80 }
81
82 pub fn version(&self) -> usize {
83 self.input.version
84 }
85
86 pub fn chunks_at(&self, point: OutputPoint) -> Chunks {
87 let (point, expanded_char_column, to_next_stop) = self.to_input_point(point, Bias::Left);
88 let fold_chunks = self.input.chunks_at(self.input.to_output_offset(point));
89 Chunks {
90 fold_chunks,
91 column: expanded_char_column,
92 tab_size: self.tab_size,
93 chunk: &SPACES[0..to_next_stop],
94 skip_leading_tab: to_next_stop > 0,
95 }
96 }
97
98 pub fn highlighted_chunks_for_rows(&mut self, rows: Range<u32>) -> HighlightedChunks {
99 let start = self.input.to_output_offset(InputPoint::new(rows.start, 0));
100 let end = self
101 .input
102 .to_output_offset(InputPoint::new(rows.end, 0).min(self.input.max_point()));
103 HighlightedChunks {
104 input_chunks: self.input.highlighted_chunks(start..end),
105 column: 0,
106 tab_size: self.tab_size,
107 chunk: "",
108 style_id: Default::default(),
109 }
110 }
111
112 #[cfg(test)]
113 pub fn text(&self) -> String {
114 self.chunks_at(Default::default()).collect()
115 }
116
117 pub fn len(&self) -> OutputOffset {
118 self.to_output_offset(self.input.len())
119 }
120
121 pub fn line_len(&self, row: u32) -> u32 {
122 self.to_output_point(InputPoint::new(row, self.input.line_len(row)))
123 .column()
124 }
125
126 pub fn longest_row(&self) -> u32 {
127 // TODO: Account for tab expansion.
128 self.input.longest_row()
129 }
130
131 pub fn max_point(&self) -> OutputPoint {
132 self.to_output_point(self.input.max_point())
133 }
134
135 pub fn clip_point(&self, point: OutputPoint, bias: Bias) -> OutputPoint {
136 self.to_output_point(
137 self.input
138 .clip_point(self.to_input_point(point, bias).0, bias),
139 )
140 }
141
142 pub fn to_output_offset(&self, input_offset: InputOffset) -> OutputOffset {
143 let input_point = input_offset.to_point(&self.input);
144 let input_row_start_offset = self
145 .input
146 .to_output_offset(InputPoint::new(input_point.row(), 0));
147 let output_point = self.to_output_point(input_point);
148 OutputOffset(input_row_start_offset.0 + output_point.column() as usize)
149 }
150
151 pub fn to_output_point(&self, input: InputPoint) -> OutputPoint {
152 let chars = self.input.chars_at(InputPoint::new(input.row(), 0));
153 let expanded = Self::expand_tabs(chars, input.column() as usize, self.tab_size);
154 OutputPoint::new(input.row(), expanded as u32)
155 }
156
157 pub fn to_input_point(&self, output: OutputPoint, bias: Bias) -> (InputPoint, usize, usize) {
158 let chars = self.input.chars_at(InputPoint::new(output.row(), 0));
159 let expanded = output.column() as usize;
160 let (collapsed, expanded_char_column, to_next_stop) =
161 Self::collapse_tabs(chars, expanded, bias, self.tab_size);
162 (
163 InputPoint::new(output.row(), collapsed as u32),
164 expanded_char_column,
165 to_next_stop,
166 )
167 }
168
169 fn expand_tabs(chars: impl Iterator<Item = char>, column: usize, tab_size: usize) -> usize {
170 let mut expanded_chars = 0;
171 let mut expanded_bytes = 0;
172 let mut collapsed_bytes = 0;
173 for c in chars {
174 if collapsed_bytes == column {
175 break;
176 }
177 if c == '\t' {
178 let tab_len = tab_size - expanded_chars % tab_size;
179 expanded_bytes += tab_len;
180 expanded_chars += tab_len;
181 } else {
182 expanded_bytes += c.len_utf8();
183 expanded_chars += 1;
184 }
185 collapsed_bytes += c.len_utf8();
186 }
187 expanded_bytes
188 }
189
190 fn collapse_tabs(
191 mut chars: impl Iterator<Item = char>,
192 column: usize,
193 bias: Bias,
194 tab_size: usize,
195 ) -> (usize, usize, usize) {
196 let mut expanded_bytes = 0;
197 let mut expanded_chars = 0;
198 let mut collapsed_bytes = 0;
199 while let Some(c) = chars.next() {
200 if expanded_bytes >= column {
201 break;
202 }
203
204 if c == '\t' {
205 let tab_len = tab_size - (expanded_chars % tab_size);
206 expanded_chars += tab_len;
207 expanded_bytes += tab_len;
208 if expanded_bytes > column {
209 expanded_chars -= expanded_bytes - column;
210 return match bias {
211 Bias::Left => (collapsed_bytes, expanded_chars, expanded_bytes - column),
212 Bias::Right => (collapsed_bytes + 1, expanded_chars, 0),
213 };
214 }
215 } else {
216 expanded_chars += 1;
217 expanded_bytes += c.len_utf8();
218 }
219
220 if expanded_bytes > column && matches!(bias, Bias::Left) {
221 expanded_chars -= 1;
222 break;
223 }
224
225 collapsed_bytes += c.len_utf8();
226 }
227 (collapsed_bytes, expanded_chars, 0)
228 }
229}
230
231#[derive(Copy, Clone, Debug, PartialEq, Eq)]
232pub struct OutputOffset(pub usize);
233
234#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
235pub struct OutputPoint(super::Point);
236
237impl OutputPoint {
238 pub fn new(row: u32, column: u32) -> Self {
239 Self(super::Point::new(row, column))
240 }
241
242 pub fn zero() -> Self {
243 Self::new(0, 0)
244 }
245
246 pub fn row(self) -> u32 {
247 self.0.row
248 }
249
250 pub fn column(self) -> u32 {
251 self.0.column
252 }
253
254 pub fn row_mut(&mut self) -> &mut u32 {
255 &mut self.0.row
256 }
257
258 pub fn column_mut(&mut self) -> &mut u32 {
259 &mut self.0.column
260 }
261}
262
263impl AddAssign<Self> for OutputPoint {
264 fn add_assign(&mut self, rhs: Self) {
265 self.0 += rhs.0;
266 }
267}
268
269#[derive(Clone, Debug, PartialEq, Eq)]
270pub struct Edit {
271 pub old_lines: Range<OutputPoint>,
272 pub new_lines: Range<OutputPoint>,
273}
274
275#[derive(Clone, Debug, Default, Eq, PartialEq)]
276pub struct TextSummary {
277 pub lines: super::Point,
278 pub first_line_chars: u32,
279 pub last_line_chars: u32,
280 pub longest_row: u32,
281 pub longest_row_chars: u32,
282}
283
284impl<'a> std::ops::AddAssign<&'a Self> for TextSummary {
285 fn add_assign(&mut self, other: &'a Self) {
286 let joined_chars = self.last_line_chars + other.first_line_chars;
287 if joined_chars > self.longest_row_chars {
288 self.longest_row = self.lines.row;
289 self.longest_row_chars = joined_chars;
290 }
291 if other.longest_row_chars > self.longest_row_chars {
292 self.longest_row = self.lines.row + other.longest_row;
293 self.longest_row_chars = other.longest_row_chars;
294 }
295
296 if self.lines.row == 0 {
297 self.first_line_chars += other.first_line_chars;
298 }
299
300 if other.lines.row == 0 {
301 self.last_line_chars += other.first_line_chars;
302 } else {
303 self.last_line_chars = other.last_line_chars;
304 }
305
306 self.lines += &other.lines;
307 }
308}
309
310// Handles a tab width <= 16
311const SPACES: &'static str = " ";
312
313pub struct Chunks<'a> {
314 fold_chunks: InputChunks<'a>,
315 chunk: &'a str,
316 column: usize,
317 tab_size: usize,
318 skip_leading_tab: bool,
319}
320
321impl<'a> Iterator for Chunks<'a> {
322 type Item = &'a str;
323
324 fn next(&mut self) -> Option<Self::Item> {
325 if self.chunk.is_empty() {
326 if let Some(chunk) = self.fold_chunks.next() {
327 self.chunk = chunk;
328 if self.skip_leading_tab {
329 self.chunk = &self.chunk[1..];
330 self.skip_leading_tab = false;
331 }
332 } else {
333 return None;
334 }
335 }
336
337 for (ix, c) in self.chunk.char_indices() {
338 match c {
339 '\t' => {
340 if ix > 0 {
341 let (prefix, suffix) = self.chunk.split_at(ix);
342 self.chunk = suffix;
343 return Some(prefix);
344 } else {
345 self.chunk = &self.chunk[1..];
346 let len = self.tab_size - self.column % self.tab_size;
347 self.column += len;
348 return Some(&SPACES[0..len]);
349 }
350 }
351 '\n' => self.column = 0,
352 _ => self.column += 1,
353 }
354 }
355
356 let result = Some(self.chunk);
357 self.chunk = "";
358 result
359 }
360}
361
362pub struct HighlightedChunks<'a> {
363 input_chunks: InputHighlightedChunks<'a>,
364 chunk: &'a str,
365 style_id: StyleId,
366 column: usize,
367 tab_size: usize,
368}
369
370impl<'a> Iterator for HighlightedChunks<'a> {
371 type Item = (&'a str, StyleId);
372
373 fn next(&mut self) -> Option<Self::Item> {
374 if self.chunk.is_empty() {
375 if let Some((chunk, style_id)) = self.input_chunks.next() {
376 self.chunk = chunk;
377 self.style_id = style_id;
378 } else {
379 return None;
380 }
381 }
382
383 for (ix, c) in self.chunk.char_indices() {
384 match c {
385 '\t' => {
386 if ix > 0 {
387 let (prefix, suffix) = self.chunk.split_at(ix);
388 self.chunk = suffix;
389 return Some((prefix, self.style_id));
390 } else {
391 self.chunk = &self.chunk[1..];
392 let len = self.tab_size - self.column % self.tab_size;
393 self.column += len;
394 return Some((&SPACES[0..len], self.style_id));
395 }
396 }
397 '\n' => self.column = 0,
398 _ => self.column += 1,
399 }
400 }
401
402 Some((mem::take(&mut self.chunk), mem::take(&mut self.style_id)))
403 }
404}
405
406#[cfg(test)]
407mod tests {
408 use super::*;
409
410 #[test]
411 fn test_expand_tabs() {
412 assert_eq!(Snapshot::expand_tabs("\t".chars(), 0, 4), 0);
413 assert_eq!(Snapshot::expand_tabs("\t".chars(), 1, 4), 4);
414 assert_eq!(Snapshot::expand_tabs("\ta".chars(), 2, 4), 5);
415 }
416}