1use crate::tokens::Span;
6
7use super::types::{PrioritizedSpan, SourcePos};
8
9#[allow(missing_docs)]
11pub fn dummy_span() -> Span {
12 Span::new(0, 0, 1, 1)
13}
14#[allow(missing_docs)]
16pub fn span_contains(span: &Span, pos: usize) -> bool {
17 pos >= span.start && pos < span.end
18}
19#[allow(missing_docs)]
23pub fn merge_spans(spans: &[Span]) -> Option<Span> {
24 if spans.is_empty() {
25 return None;
26 }
27 let mut result = spans[0].clone();
28 for span in &spans[1..] {
29 result = result.merge(span);
30 }
31 Some(result)
32}
33#[allow(missing_docs)]
35pub fn span_len(span: &Span) -> usize {
36 span.end.saturating_sub(span.start)
37}
38#[allow(missing_docs)]
40pub fn span_start_pos(span: &Span) -> SourcePos {
41 SourcePos::new(span.line, span.column)
42}
43#[allow(missing_docs)]
45pub fn span_start(span: &Span) -> usize {
46 span.start
47}
48#[allow(missing_docs)]
50pub fn span_end(span: &Span) -> usize {
51 span.end
52}
53#[allow(missing_docs)]
55pub fn span_after(span: &Span) -> Span {
56 Span::new(span.end, span.end, span.line, span.column + span_len(span))
57}
58#[allow(missing_docs)]
62pub fn span_before(span: &Span) -> Span {
63 let start = span.start.saturating_sub(1);
64 let col = span.column.saturating_sub(1).max(1);
65 Span::new(start, start, span.line, col)
66}
67#[allow(missing_docs)]
69pub fn span_extend(span: &Span, n: usize, source_len: usize) -> Span {
70 let new_end = (span.end + n).min(source_len);
71 Span::new(span.start, new_end, span.line, span.column)
72}
73#[allow(missing_docs)]
75pub fn span_shrink_left(span: &Span, n: usize) -> Span {
76 let new_start = (span.start + n).min(span.end);
77 Span::new(new_start, span.end, span.line, span.column + n)
78}
79#[allow(missing_docs)]
81pub fn span_shrink_right(span: &Span, n: usize) -> Span {
82 let new_end = span.end.saturating_sub(n).max(span.start);
83 Span::new(span.start, new_end, span.line, span.column)
84}
85#[allow(missing_docs)]
87pub fn spans_overlap(a: &Span, b: &Span) -> bool {
88 a.start < b.end && b.start < a.end
89}
90#[allow(missing_docs)]
92pub fn span_contains_span(outer: &Span, inner: &Span) -> bool {
93 outer.start <= inner.start && inner.end <= outer.end
94}
95#[allow(missing_docs)]
97pub fn span_intersection(a: &Span, b: &Span) -> Option<Span> {
98 let start = a.start.max(b.start);
99 let end = a.end.min(b.end);
100 if start < end {
101 Some(Span::new(start, end, a.line, a.column))
102 } else {
103 None
104 }
105}
106#[allow(missing_docs)]
110pub fn extract_span<'a>(source: &'a str, span: &Span) -> &'a str {
111 source.get(span.start..span.end).unwrap_or("")
112}
113#[allow(missing_docs)]
117pub fn extract_line<'a>(source: &'a str, span: &Span) -> &'a str {
118 let bytes = source.as_bytes();
119 let line_start = bytes[..span.start]
120 .iter()
121 .rposition(|&b| b == b'\n')
122 .map(|i| i + 1)
123 .unwrap_or(0);
124 let line_end = bytes[span.start..]
125 .iter()
126 .position(|&b| b == b'\n')
127 .map(|i| span.start + i)
128 .unwrap_or(source.len());
129 &source[line_start..line_end]
130}
131#[allow(missing_docs)]
133pub fn count_lines(source: &str) -> usize {
134 source.bytes().filter(|&b| b == b'\n').count() + 1
135}
136#[allow(missing_docs)]
140pub fn pos_to_span(source: &str, line: usize, col: usize) -> Option<Span> {
141 let mut cur_line = 1usize;
142 let mut cur_col = 1usize;
143 for (i, ch) in source.char_indices() {
144 if cur_line == line && cur_col == col {
145 let end = i + ch.len_utf8();
146 return Some(Span::new(i, end, line, col));
147 }
148 if ch == '\n' {
149 cur_line += 1;
150 cur_col = 1;
151 } else {
152 cur_col += 1;
153 }
154 }
155 None
156}
157#[allow(missing_docs)]
159pub fn span_short(span: &Span) -> String {
160 format!("{}:{}", span.line, span.column)
161}
162#[allow(missing_docs)]
164pub fn span_range(span: &Span) -> String {
165 let end_col = span.column + span_len(span);
166 format!("{}:{}-{}", span.line, span.column, end_col)
167}
168#[allow(missing_docs)]
172pub fn span_caret(col: usize, len: usize) -> String {
173 let spaces = " ".repeat(col.saturating_sub(1));
174 let carets = if len == 0 {
175 "^".to_string()
176 } else {
177 let mut s = "^".to_string();
178 s.push_str(&"~".repeat(len.saturating_sub(1)));
179 s
180 };
181 format!("{}{}", spaces, carets)
182}
183#[cfg(test)]
184mod tests {
185 use super::*;
186 use crate::span_util::*;
187 #[test]
188 fn test_dummy_span() {
189 let span = dummy_span();
190 assert_eq!(span.line, 1);
191 assert_eq!(span.column, 1);
192 }
193 #[test]
194 fn test_span_contains() {
195 let span = Span::new(10, 20, 1, 1);
196 assert!(span_contains(&span, 10));
197 assert!(span_contains(&span, 15));
198 assert!(!span_contains(&span, 20));
199 assert!(!span_contains(&span, 5));
200 }
201 #[test]
202 fn test_merge_spans() {
203 let s1 = Span::new(0, 5, 1, 1);
204 let s2 = Span::new(10, 15, 1, 11);
205 let s3 = Span::new(20, 25, 2, 1);
206 let merged = merge_spans(&[s1, s2, s3]).expect("span should be present");
207 assert_eq!(merged.start, 0);
208 assert_eq!(merged.end, 25);
209 }
210 #[test]
211 fn test_span_len() {
212 let span = Span::new(10, 25, 1, 1);
213 assert_eq!(span_len(&span), 15);
214 }
215 #[test]
216 fn test_source_pos_display() {
217 let pos = SourcePos::new(3, 7);
218 assert_eq!(pos.to_string(), "3:7");
219 }
220 #[test]
221 fn test_source_pos_advance() {
222 let pos = SourcePos::start();
223 let next = pos.advance_col();
224 assert_eq!(next.col, 2);
225 let nl = pos.advance_line();
226 assert_eq!(nl.line, 2);
227 assert_eq!(nl.col, 1);
228 }
229 #[test]
230 fn test_spans_overlap() {
231 let a = Span::new(0, 10, 1, 1);
232 let b = Span::new(5, 15, 1, 6);
233 let c = Span::new(10, 20, 1, 11);
234 assert!(spans_overlap(&a, &b));
235 assert!(!spans_overlap(&a, &c));
236 }
237 #[test]
238 fn test_span_contains_span() {
239 let outer = Span::new(0, 20, 1, 1);
240 let inner = Span::new(5, 15, 1, 6);
241 let too_big = Span::new(5, 25, 1, 6);
242 assert!(span_contains_span(&outer, &inner));
243 assert!(!span_contains_span(&outer, &too_big));
244 }
245 #[test]
246 fn test_span_intersection() {
247 let a = Span::new(0, 10, 1, 1);
248 let b = Span::new(5, 15, 1, 6);
249 let isect = span_intersection(&a, &b).expect("span should be present");
250 assert_eq!(isect.start, 5);
251 assert_eq!(isect.end, 10);
252 let c = Span::new(20, 30, 2, 1);
253 assert!(span_intersection(&a, &c).is_none());
254 }
255 #[test]
256 fn test_extract_span() {
257 let source = "hello world";
258 let span = Span::new(6, 11, 1, 7);
259 assert_eq!(extract_span(source, &span), "world");
260 }
261 #[test]
262 fn test_extract_line() {
263 let source = "line one\nline two\nline three";
264 let span = Span::new(9, 13, 2, 1);
265 assert_eq!(extract_line(source, &span), "line two");
266 }
267 #[test]
268 fn test_count_lines() {
269 assert_eq!(count_lines("a\nb\nc"), 3);
270 assert_eq!(count_lines("no newlines"), 1);
271 }
272 #[test]
273 fn test_span_caret() {
274 let c = span_caret(5, 3);
275 assert!(c.starts_with(" ^"));
276 }
277 #[test]
278 fn test_span_builder() {
279 let builder = SpanBuilder::new(10, 2, 5);
280 let span = builder.finish(20);
281 assert_eq!(span.start, 10);
282 assert_eq!(span.end, 20);
283 assert_eq!(span.line, 2);
284 }
285 #[test]
286 fn test_spanned_map() {
287 let s = Spanned::new(42u32, dummy_span());
288 let s2 = s.map(|v| v * 2);
289 assert_eq!(s2.value, 84);
290 }
291 #[test]
292 fn test_labeled_span() {
293 let span = Span::new(0, 5, 1, 1);
294 let ls = LabeledSpan::new("here", span);
295 assert_eq!(ls.label, "here");
296 assert_eq!(ls.len(), 5);
297 assert!(!ls.is_empty());
298 }
299 #[test]
300 fn test_span_short_and_range() {
301 let span = Span::new(10, 15, 3, 7);
302 assert_eq!(span_short(&span), "3:7");
303 let r = span_range(&span);
304 assert!(r.contains("3:7"));
305 }
306 #[test]
307 fn test_span_extend_shrink() {
308 let span = Span::new(5, 10, 1, 6);
309 let extended = span_extend(&span, 3, 100);
310 assert_eq!(extended.end, 13);
311 let shrunk = span_shrink_right(&span, 2);
312 assert_eq!(shrunk.end, 8);
313 let shrunk_l = span_shrink_left(&span, 2);
314 assert_eq!(shrunk_l.start, 7);
315 }
316 #[test]
317 fn test_merge_empty() {
318 assert!(merge_spans(&[]).is_none());
319 }
320 #[test]
321 fn test_span_before_after() {
322 let span = Span::new(10, 15, 1, 11);
323 let after = span_after(&span);
324 assert_eq!(after.start, 15);
325 let before = span_before(&span);
326 assert_eq!(before.start, 9);
327 }
328}
329#[allow(missing_docs)]
331pub fn span_before_other(a: &Span, b: &Span) -> bool {
332 a.start < b.start
333}
334#[allow(missing_docs)]
336pub fn sort_spans(spans: &mut [Span]) {
337 spans.sort_by_key(|s| s.start);
338}
339#[allow(missing_docs)]
341pub fn coalesce_spans(spans: &[Span]) -> Vec<Span> {
342 if spans.is_empty() {
343 return vec![];
344 }
345 let mut result: Vec<Span> = Vec::new();
346 let mut current = spans[0].clone();
347 for span in &spans[1..] {
348 if span.start <= current.end {
349 current = current.merge(span);
350 } else {
351 result.push(current.clone());
352 current = span.clone();
353 }
354 }
355 result.push(current);
356 result
357}
358#[allow(missing_docs)]
362pub fn span_gap(a: &Span, b: &Span) -> Option<Span> {
363 let (first, second) = if a.end <= b.start { (a, b) } else { (b, a) };
364 if first.end < second.start {
365 Some(Span::new(
366 first.end,
367 second.start,
368 first.line,
369 first.column + span_len(first),
370 ))
371 } else {
372 None
373 }
374}
375#[allow(missing_docs)]
379pub fn build_line_index(source: &str) -> Vec<usize> {
380 let mut starts = vec![0usize];
381 for (i, &b) in source.as_bytes().iter().enumerate() {
382 if b == b'\n' {
383 starts.push(i + 1);
384 }
385 }
386 starts
387}
388#[allow(missing_docs)]
392pub fn offset_to_line_col(line_index: &[usize], offset: usize) -> (usize, usize) {
393 let line = match line_index.binary_search(&offset) {
394 Ok(i) => i,
395 Err(i) => i.saturating_sub(1),
396 };
397 let col = offset - line_index.get(line).copied().unwrap_or(0);
398 (line + 1, col + 1)
399}
400#[cfg(test)]
401mod span_extra_tests {
402 use super::*;
403 use crate::span_util::*;
404 #[test]
405 fn test_source_cursor_basic() {
406 let src = "hello";
407 let mut cursor = SourceCursor::new(src);
408 assert_eq!(cursor.peek(), Some('h'));
409 assert_eq!(cursor.advance(), Some('h'));
410 assert_eq!(cursor.pos(), 1);
411 assert_eq!(cursor.peek(), Some('e'));
412 }
413 #[test]
414 fn test_source_cursor_newline() {
415 let src = "a\nb";
416 let mut cursor = SourceCursor::new(src);
417 cursor.advance();
418 cursor.advance();
419 assert_eq!(cursor.source_pos().line, 2);
420 assert_eq!(cursor.source_pos().col, 1);
421 }
422 #[test]
423 fn test_source_cursor_advance_while() {
424 let src = " hello";
425 let mut cursor = SourceCursor::new(src);
426 let spaces = cursor.advance_while(|c| c == ' ');
427 assert_eq!(spaces, " ");
428 assert_eq!(cursor.peek(), Some('h'));
429 }
430 #[test]
431 fn test_source_cursor_eof() {
432 let mut cursor = SourceCursor::new("ab");
433 cursor.advance();
434 cursor.advance();
435 assert!(cursor.is_eof());
436 assert_eq!(cursor.advance(), None);
437 }
438 #[test]
439 fn test_span_registry() {
440 let mut reg = SpanRegistry::new();
441 let span = Span::new(0, 5, 1, 1);
442 reg.register("foo", span.clone());
443 assert!(reg.contains("foo"));
444 assert!(!reg.contains("bar"));
445 assert_eq!(reg.get("foo").expect("key should exist").end, 5);
446 assert_eq!(reg.len(), 1);
447 }
448 #[test]
449 fn test_span_registry_merge() {
450 let mut a = SpanRegistry::new();
451 let mut b = SpanRegistry::new();
452 a.register("x", Span::new(0, 1, 1, 1));
453 b.register("y", Span::new(2, 3, 1, 3));
454 a.merge(b);
455 assert_eq!(a.len(), 2);
456 }
457 #[test]
458 fn test_coalesce_spans() {
459 let spans = vec![
460 Span::new(0, 5, 1, 1),
461 Span::new(3, 8, 1, 4),
462 Span::new(20, 25, 2, 1),
463 ];
464 let coalesced = coalesce_spans(&spans);
465 assert_eq!(coalesced.len(), 2);
466 assert_eq!(coalesced[0].end, 8);
467 assert_eq!(coalesced[1].start, 20);
468 }
469 #[test]
470 fn test_span_gap() {
471 let a = Span::new(0, 5, 1, 1);
472 let b = Span::new(10, 15, 1, 11);
473 let gap = span_gap(&a, &b).expect("span should be present");
474 assert_eq!(gap.start, 5);
475 assert_eq!(gap.end, 10);
476 }
477 #[test]
478 fn test_span_gap_overlap() {
479 let a = Span::new(0, 10, 1, 1);
480 let b = Span::new(5, 15, 1, 6);
481 assert!(span_gap(&a, &b).is_none());
482 }
483 #[test]
484 fn test_build_line_index() {
485 let src = "abc\ndef\nghi";
486 let idx = build_line_index(src);
487 assert_eq!(idx, vec![0, 4, 8]);
488 }
489 #[test]
490 fn test_offset_to_line_col() {
491 let src = "abc\ndef\nghi";
492 let idx = build_line_index(src);
493 assert_eq!(offset_to_line_col(&idx, 0), (1, 1));
494 assert_eq!(offset_to_line_col(&idx, 4), (2, 1));
495 assert_eq!(offset_to_line_col(&idx, 5), (2, 2));
496 }
497 #[test]
498 fn test_sort_spans() {
499 let mut spans = vec![
500 Span::new(10, 15, 1, 11),
501 Span::new(0, 5, 1, 1),
502 Span::new(5, 10, 1, 6),
503 ];
504 sort_spans(&mut spans);
505 assert_eq!(spans[0].start, 0);
506 assert_eq!(spans[1].start, 5);
507 assert_eq!(spans[2].start, 10);
508 }
509 #[test]
510 fn test_span_before_other() {
511 let a = Span::new(0, 5, 1, 1);
512 let b = Span::new(10, 15, 1, 11);
513 assert!(span_before_other(&a, &b));
514 assert!(!span_before_other(&b, &a));
515 }
516}
517#[allow(dead_code)]
521#[allow(missing_docs)]
522pub fn highlight_span(source: &str, span: &Span) -> String {
523 let line = extract_line(source, span);
524 let caret = span_caret(span.column, span_len(span));
525 format!("{}\n{}", line, caret)
526}
527#[allow(dead_code)]
529#[allow(missing_docs)]
530pub fn zero_span(pos: usize, line: usize, col: usize) -> Span {
531 Span::new(pos, pos, line, col)
532}
533#[allow(dead_code)]
535#[allow(missing_docs)]
536pub fn is_valid_offset(source: &str, offset: usize) -> bool {
537 offset <= source.len()
538}
539#[allow(dead_code)]
543#[allow(missing_docs)]
544pub fn span_line_count(source: &str, span: &Span) -> usize {
545 let text = extract_span(source, span);
546 text.bytes().filter(|&b| b == b'\n').count() + 1
547}
548#[allow(dead_code)]
550#[allow(missing_docs)]
551pub fn span_end_col(span: &Span) -> usize {
552 span.column + span_len(span)
553}
554#[allow(dead_code)]
556#[allow(missing_docs)]
557pub fn span_start_offsets(spans: &[Span]) -> Vec<usize> {
558 spans.iter().map(|s| s.start).collect()
559}
560#[allow(dead_code)]
562#[allow(missing_docs)]
563pub fn find_span_at(spans: &[Span], offset: usize) -> Option<&Span> {
564 spans.iter().find(|s| span_contains(s, offset))
565}
566#[allow(dead_code)]
568#[allow(missing_docs)]
569pub fn partition_spans_by_region<'a>(
570 spans: &'a [Span],
571 region: &Span,
572) -> (Vec<&'a Span>, Vec<&'a Span>) {
573 let inside = spans
574 .iter()
575 .filter(|s| span_contains_span(region, s))
576 .collect();
577 let outside = spans
578 .iter()
579 .filter(|s| !span_contains_span(region, s))
580 .collect();
581 (inside, outside)
582}
583#[cfg(test)]
584mod diag_span_tests {
585 use super::*;
586 use crate::span_util::*;
587 #[test]
588 fn test_diagnostic_span_error() {
589 let span = dummy_span();
590 let d = DiagnosticSpan::error(span, "test error");
591 assert!(d.severity.is_error());
592 assert!(d.format_short().contains("error"));
593 }
594 #[test]
595 fn test_diagnostic_span_warning() {
596 let span = dummy_span();
597 let d = DiagnosticSpan::warning(span, "test warning");
598 assert_eq!(d.severity.label(), "warning");
599 }
600 #[test]
601 fn test_diagnostic_set_count() {
602 let mut set = DiagnosticSet::new();
603 set.add_error(dummy_span(), "e1");
604 set.add_error(dummy_span(), "e2");
605 set.add_warning(dummy_span(), "w1");
606 assert_eq!(set.count_severity(&SpanSeverity::Error), 2);
607 assert_eq!(set.count_severity(&SpanSeverity::Warning), 1);
608 assert_eq!(set.len(), 3);
609 assert!(set.has_errors());
610 }
611 #[test]
612 fn test_diagnostic_set_no_errors() {
613 let mut set = DiagnosticSet::new();
614 set.add_info(dummy_span(), "info");
615 assert!(!set.has_errors());
616 }
617 #[test]
618 fn test_diagnostic_set_merge() {
619 let mut a = DiagnosticSet::new();
620 a.add_error(dummy_span(), "e1");
621 let mut b = DiagnosticSet::new();
622 b.add_warning(dummy_span(), "w1");
623 a.merge(b);
624 assert_eq!(a.len(), 2);
625 }
626 #[test]
627 fn test_diagnostic_set_clear() {
628 let mut set = DiagnosticSet::new();
629 set.add_error(dummy_span(), "e");
630 set.clear();
631 assert!(set.is_empty());
632 }
633 #[test]
634 fn test_highlight_span() {
635 let src = "hello world";
636 let span = Span::new(6, 11, 1, 7);
637 let h = highlight_span(src, &span);
638 assert!(h.contains("hello world"));
639 assert!(h.contains('^'));
640 }
641 #[test]
642 fn test_zero_span() {
643 let z = zero_span(10, 2, 3);
644 assert_eq!(z.start, 10);
645 assert_eq!(z.end, 10);
646 assert!(span_len(&z) == 0);
647 }
648 #[test]
649 fn test_span_line_count() {
650 let src = "ab\ncd\nef";
651 let span = Span::new(0, 8, 1, 1);
652 assert_eq!(span_line_count(src, &span), 3);
653 }
654 #[test]
655 fn test_span_diff() {
656 let old = Span::new(0, 5, 1, 1);
657 let new = Span::new(0, 8, 1, 1);
658 let diff = SpanDiff::compute(old, new);
659 assert!(diff.grew());
660 assert!(!diff.shrank());
661 assert!(!diff.unchanged());
662 }
663 #[test]
664 fn test_span_map() {
665 let mut m: SpanMap<&str> = SpanMap::new();
666 m.insert(5, "hello");
667 m.insert(10, "world");
668 assert_eq!(m.get(5), Some(&"hello"));
669 assert_eq!(m.get(99), None);
670 assert_eq!(m.len(), 2);
671 }
672 #[test]
673 fn test_find_span_at() {
674 let spans = vec![Span::new(0, 5, 1, 1), Span::new(10, 15, 1, 11)];
675 let found = find_span_at(&spans, 3);
676 assert!(found.is_some());
677 let not_found = find_span_at(&spans, 7);
678 assert!(not_found.is_none());
679 }
680 #[test]
681 fn test_span_end_col() {
682 let span = Span::new(0, 5, 1, 1);
683 assert_eq!(span_end_col(&span), 6);
684 }
685 #[test]
686 fn test_is_valid_offset() {
687 let src = "hello";
688 assert!(is_valid_offset(src, 5));
689 assert!(!is_valid_offset(src, 6));
690 }
691}
692#[allow(dead_code)]
696#[allow(missing_docs)]
697pub fn byte_col_to_utf16(line_text: &str, byte_col: usize) -> usize {
698 let prefix = line_text.get(..byte_col).unwrap_or(line_text);
699 prefix.chars().map(|c| c.len_utf16()).sum()
700}
701#[allow(dead_code)]
705#[allow(missing_docs)]
706pub fn utf16_col_to_byte(line_text: &str, utf16_col: usize) -> usize {
707 let mut remaining = utf16_col;
708 let mut byte_offset = 0;
709 for ch in line_text.chars() {
710 if remaining == 0 {
711 break;
712 }
713 let units = ch.len_utf16();
714 if remaining < units {
715 break;
716 }
717 remaining -= units;
718 byte_offset += ch.len_utf8();
719 }
720 byte_offset
721}
722#[allow(dead_code)]
725#[allow(missing_docs)]
726pub fn line_col_to_offset(source: &str, line: usize, col: usize) -> Option<usize> {
727 let mut cur_line = 1usize;
728 let mut cur_col = 1usize;
729 let mut pos = 0usize;
730 for ch in source.chars() {
731 if cur_line == line && cur_col == col {
732 return Some(pos);
733 }
734 if ch == '\n' {
735 cur_line += 1;
736 cur_col = 1;
737 } else {
738 cur_col += 1;
739 }
740 pos += ch.len_utf8();
741 }
742 if cur_line == line && cur_col == col {
743 Some(pos)
744 } else {
745 None
746 }
747}
748#[allow(dead_code)]
750#[allow(missing_docs)]
751pub fn shift_spans(spans: &mut [Span], edit_start: usize, delta: i64) {
752 for span in spans.iter_mut() {
753 if span.start >= edit_start {
754 span.start = (span.start as i64 + delta).max(0) as usize;
755 span.end = (span.end as i64 + delta).max(span.start as i64) as usize;
756 } else if span.end > edit_start {
757 span.end = edit_start;
758 }
759 }
760}
761#[allow(dead_code)]
763#[allow(missing_docs)]
764pub fn count_chars(source: &str, span: &Span) -> usize {
765 extract_span(source, span).chars().count()
766}
767#[allow(dead_code)]
769#[allow(missing_docs)]
770pub fn split_span(span: &Span, split_at: usize) -> (Span, Span) {
771 assert!(
772 split_at >= span.start && split_at <= span.end,
773 "split_at out of range"
774 );
775 let left = Span::new(span.start, split_at, span.line, span.column);
776 let right_col = span.column + (split_at - span.start);
777 let right = Span::new(split_at, span.end, span.line, right_col);
778 (left, right)
779}
780#[allow(dead_code)]
782#[allow(missing_docs)]
783pub fn span_to_diagnostic_string(file: &str, span: &Span) -> String {
784 format!("{}:{}:{}", file, span.line, span.column)
785}
786#[allow(dead_code)]
788#[allow(missing_docs)]
789pub fn format_diagnostic(source: &str, span: &Span, label: &str) -> String {
790 let line = extract_line(source, span);
791 let caret = span_caret(span.column, span_len(span));
792 format!("{}\n{}\n{}", line, caret, label)
793}
794#[allow(dead_code)]
796#[allow(missing_docs)]
797pub fn span_debug_str(span: &Span) -> String {
798 format!(
799 "Span {{ start: {}, end: {}, line: {}, col: {} }}",
800 span.start, span.end, span.line, span.column
801 )
802}
803#[allow(dead_code)]
805#[allow(missing_docs)]
806pub fn spans_adjacent(a: &Span, b: &Span) -> bool {
807 a.end == b.start || b.end == a.start
808}
809#[allow(dead_code)]
811#[allow(missing_docs)]
812pub fn span_center(span: &Span) -> usize {
813 span.start + span_len(span) / 2
814}
815#[allow(dead_code)]
817#[allow(missing_docs)]
818pub fn span_strictly_contains(outer: &Span, inner: &Span) -> bool {
819 outer.start <= inner.start
820 && inner.end <= outer.end
821 && !(outer.start == inner.start && outer.end == inner.end)
822}
823#[allow(dead_code)]
825#[allow(missing_docs)]
826pub fn sort_spans_by_end(spans: &mut [Span]) {
827 spans.sort_by(|a, b| a.end.cmp(&b.end).then(a.start.cmp(&b.start)));
828}
829#[allow(dead_code)]
831#[allow(missing_docs)]
832pub fn sort_spans_reverse(spans: &mut [Span]) {
833 spans.sort_by(|a, b| b.start.cmp(&a.start));
834}
835#[allow(dead_code)]
837#[allow(missing_docs)]
838pub fn spans_are_sorted(spans: &[Span]) -> bool {
839 spans.windows(2).all(|w| w[0].start <= w[1].start)
840}
841#[allow(dead_code)]
843#[allow(missing_docs)]
844pub fn dedup_spans(spans: &mut Vec<Span>) {
845 spans.dedup_by(|a, b| a.start == b.start && a.end == b.end);
846}
847#[allow(dead_code)]
849#[allow(missing_docs)]
850pub fn filter_non_overlapping<'a>(spans: &'a [Span], exclude: &Span) -> Vec<&'a Span> {
851 spans
852 .iter()
853 .filter(|s| !spans_overlap(s, exclude))
854 .collect()
855}
856#[allow(dead_code)]
858#[allow(missing_docs)]
859pub fn span_cmp(a: &Span, b: &Span) -> std::cmp::Ordering {
860 a.start.cmp(&b.start).then(a.end.cmp(&b.end))
861}
862#[allow(dead_code)]
864#[allow(missing_docs)]
865pub fn span_precedes(a: &Span, b: &Span) -> bool {
866 a.end <= b.start
867}
868#[allow(dead_code)]
870#[allow(missing_docs)]
871pub fn span_eq_range(a: &Span, b: &Span) -> bool {
872 a.start == b.start && a.end == b.end
873}
874#[allow(dead_code)]
876#[allow(missing_docs)]
877pub fn span_distance(a: &Span, b: &Span) -> usize {
878 if spans_overlap(a, b) {
879 0
880 } else if a.end <= b.start {
881 b.start - a.end
882 } else {
883 a.start - b.end
884 }
885}
886#[allow(dead_code)]
888#[allow(missing_docs)]
889pub fn span_earlier<'a>(a: &'a Span, b: &'a Span) -> &'a Span {
890 if a.start <= b.start {
891 a
892 } else {
893 b
894 }
895}
896#[allow(dead_code)]
898#[allow(missing_docs)]
899pub fn span_later<'a>(a: &'a Span, b: &'a Span) -> &'a Span {
900 if a.end >= b.end {
901 a
902 } else {
903 b
904 }
905}
906#[allow(dead_code)]
908#[allow(missing_docs)]
909pub fn extract_context_window(
910 source: &str,
911 span: &Span,
912 context_lines: usize,
913) -> Vec<(usize, String)> {
914 let lines: Vec<&str> = source.split('\n').collect();
915 let span_line = span.line.saturating_sub(1);
916 let first = span_line.saturating_sub(context_lines);
917 let last = (span_line + context_lines + 1).min(lines.len());
918 lines[first..last]
919 .iter()
920 .enumerate()
921 .map(|(i, &l)| (first + i + 1, l.to_string()))
922 .collect()
923}
924#[allow(dead_code)]
926#[allow(missing_docs)]
927pub fn format_context_window(window: &[(usize, String)], highlight_line: usize) -> String {
928 let mut out = String::new();
929 for (ln, text) in window {
930 let marker = if *ln == highlight_line { ">" } else { " " };
931 out.push_str(&format!("{} {:4} | {}\n", marker, ln, text));
932 }
933 out
934}
935#[cfg(test)]
936mod extended_span_tests {
937 use super::*;
938 use crate::span_util::*;
939 #[test]
940 fn test_file_id_basic() {
941 let id = FileId::new(42);
942 assert_eq!(id.raw(), 42);
943 assert_eq!(id.to_string(), "file#42");
944 }
945 #[test]
946 fn test_file_registry_register() {
947 let mut reg = FileRegistry::new();
948 let id = reg.register("foo.lean", "def x := 1");
949 assert_eq!(reg.source(id), Some("def x := 1"));
950 assert_eq!(reg.path(id), Some("foo.lean"));
951 }
952 #[test]
953 fn test_file_registry_extract() {
954 let mut reg = FileRegistry::new();
955 let id = reg.register("a.lean", "hello world");
956 let span = Span::new(6, 11, 1, 7);
957 let fspan = FileSpan::new(id, span);
958 assert_eq!(reg.extract(&fspan), "world");
959 }
960 #[test]
961 fn test_file_span_merge() {
962 let id = FileId::new(1);
963 let a = FileSpan::new(id, Span::new(0, 5, 1, 1));
964 let b = FileSpan::new(id, Span::new(3, 8, 1, 4));
965 let merged = a.merge_with(&b);
966 assert_eq!(merged.span.start, 0);
967 assert_eq!(merged.span.end, 8);
968 }
969 #[test]
970 fn test_line_col_to_offset_basic() {
971 let src = "abc\ndef\nghi";
972 assert_eq!(line_col_to_offset(src, 1, 1), Some(0));
973 assert_eq!(line_col_to_offset(src, 2, 1), Some(4));
974 }
975 #[test]
976 fn test_shift_spans() {
977 let mut spans = vec![Span::new(0, 5, 1, 1), Span::new(10, 15, 1, 11)];
978 shift_spans(&mut spans, 8, 3);
979 assert_eq!(spans[0].start, 0);
980 assert_eq!(spans[1].start, 13);
981 }
982 #[test]
983 fn test_split_span_basic() {
984 let span = Span::new(0, 10, 1, 1);
985 let (left, right) = split_span(&span, 5);
986 assert_eq!(left.end, 5);
987 assert_eq!(right.start, 5);
988 }
989 #[test]
990 fn test_span_strictly_contains() {
991 let outer = Span::new(0, 10, 1, 1);
992 let inner = Span::new(2, 8, 1, 3);
993 assert!(span_strictly_contains(&outer, &inner));
994 }
995 #[test]
996 fn test_spans_adjacent() {
997 let a = Span::new(0, 5, 1, 1);
998 let b = Span::new(5, 10, 1, 6);
999 assert!(spans_adjacent(&a, &b));
1000 }
1001 #[test]
1002 fn test_annotated_span() {
1003 let span = Span::new(0, 5, 1, 1);
1004 let a = AnnotatedSpan::new(span, "test");
1005 assert_eq!(a.annotation, "test");
1006 assert_eq!(a.len(), 5);
1007 }
1008 #[test]
1009 fn test_span_annotations_at_offset() {
1010 let mut anns = SpanAnnotations::new();
1011 anns.annotate(Span::new(0, 10, 1, 1), "first");
1012 anns.annotate(Span::new(5, 15, 1, 6), "second");
1013 let at7 = anns.at_offset(7);
1014 assert_eq!(at7.len(), 2);
1015 }
1016 #[test]
1017 fn test_incremental_tracker() {
1018 let mut tracker = IncrementalSpanTracker::new();
1019 tracker.track(Span::new(0, 5, 1, 1));
1020 tracker.track(Span::new(10, 15, 1, 11));
1021 tracker.apply_edit(8, 3);
1022 assert_eq!(tracker.spans()[1].start, 13);
1023 assert_eq!(tracker.edit_count(), 1);
1024 }
1025 #[test]
1026 fn test_span_origin() {
1027 assert_eq!(SpanOrigin::UserSource.kind_str(), "user");
1028 assert_eq!(SpanOrigin::Synthetic.kind_str(), "synthetic");
1029 }
1030 #[test]
1031 fn test_span_stats() {
1032 let spans = vec![Span::new(0, 5, 1, 1), Span::new(10, 20, 2, 1)];
1033 let stats = SpanStats::compute(&spans);
1034 assert_eq!(stats.count, 2);
1035 assert_eq!(stats.total_len, 15);
1036 assert_eq!(stats.avg_len(), 7);
1037 }
1038 #[test]
1039 fn test_span_chain() {
1040 let mut chain = SpanChain::new();
1041 chain.push(Span::new(0, 5, 1, 1));
1042 chain.push(Span::new(6, 10, 1, 7));
1043 let total = chain.total_span().expect("span should be present");
1044 assert_eq!(total.start, 0);
1045 assert_eq!(total.end, 10);
1046 }
1047 #[test]
1048 fn test_span_cmp() {
1049 let a = Span::new(0, 5, 1, 1);
1050 let b = Span::new(5, 10, 1, 6);
1051 assert_eq!(span_cmp(&a, &b), std::cmp::Ordering::Less);
1052 }
1053 #[test]
1054 fn test_span_distance_nonoverlap() {
1055 let a = Span::new(0, 5, 1, 1);
1056 let b = Span::new(10, 15, 1, 11);
1057 assert_eq!(span_distance(&a, &b), 5);
1058 }
1059 #[test]
1060 fn test_span_earlier_later() {
1061 let a = Span::new(0, 5, 1, 1);
1062 let b = Span::new(3, 12, 1, 4);
1063 assert_eq!(span_earlier(&a, &b).start, 0);
1064 assert_eq!(span_later(&a, &b).end, 12);
1065 }
1066 #[test]
1067 fn test_count_chars_unicode() {
1068 let src = "αβγδ";
1069 let span = Span::new(0, src.len(), 1, 1);
1070 assert_eq!(count_chars(src, &span), 4);
1071 }
1072 #[test]
1073 fn test_extract_context_window() {
1074 let src = "line1\nline2\nline3\nline4\nline5";
1075 let span = Span::new(12, 17, 3, 1);
1076 let window = extract_context_window(src, &span, 1);
1077 assert_eq!(window.len(), 3);
1078 assert_eq!(window[1].0, 3);
1079 }
1080 #[test]
1081 fn test_dedup_spans() {
1082 let mut spans = vec![
1083 Span::new(0, 5, 1, 1),
1084 Span::new(0, 5, 1, 1),
1085 Span::new(6, 10, 1, 7),
1086 ];
1087 dedup_spans(&mut spans);
1088 assert_eq!(spans.len(), 2);
1089 }
1090 #[test]
1091 fn test_sort_spans_reverse() {
1092 let mut spans = vec![Span::new(0, 5, 1, 1), Span::new(10, 15, 1, 11)];
1093 sort_spans_reverse(&mut spans);
1094 assert_eq!(spans[0].start, 10);
1095 }
1096 #[test]
1097 fn test_byte_col_to_utf16_ascii() {
1098 let line = "hello world";
1099 assert_eq!(byte_col_to_utf16(line, 5), 5);
1100 }
1101 #[test]
1102 fn test_span_debug_str() {
1103 let span = Span::new(1, 5, 2, 3);
1104 let s = span_debug_str(&span);
1105 assert!(s.contains("start: 1"));
1106 }
1107 #[test]
1108 fn test_span_center() {
1109 let span = Span::new(2, 8, 1, 3);
1110 assert_eq!(span_center(&span), 5);
1111 }
1112 #[test]
1113 fn test_sort_spans_by_end() {
1114 let mut spans = vec![Span::new(5, 15, 1, 6), Span::new(0, 8, 1, 1)];
1115 sort_spans_by_end(&mut spans);
1116 assert_eq!(spans[0].end, 8);
1117 }
1118}
1119#[allow(dead_code)]
1121#[allow(missing_docs)]
1122pub fn spans_sorted_check(spans: &[Span]) -> bool {
1123 spans.windows(2).all(|w| w[0].start <= w[1].start)
1124}
1125#[allow(dead_code)]
1127#[allow(missing_docs)]
1128pub fn total_coverage(spans: &[Span]) -> usize {
1129 if spans.is_empty() {
1130 return 0;
1131 }
1132 let mut sorted = spans.to_vec();
1133 sort_spans(&mut sorted);
1134 let coalesced = coalesce_spans(&sorted);
1135 coalesced.iter().map(span_len).sum()
1136}
1137#[allow(dead_code)]
1139#[allow(missing_docs)]
1140pub fn highest_priority_at(spans: &[PrioritizedSpan], offset: usize) -> Option<&PrioritizedSpan> {
1141 spans
1142 .iter()
1143 .filter(|ps| span_contains(&ps.span, offset))
1144 .max_by_key(|ps| ps.priority)
1145}
1146#[cfg(test)]
1147mod coverage_extra_tests {
1148 use super::*;
1149 use crate::span_util::*;
1150 #[test]
1151 fn test_total_coverage_no_overlap() {
1152 let spans = vec![Span::new(0, 5, 1, 1), Span::new(10, 15, 1, 11)];
1153 assert_eq!(total_coverage(&spans), 10);
1154 }
1155 #[test]
1156 fn test_total_coverage_overlap() {
1157 let spans = vec![Span::new(0, 10, 1, 1), Span::new(5, 15, 1, 6)];
1158 assert_eq!(total_coverage(&spans), 15);
1159 }
1160 #[test]
1161 fn test_total_coverage_empty() {
1162 assert_eq!(total_coverage(&[]), 0);
1163 }
1164 #[test]
1165 fn test_spans_sorted_check_true() {
1166 let sorted = vec![Span::new(0, 5, 1, 1), Span::new(6, 10, 1, 7)];
1167 assert!(spans_sorted_check(&sorted));
1168 }
1169 #[test]
1170 fn test_spans_sorted_check_false() {
1171 let unsorted = vec![Span::new(6, 10, 1, 7), Span::new(0, 5, 1, 1)];
1172 assert!(!spans_sorted_check(&unsorted));
1173 }
1174 #[test]
1175 fn test_highest_priority_at() {
1176 let spans = vec![
1177 PrioritizedSpan::new(Span::new(0, 10, 1, 1), 1),
1178 PrioritizedSpan::new(Span::new(3, 7, 1, 4), 5),
1179 ];
1180 let best = highest_priority_at(&spans, 5).expect("span should be present");
1181 assert_eq!(best.priority, 5);
1182 }
1183 #[test]
1184 fn test_highest_priority_at_none() {
1185 let spans = vec![PrioritizedSpan::new(Span::new(0, 5, 1, 1), 1)];
1186 assert!(highest_priority_at(&spans, 10).is_none());
1187 }
1188 #[test]
1189 fn test_span_range() {
1190 let r = SpanRange::new(2, 7);
1191 assert_eq!(r.len(), 5);
1192 assert!(r.contains(4));
1193 assert!(!r.contains(7));
1194 }
1195 #[test]
1196 fn test_padded_span() {
1197 let inner = Span::new(5, 10, 1, 6);
1198 let padded = PaddedSpan::new(inner, 3, 2);
1199 let expanded = padded.expanded(100);
1200 assert_eq!(expanded.start, 2);
1201 assert_eq!(expanded.end, 12);
1202 }
1203 #[test]
1204 fn test_padded_span_clamp() {
1205 let inner = Span::new(0, 5, 1, 1);
1206 let padded = PaddedSpan::new(inner, 10, 100);
1207 let expanded = padded.expanded(8);
1208 assert_eq!(expanded.start, 0);
1209 assert_eq!(expanded.end, 8);
1210 }
1211}