1use std::collections::VecDeque;
11
12use crate::pos::Pos;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum BreakType {
21 Lf,
23 Cr,
25 CrLf,
27 Eof,
29}
30
31impl BreakType {
32 #[must_use]
34 pub const fn byte_len(self) -> usize {
35 match self {
36 Self::Lf | Self::Cr => 1,
37 Self::CrLf => 2,
38 Self::Eof => 0,
39 }
40 }
41
42 #[must_use]
48 pub const fn advance(self, mut pos: Pos) -> Pos {
49 match self {
50 Self::Lf => pos.advance('\n'),
51 Self::CrLf => {
52 pos.byte_offset += '\r'.len_utf8();
53 pos.advance('\n')
54 }
55 Self::Cr => {
56 pos.byte_offset += '\r'.len_utf8();
57 pos.line += 1;
58 pos.column = 0;
59 pos
60 }
61 Self::Eof => pos,
62 }
63 }
64}
65
66#[derive(Debug, Clone, PartialEq, Eq)]
68pub struct Line<'input> {
69 pub content: &'input str,
71 pub offset: usize,
73 pub indent: usize,
77 pub break_type: BreakType,
79 pub pos: Pos,
81}
82
83fn detect_break(s: &str) -> (BreakType, &str) {
92 if let Some(rest) = s.strip_prefix("\r\n") {
93 return (BreakType::CrLf, rest);
94 }
95 if let Some(rest) = s.strip_prefix('\r') {
96 return (BreakType::Cr, rest);
97 }
98 if let Some(rest) = s.strip_prefix('\n') {
99 return (BreakType::Lf, rest);
100 }
101 (BreakType::Eof, s)
102}
103
104fn scan_line(remaining: &str, pos: Pos) -> Option<(Line<'_>, &str)> {
108 if remaining.is_empty() {
109 return None;
110 }
111
112 let line_end = remaining.find(['\n', '\r']).unwrap_or(remaining.len());
114
115 let content = &remaining[..line_end];
116 let after_content = &remaining[line_end..];
117
118 let (break_type, after_break) = detect_break(after_content);
121
122 let indent = content.chars().take_while(|&ch| ch == ' ').count();
124
125 let offset = pos.byte_offset;
127
128 let line = Line {
129 content,
130 offset,
131 indent,
132 break_type,
133 pos,
134 };
135
136 Some((line, after_break))
137}
138
139pub struct LineBuffer<'input> {
148 remaining: &'input str,
150 prepend: VecDeque<Line<'input>>,
155 next: Option<Line<'input>>,
157 remaining_pos: Pos,
159 lookahead: Vec<Line<'input>>,
161}
162
163impl<'input> LineBuffer<'input> {
164 #[must_use]
166 pub fn new(input: &'input str) -> Self {
167 let mut buf = Self {
168 remaining: input,
169 prepend: VecDeque::new(),
170 next: None,
171 remaining_pos: Pos::ORIGIN,
172 lookahead: Vec::new(),
173 };
174 buf.prime();
175 buf
176 }
177
178 pub fn prepend_line(&mut self, line: Line<'input>) {
187 self.lookahead.clear();
188 self.prepend.push_front(line);
189 }
190
191 #[must_use]
196 pub fn peek_next(&self) -> Option<&Line<'input>> {
197 self.prepend.front().or(self.next.as_ref())
198 }
199
200 #[must_use]
203 pub fn is_next_synthetic(&self) -> bool {
204 !self.prepend.is_empty()
205 }
206
207 #[must_use]
209 pub fn peek_next_indent(&self) -> Option<usize> {
210 self.peek_next().map(|l| l.indent)
211 }
212
213 #[must_use]
218 pub fn peek_second(&self) -> Option<Line<'input>> {
219 if !self.prepend.is_empty() {
221 if self.prepend.len() >= 2 {
224 return self.prepend.get(1).cloned();
225 }
226 return self.next.clone();
227 }
228 self.next.as_ref()?; scan_line(self.remaining, self.remaining_pos).map(|(line, _)| line)
231 }
232
233 pub fn consume_next(&mut self) -> Option<Line<'input>> {
238 if let Some(line) = self.prepend.pop_front() {
240 return Some(line);
241 }
242 self.lookahead.clear();
244 let line = self.next.take()?;
245 self.prime();
246 Some(line)
247 }
248
249 #[must_use]
252 pub fn at_eof(&self) -> bool {
253 self.prepend.is_empty() && self.next.is_none()
254 }
255
256 pub fn signal_document_boundary(&mut self) {
274 if let Some(ref mut next) = self.next {
276 if next.content.starts_with('\u{FEFF}') {
277 let bom_len = '\u{FEFF}'.len_utf8(); next.content = &next.content[bom_len..];
279 next.offset += bom_len;
280 next.pos.byte_offset += bom_len;
281 }
283 }
284 self.lookahead.clear();
286 }
287
288 pub fn peek_until_dedent(&mut self, base_indent: usize) -> &[Line<'input>] {
301 self.lookahead.clear();
303
304 let mut cursor_remaining = self.remaining;
307 let mut cursor_pos = self.remaining_pos;
308
309 let start_line = match self.next.as_ref() {
312 None => return &self.lookahead,
313 Some(l) => l.clone(),
314 };
315
316 let mut scanning_next = Some(start_line);
319
320 loop {
321 let line = match scanning_next.take() {
322 Some(l) => l,
323 None => {
324 match scan_line(cursor_remaining, cursor_pos) {
326 None => break,
327 Some((l, rest)) => {
328 cursor_pos = pos_after_line(&l);
329 cursor_remaining = rest;
330 l
331 }
332 }
333 }
334 };
335
336 if line.content.is_empty() {
339 self.lookahead.push(line);
340 continue;
341 }
342
343 if base_indent != usize::MAX && line.indent <= base_indent {
347 break;
348 }
349
350 self.lookahead.push(line);
351 }
352
353 &self.lookahead
354 }
355
356 fn prime(&mut self) {
362 match scan_line(self.remaining, self.remaining_pos) {
363 None => {
364 self.next = None;
365 }
366 Some((line, rest)) => {
367 let new_pos = pos_after_line(&line);
369 self.remaining_pos = new_pos;
370 self.remaining = rest;
371 self.next = Some(line);
372 }
373 }
374 }
375}
376
377pub fn pos_after_line(line: &Line<'_>) -> Pos {
384 let byte_offset = line.offset + line.content.len() + line.break_type.byte_len();
385 match line.break_type {
386 BreakType::Eof => Pos {
387 byte_offset,
388 line: line.pos.line,
389 column: line.pos.column + crate::pos::column_at(line.content, line.content.len()),
390 },
391 BreakType::Lf | BreakType::Cr | BreakType::CrLf => Pos {
392 byte_offset,
393 line: line.pos.line + 1,
394 column: 0,
395 },
396 }
397}
398
399#[cfg(test)]
404mod tests {
405 use rstest::rstest;
406
407 use super::*;
408
409 #[rstest]
414 #[case::break_type_advance_lf(BreakType::Lf, Pos::ORIGIN, 1, 2, 0)]
415 #[case::break_type_advance_crlf(BreakType::CrLf, Pos::ORIGIN, 2, 2, 0)]
416 #[case::break_type_advance_lf_at_non_origin_pos(BreakType::Lf, Pos { byte_offset: 5, line: 2, column: 3 }, 6, 3, 0)]
418 #[case::break_type_advance_crlf_at_non_origin_pos(BreakType::CrLf, Pos { byte_offset: 5, line: 2, column: 3 }, 7, 3, 0)]
419 #[case::break_type_advance_cr_resets_column(BreakType::Cr, Pos { byte_offset: 3, line: 1, column: 3 }, 4, 2, 0)]
420 fn break_type_advance_all_fields(
421 #[case] break_type: BreakType,
422 #[case] input: Pos,
423 #[case] expected_byte_offset: usize,
424 #[case] expected_line: usize,
425 #[case] expected_column: usize,
426 ) {
427 let after = break_type.advance(input);
428 assert_eq!(after.byte_offset, expected_byte_offset);
429 assert_eq!(after.line, expected_line);
430 assert_eq!(after.column, expected_column);
431 }
432
433 #[test]
434 fn break_type_advance_cr_increments_line() {
435 let pos = Pos::ORIGIN;
436 let after = BreakType::Cr.advance(pos);
437 assert_eq!(after.line, 2);
438 }
439
440 #[test]
441 fn break_type_advance_eof_is_noop() {
442 let pos = Pos {
443 byte_offset: 5,
444 line: 3,
445 column: 2,
446 };
447 let after = BreakType::Eof.advance(pos);
448 assert_eq!(after, pos);
449 }
450
451 #[rstest]
456 #[case::new_single_line_with_lf_primes_first_line("foo\n", "foo", BreakType::Lf)]
457 #[case::new_input_with_only_lf_primes_empty_line("\n", "", BreakType::Lf)]
458 fn new_single_line_peek(
459 #[case] input: &str,
460 #[case] expected_content: &str,
461 #[case] expected_break: BreakType,
462 ) {
463 let buf = LineBuffer::new(input);
464 let Some(line) = buf.peek_next() else {
465 unreachable!("expected a line");
466 };
467 assert_eq!(line.content, expected_content);
468 assert_eq!(line.break_type, expected_break);
469 }
470
471 #[test]
472 fn new_empty_input_at_eof_immediately() {
473 let buf = LineBuffer::new("");
474 assert!(buf.peek_next().is_none());
475 assert!(buf.at_eof());
476 }
477
478 #[test]
479 fn new_single_line_no_newline_primes_eof_line() {
480 let buf = LineBuffer::new("foo");
481 let Some(line) = buf.peek_next() else {
482 unreachable!("expected a line");
483 };
484 assert_eq!(line.content, "foo");
485 assert_eq!(line.break_type, BreakType::Eof);
486 assert_eq!(line.offset, 0);
487 }
488
489 #[test]
494 fn consume_returns_primed_line_and_advances() {
495 let mut buf = LineBuffer::new("a\nb\n");
496 let Some(first) = buf.consume_next() else {
497 unreachable!("expected first line");
498 };
499 assert_eq!(first.content, "a");
500 assert_eq!(first.break_type, BreakType::Lf);
501 let Some(second) = buf.consume_next() else {
502 unreachable!("expected second line");
503 };
504 assert_eq!(second.content, "b");
505 assert_eq!(second.break_type, BreakType::Lf);
506 }
507
508 #[test]
509 fn consume_after_last_line_returns_none() {
510 let mut buf = LineBuffer::new("foo");
511 assert!(buf.consume_next().is_some());
512 assert!(buf.consume_next().is_none());
513 }
514
515 #[test]
516 fn at_eof_false_before_consuming_last_and_true_after() {
517 let mut buf = LineBuffer::new("foo");
518 assert!(!buf.at_eof());
519 buf.consume_next();
520 assert!(buf.at_eof());
521 }
522
523 #[test]
524 fn consume_all_lines_then_peek_returns_none() {
525 let mut buf = LineBuffer::new("a\nb");
526 buf.consume_next();
527 buf.consume_next();
528 assert!(buf.peek_next().is_none());
529 }
530
531 #[rstest]
536 #[case::only_lf_produces_one_empty_line("\n", BreakType::Lf)]
537 #[case::only_cr_produces_one_empty_line("\r", BreakType::Cr)]
538 #[case::only_crlf_produces_one_empty_line_not_two("\r\n", BreakType::CrLf)]
539 fn single_terminator_produces_empty_line(
540 #[case] input: &str,
541 #[case] expected_break: BreakType,
542 ) {
543 let mut buf = LineBuffer::new(input);
544 let Some(line) = buf.consume_next() else {
545 unreachable!("expected a line");
546 };
547 assert_eq!(line.content, "");
548 assert_eq!(line.break_type, expected_break);
549 assert!(buf.consume_next().is_none());
550 }
551
552 #[test]
553 fn lf_terminator_produces_lf_break_type() {
554 let mut buf = LineBuffer::new("a\n");
555 let Some(line) = buf.consume_next() else {
556 unreachable!("expected a line");
557 };
558 assert_eq!(line.break_type, BreakType::Lf);
559 }
560
561 #[test]
562 fn crlf_terminator_produces_crlf_break_type_not_two_lines() {
563 let mut buf = LineBuffer::new("a\r\nb");
564 let Some(first) = buf.consume_next() else {
565 unreachable!("expected first");
566 };
567 assert_eq!(first.content, "a");
568 assert_eq!(first.break_type, BreakType::CrLf);
569 let Some(second) = buf.consume_next() else {
570 unreachable!("expected second");
571 };
572 assert_eq!(second.content, "b");
573 assert_eq!(second.break_type, BreakType::Eof);
574 assert!(buf.consume_next().is_none());
575 }
576
577 #[test]
578 fn bare_cr_terminator_produces_cr_break_type() {
579 let mut buf = LineBuffer::new("a\rb");
580 let Some(first) = buf.consume_next() else {
581 unreachable!("expected first");
582 };
583 assert_eq!(first.content, "a");
584 assert_eq!(first.break_type, BreakType::Cr);
585 let Some(second) = buf.consume_next() else {
586 unreachable!("expected second");
587 };
588 assert_eq!(second.content, "b");
589 assert_eq!(second.break_type, BreakType::Eof);
590 }
591
592 #[test]
593 fn no_terminator_on_last_line_produces_eof_break_type() {
594 let mut buf = LineBuffer::new("a\nb");
595 buf.consume_next();
596 let Some(second) = buf.consume_next() else {
597 unreachable!("expected second");
598 };
599 assert_eq!(second.content, "b");
600 assert_eq!(second.break_type, BreakType::Eof);
601 }
602
603 #[test]
604 fn mixed_line_endings_each_line_has_correct_break_type() {
605 let mut buf = LineBuffer::new("a\nb\r\nc\rd");
606 let types: Vec<BreakType> = (0..4)
607 .filter_map(|_| buf.consume_next().map(|l| l.break_type))
608 .collect();
609 assert_eq!(
610 types,
611 [
612 BreakType::Lf,
613 BreakType::CrLf,
614 BreakType::Cr,
615 BreakType::Eof
616 ]
617 );
618 }
619
620 #[test]
621 fn two_consecutive_lf_produce_two_empty_lines() {
622 let mut buf = LineBuffer::new("\n\n");
623 let Some(first) = buf.consume_next() else {
624 unreachable!("expected first");
625 };
626 assert_eq!(first.content, "");
627 assert_eq!(first.break_type, BreakType::Lf);
628 let Some(second) = buf.consume_next() else {
629 unreachable!("expected second");
630 };
631 assert_eq!(second.content, "");
632 assert_eq!(second.break_type, BreakType::Lf);
633 assert!(buf.consume_next().is_none());
634 }
635
636 #[test]
637 fn trailing_lf_does_not_produce_extra_empty_line() {
638 let mut buf = LineBuffer::new("foo\n");
641 let Some(line) = buf.consume_next() else {
642 unreachable!("expected a line");
643 };
644 assert_eq!(line.content, "foo");
645 assert!(buf.consume_next().is_none());
646 }
647
648 #[rstest]
653 #[case::pos_line_increments_after_bare_cr("a\rb")]
654 #[case::pos_line_increments_after_crlf("a\r\nb")]
655 fn pos_line_increments_after_terminator(#[case] input: &str) {
656 let mut buf = LineBuffer::new(input);
657 let Some(first) = buf.consume_next() else {
658 unreachable!("expected first");
659 };
660 assert_eq!(first.pos.line, 1);
661 let Some(second) = buf.consume_next() else {
662 unreachable!("expected second");
663 };
664 assert_eq!(second.pos.line, 2);
665 assert_eq!(second.pos.column, 0);
666 }
667
668 #[test]
669 fn offset_is_byte_offset_of_content_start() {
670 let mut buf = LineBuffer::new("foo\nbar\n");
671 let Some(first) = buf.consume_next() else {
672 unreachable!("expected first");
673 };
674 assert_eq!(first.offset, 0);
675 let Some(second) = buf.consume_next() else {
676 unreachable!("expected second");
677 };
678 assert_eq!(second.offset, 4); }
680
681 #[test]
682 fn offset_and_pos_byte_offset_agree() {
683 let mut buf = LineBuffer::new("foo\nbar");
684 while let Some(line) = buf.consume_next() {
685 assert_eq!(line.offset, line.pos.byte_offset);
686 }
687 }
688
689 #[test]
690 fn pos_line_number_increments_per_line() {
691 let mut buf = LineBuffer::new("a\nb\nc");
692 let lines: Vec<Line<'_>> = (0..3).filter_map(|_| buf.consume_next()).collect();
693 assert_eq!(lines.len(), 3, "expected 3 lines");
694 assert_eq!(lines.first().map(|l| l.pos.line), Some(1));
695 assert_eq!(lines.get(1).map(|l| l.pos.line), Some(2));
696 assert_eq!(lines.get(2).map(|l| l.pos.line), Some(3));
697 }
698
699 #[test]
700 fn pos_column_is_zero_at_start_of_each_line() {
701 let mut buf = LineBuffer::new("a\nb");
702 while let Some(line) = buf.consume_next() {
703 assert_eq!(line.pos.column, 0);
704 }
705 }
706
707 #[test]
708 fn pos_column_resets_after_bare_cr() {
709 let mut buf = LineBuffer::new("abc\rd");
712 buf.consume_next(); let Some(second) = buf.consume_next() else {
714 unreachable!("expected second");
715 };
716 assert_eq!(second.pos.column, 0);
717 }
718
719 #[test]
720 fn pos_after_mixed_endings_tracks_lines_correctly() {
721 let mut buf = LineBuffer::new("a\nb\r\nc\rd");
723 let lines: Vec<Line<'_>> = (0..4).filter_map(|_| buf.consume_next()).collect();
724 assert_eq!(lines.len(), 4, "expected 4 lines");
725 let line_nums: Vec<usize> = lines.iter().map(|l| l.pos.line).collect();
726 assert_eq!(line_nums, [1, 2, 3, 4]);
727 for line in &lines {
728 assert_eq!(
729 line.pos.column, 0,
730 "line {} should start at column 0",
731 line.pos.line
732 );
733 }
734 }
735
736 #[test]
737 fn multibyte_content_byte_offset_is_byte_based_not_char_based() {
738 let mut buf = LineBuffer::new("中\nfoo");
740 let Some(first) = buf.consume_next() else {
741 unreachable!("expected first");
742 };
743 assert_eq!(first.offset, 0);
744 assert_eq!(first.content, "中");
745 let Some(second) = buf.consume_next() else {
746 unreachable!("expected second");
747 };
748 assert_eq!(second.offset, 4);
750 }
751
752 #[test]
757 fn bom_not_stripped_by_new_before_boundary_signal() {
758 let input = "\u{FEFF}foo\n";
762 let buf = LineBuffer::new(input);
763 let Some(line) = buf.peek_next() else {
764 unreachable!("expected a line");
765 };
766 assert_eq!(line.content, "\u{FEFF}foo");
767 }
768
769 #[test]
770 fn bom_stripped_from_first_line_via_boundary_signal() {
771 let input = "\u{FEFF}foo\n";
774 let mut buf = LineBuffer::new(input);
775 buf.signal_document_boundary();
776 let Some(line) = buf.peek_next() else {
777 unreachable!("expected a line");
778 };
779 assert_eq!(line.content, "foo");
780 assert_eq!(line.offset, 3);
782 assert_eq!(line.pos.byte_offset, 3);
783 }
784
785 #[test]
786 fn bom_not_stripped_on_non_boundary_mid_content_line() {
787 let input = "foo\n\u{FEFF}bar\n";
790 let mut buf = LineBuffer::new(input);
791 buf.consume_next(); let Some(second) = buf.consume_next() else {
793 unreachable!("expected second");
794 };
795 assert_eq!(second.content, "\u{FEFF}bar");
796 }
797
798 #[test]
799 fn bom_stripped_after_document_boundary_signal() {
800 let input = "foo\n\u{FEFF}bar\n";
803 let mut buf = LineBuffer::new(input);
804 buf.consume_next(); buf.signal_document_boundary();
806 let Some(second) = buf.peek_next() else {
807 unreachable!("expected second");
808 };
809 assert_eq!(second.content, "bar");
810 assert_eq!(second.offset, 4 + 3); assert_eq!(second.pos.byte_offset, 4 + 3);
812 }
813
814 #[test]
815 fn signal_document_boundary_strips_bom_from_primed_next_line() {
816 let input = "...\n\u{FEFF}doc1\n\u{FEFF}doc2\n";
820 let mut buf = LineBuffer::new(input);
821 buf.consume_next(); buf.signal_document_boundary();
824
825 let first = buf.consume_next().expect("first line");
827 assert_eq!(
828 first.content, "doc1",
829 "BOM stripped from primed next by signal"
830 );
831
832 let second = buf.peek_next().expect("second line");
836 assert_eq!(
837 second.content, "\u{FEFF}doc2",
838 "BOM on subsequent line preserved — not affected by one-shot signal"
839 );
840 }
841
842 #[test]
843 fn bom_stripped_line_offset_correct_after_boundary_signal() {
844 let input = "\u{FEFF}key: value\n";
849 let mut buf = LineBuffer::new(input);
850 buf.signal_document_boundary();
851 let Some(line) = buf.peek_next() else {
852 unreachable!("expected line");
853 };
854 assert_eq!(line.offset, 3);
855 assert_eq!(line.pos.byte_offset, 3);
856 assert_eq!(line.content, "key: value");
857
858 let input2 = "...\n\u{FEFF}key: value\n";
860 let mut buf2 = LineBuffer::new(input2);
861 buf2.consume_next(); buf2.signal_document_boundary();
863 let Some(line2) = buf2.peek_next() else {
864 unreachable!("expected line2");
865 };
866 assert_eq!(line2.offset, 4 + 3);
868 assert_eq!(line2.pos.byte_offset, 4 + 3);
869 assert_eq!(line2.content, "key: value");
870 }
871
872 #[rstest]
877 #[case::indent_counts_only_leading_spaces(" foo", 3)]
878 #[case::indent_is_zero_for_no_leading_spaces("foo", 0)]
879 #[case::leading_tab_does_not_count_toward_indent("\tfoo", 0)]
880 #[case::tab_after_spaces_does_not_count(" \tfoo", 2)]
881 #[case::indent_of_blank_line_is_zero("\n", 0)]
882 fn indent_value(#[case] input: &str, #[case] expected: usize) {
883 let buf = LineBuffer::new(input);
884 let Some(line) = buf.peek_next() else {
885 unreachable!("expected a line");
886 };
887 assert_eq!(line.indent, expected);
888 }
889
890 #[test]
891 fn indent_of_spaces_only_line_equals_space_count() {
892 let buf = LineBuffer::new(" \n");
893 let Some(line) = buf.peek_next() else {
894 unreachable!("expected a line");
895 };
896 assert_eq!(line.indent, 3);
897 assert_eq!(line.content, " ");
898 }
899
900 #[rstest]
905 #[case::peek_next_indent_returns_indent_of_next_line(" foo", Some(3))]
906 #[case::peek_next_indent_returns_none_at_eof("", None)]
907 fn peek_next_indent_returns(#[case] input: &str, #[case] expected: Option<usize>) {
908 let buf = LineBuffer::new(input);
909 assert_eq!(buf.peek_next_indent(), expected);
910 }
911
912 #[test]
913 fn peek_next_indent_does_not_consume() {
914 let mut buf = LineBuffer::new(" foo");
915 assert_eq!(buf.peek_next_indent(), Some(2));
916 assert_eq!(buf.peek_next_indent(), Some(2));
917 let Some(line) = buf.consume_next() else {
918 unreachable!("expected a line");
919 };
920 assert_eq!(line.content, " foo");
921 }
922
923 #[test]
928 fn peek_until_dedent_empty_input_returns_empty_slice() {
929 let mut buf = LineBuffer::new("");
930 assert!(buf.peek_until_dedent(0).is_empty());
931 }
932
933 #[test]
934 fn peek_until_dedent_returns_lines_until_indent_le_base() {
935 let mut buf = LineBuffer::new(" a\n b\nc\n");
936 let lines = buf.peek_until_dedent(1);
937 assert_eq!(lines.len(), 2);
938 assert_eq!(lines.first().map(|l| l.content), Some(" a"));
939 assert_eq!(lines.get(1).map(|l| l.content), Some(" b"));
940 }
941
942 #[test]
943 fn peek_until_dedent_does_not_consume_lines() {
944 let mut buf = LineBuffer::new(" a\n b\nc\n");
945 let _ = buf.peek_until_dedent(1);
946 let Some(first) = buf.consume_next() else {
947 unreachable!("expected first");
948 };
949 assert_eq!(first.content, " a");
950 }
951
952 #[test]
953 fn peek_until_dedent_includes_all_lines_when_no_dedent_occurs() {
954 let mut buf = LineBuffer::new(" a\n b\n c");
955 let lines = buf.peek_until_dedent(1);
956 assert_eq!(lines.len(), 3);
957 }
958
959 #[test]
960 fn peek_until_dedent_returns_empty_slice_when_first_line_already_dedented() {
961 let mut buf = LineBuffer::new("a\n b\n");
962 let lines = buf.peek_until_dedent(1);
963 assert!(lines.is_empty());
965 }
966
967 #[test]
968 fn peek_until_dedent_second_call_returns_same_slice() {
969 let mut buf = LineBuffer::new(" a\n b\nc");
970 let first_call: Vec<String> = buf
971 .peek_until_dedent(1)
972 .iter()
973 .map(|l| l.content.to_owned())
974 .collect();
975 let second_call: Vec<String> = buf
976 .peek_until_dedent(1)
977 .iter()
978 .map(|l| l.content.to_owned())
979 .collect();
980 assert_eq!(first_call, second_call);
981 assert_eq!(first_call, [" a", " b"]);
982 }
983
984 #[test]
985 fn peek_until_dedent_base_zero_stops_at_non_indented_lines() {
986 let mut buf = LineBuffer::new(" a\n b\n");
989 let lines = buf.peek_until_dedent(0);
990 assert_eq!(lines.len(), 2);
991 }
992
993 #[test]
994 fn peek_until_dedent_blank_lines_are_transparent() {
995 let mut buf = LineBuffer::new(" a\n\n b\nc");
1002 let lines = buf.peek_until_dedent(1);
1003 assert_eq!(lines.len(), 3);
1004 assert_eq!(lines.first().map(|l| l.content), Some(" a"));
1005 assert_eq!(lines.get(1).map(|l| l.content), Some(""));
1006 assert_eq!(lines.get(2).map(|l| l.content), Some(" b"));
1007 }
1008
1009 #[rstest]
1014 #[case::pos_after_line_lf_ascii(Line { content: "hello", offset: 0, indent: 0, break_type: BreakType::Lf, pos: Pos { byte_offset: 0, line: 1, column: 0 } }, 6, 2, 0)]
1015 #[case::pos_after_line_lf_empty_content(Line { content: "", offset: 10, indent: 0, break_type: BreakType::Lf, pos: Pos { byte_offset: 10, line: 3, column: 0 } }, 11, 4, 0)]
1016 #[case::pos_after_line_lf_multibyte(Line { content: "日本", offset: 0, indent: 0, break_type: BreakType::Lf, pos: Pos { byte_offset: 0, line: 1, column: 0 } }, 7, 2, 0)]
1017 #[case::pos_after_line_cr_ascii(Line { content: "abc", offset: 0, indent: 0, break_type: BreakType::Cr, pos: Pos { byte_offset: 0, line: 1, column: 0 } }, 4, 2, 0)]
1019 #[case::pos_after_line_cr_empty_content(Line { content: "", offset: 5, indent: 0, break_type: BreakType::Cr, pos: Pos { byte_offset: 5, line: 2, column: 0 } }, 6, 3, 0)]
1020 #[case::pos_after_line_crlf_ascii(Line { content: "key: val", offset: 0, indent: 0, break_type: BreakType::CrLf, pos: Pos { byte_offset: 0, line: 1, column: 0 } }, 10, 2, 0)]
1021 #[case::pos_after_line_crlf_empty_content(Line { content: "", offset: 0, indent: 0, break_type: BreakType::CrLf, pos: Pos { byte_offset: 0, line: 1, column: 0 } }, 2, 2, 0)]
1022 #[case::pos_after_line_eof_empty_content(Line { content: "", offset: 20, indent: 0, break_type: BreakType::Eof, pos: Pos { byte_offset: 20, line: 5, column: 0 } }, 20, 5, 0)]
1023 #[case::pos_after_line_eof_ascii(Line { content: "last", offset: 10, indent: 0, break_type: BreakType::Eof, pos: Pos { byte_offset: 10, line: 3, column: 0 } }, 14, 3, 4)]
1024 #[case::pos_after_line_eof_ascii_nonzero_start_column(Line { content: "end", offset: 7, indent: 0, break_type: BreakType::Eof, pos: Pos { byte_offset: 7, line: 2, column: 5 } }, 10, 2, 8)]
1025 #[case::pos_after_line_eof_multibyte(Line { content: "日本語", offset: 0, indent: 0, break_type: BreakType::Eof, pos: Pos { byte_offset: 0, line: 1, column: 0 } }, 9, 1, 3)]
1026 #[case::pos_after_line_eof_mixed_content(Line { content: "ab日", offset: 0, indent: 0, break_type: BreakType::Eof, pos: Pos { byte_offset: 0, line: 1, column: 0 } }, 5, 1, 3)]
1027 fn pos_after_line_cases(
1028 #[case] line: Line<'static>,
1029 #[case] expected_byte_offset: usize,
1030 #[case] expected_line: usize,
1031 #[case] expected_column: usize,
1032 ) {
1033 let result = pos_after_line(&line);
1034 assert_eq!(result.byte_offset, expected_byte_offset);
1035 assert_eq!(result.line, expected_line);
1036 assert_eq!(result.column, expected_column);
1037 }
1038}