line_column/span.rs
1//! Out of the box [`Span`] for storing source code and text range.
2
3use core::{fmt, ops};
4use std::{string::String, sync::Arc};
5
6pub use text_size::{TextRange, TextSize};
7
8pub mod wrapper;
9
10/// [`text_size::TextRange`] wrapper
11///
12/// Stored source code pointers, allowing for easy retrieval of lines, columns, and source code text
13///
14/// If `len() == 0`, it is used to indicate offset
15///
16/// # Examples
17///
18/// ```
19/// use line_column::span::*;
20///
21/// let source = Span::new_full("foo,bar,baz");
22/// let comma = source.create(TextRange::at(3.into(), TextSize::of(',')));
23/// let bar = comma.after().take(TextSize::of("bar"));
24///
25/// assert_eq!(comma.text(), ",");
26/// assert_eq!(bar.text(), "bar");
27/// assert_eq!(bar.source(), "foo,bar,baz");
28/// assert_eq!(bar.line_column(), (1, 5));
29/// ```
30#[derive(Clone, Default)]
31pub struct Span {
32 source: Arc<String>,
33 range: TextRange,
34}
35
36impl fmt::Debug for Span {
37 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38 let text = self.text();
39 write!(f, "Span({text:?}@{:?})", self.range())
40 }
41}
42
43impl Span {
44 /// New a source and span range.
45 ///
46 /// **NOTE**: It is not recommended to call repeatedly,
47 /// otherwise the `source` will be allocated repeatedly. Consider using [`Span::create`]
48 ///
49 /// # Panics
50 ///
51 /// - Panics if `range` out of source.
52 /// - Panics if `source.len()` out of [`TextSize`].
53 ///
54 /// # Examples
55 ///
56 /// ```
57 /// use line_column::span::*;
58 ///
59 /// let source = "abcdef";
60 /// let span = Span::new(source, TextRange::new(2.into(), 4.into()));
61 /// assert_eq!(span.text(), "cd");
62 /// ```
63 #[inline]
64 #[track_caller]
65 pub fn new(source: impl Into<String>, range: TextRange) -> Self {
66 Self::checked_new(source.into().into(), range)
67 }
68
69 /// New a full span of source.
70 ///
71 /// **NOTE**: It is not recommended to call repeatedly,
72 /// otherwise the `source` will be allocated repeatedly. Consider using [`Span::create`]
73 ///
74 /// # Panics
75 ///
76 /// - Panics if `source.len()` out of [`TextSize`].
77 ///
78 /// # Examples
79 ///
80 /// ```
81 /// use line_column::span::*;
82 ///
83 /// let source = "abcdef";
84 /// let full = Span::new_full(source);
85 /// assert_eq!(full.text(), "abcdef");
86 /// ```
87 #[inline]
88 pub fn new_full(source: impl Into<String>) -> Self {
89 let source = source.into();
90 let range = TextRange::up_to(len_size(source.len()));
91 Self::checked_new(source.into(), range)
92 }
93
94 /// New a span source range from exist span.
95 ///
96 /// # Panics
97 ///
98 /// - Panics if `range` out of source.
99 ///
100 /// # Examples
101 ///
102 /// ```
103 /// use line_column::span::*;
104 ///
105 /// let source = "abcdef";
106 /// let full = Span::new_full(source);
107 /// assert_eq!(full.text(), "abcdef");
108 ///
109 /// let span = full.create(TextRange::at(1.into(), 3.into()));
110 /// assert_eq!(span.text(), "bcd");
111 /// let span2 = span.create(TextRange::at(3.into(), 3.into()));
112 /// assert_eq!(span2.text(), "def");
113 /// ```
114 #[inline]
115 #[track_caller]
116 pub fn create(&self, range: TextRange) -> Self {
117 Self::checked_new(self.source.clone(), range)
118 }
119
120 /// New a span relative range from exist span.
121 ///
122 /// # Panics
123 ///
124 /// - Panics if `range+start` out of source.
125 ///
126 /// # Examples
127 ///
128 /// ```
129 /// use line_column::span::*;
130 ///
131 /// let source = "abcdef";
132 /// let full = Span::new_full(source);
133 /// assert_eq!(full.text(), "abcdef");
134 ///
135 /// let span = full.slice(TextRange::at(1.into(), 3.into()));
136 /// assert_eq!(span.text(), "bcd");
137 /// let span2 = span.slice(TextRange::at(1.into(), 3.into()));
138 /// assert_eq!(span2.text(), "cde");
139 /// ```
140 #[inline]
141 #[track_caller]
142 pub fn slice(&self, range: TextRange) -> Self {
143 let start = self.range.start();
144 self.create(range+start)
145 }
146
147 /// New splited span pair relative index from exist span.
148 ///
149 /// # Panics
150 ///
151 /// - Panics if `len+start` out of source.
152 ///
153 /// # Examples
154 ///
155 /// ```
156 /// use line_column::span::*;
157 ///
158 /// let source = "abcdef";
159 /// let full = Span::new_full(source);
160 /// assert_eq!(full.text(), "abcdef");
161 ///
162 /// let (a, span) = full.split(TextSize::of("a"));
163 /// assert_eq!(a.text(), "a");
164 /// assert_eq!(span.text(), "bcdef");
165 ///
166 /// let (bcd, span2) = span.split(TextSize::of("bcd"));
167 /// assert_eq!(bcd.text(), "bcd");
168 /// assert_eq!(span2.text(), "ef");
169 /// ```
170 #[inline]
171 #[track_caller]
172 pub fn split(&self, len: TextSize) -> (Self, Self) {
173 self.split_at(self.range.start()+len)
174 }
175
176 /// New splited span pair index from exist span.
177 ///
178 /// # Panics
179 ///
180 /// - Panics if `index` out of source.
181 ///
182 /// # Examples
183 ///
184 /// ```
185 /// use line_column::span::*;
186 ///
187 /// let source = "abcdef";
188 /// let full = Span::new_full(source);
189 /// assert_eq!(full.text(), "abcdef");
190 ///
191 /// let (a, span) = full.split_at(TextSize::of("a"));
192 /// assert_eq!(a.text(), "a");
193 /// assert_eq!(span.text(), "bcdef");
194 ///
195 /// let (bcd, span2) = span.split_at(TextSize::of("abcd"));
196 /// assert_eq!(bcd.text(), "bcd");
197 /// assert_eq!(span2.text(), "ef");
198 /// ```
199 #[inline]
200 #[track_caller]
201 pub fn split_at(&self, index: TextSize) -> (Self, Self) {
202 let start = self.range.start();
203 let end = self.range.end();
204 (
205 self.create(TextRange::new(start, index)),
206 self.create(TextRange::new(index, end)),
207 )
208 }
209
210 #[inline]
211 #[track_caller]
212 fn checked_new(source: Arc<String>, range: TextRange) -> Self {
213 let source_length = len_size(source.len());
214
215 assert!(range.end() <= source_length, "range end > source length ({:?} > {source_length:?})", range.end());
216
217 Self { source, range }
218 }
219
220 /// Returns the is empty of this [`Span`] range.
221 ///
222 /// # Examples
223 ///
224 /// ```
225 /// use line_column::span::*;
226 ///
227 /// let span = Span::new_full("foo");
228 /// let empty = span.create(TextRange::empty(1.into()));
229 /// assert_eq!(span.is_empty(), false);
230 /// assert_eq!(empty.is_empty(), true);
231 /// assert_eq!(empty.range(), TextRange::new(1.into(), 1.into()));
232 /// ```
233 #[inline]
234 pub fn is_empty(&self) -> bool {
235 self.range().is_empty()
236 }
237
238 /// Returns the length of this [`Span`] range.
239 ///
240 /// # Examples
241 ///
242 /// ```
243 /// use line_column::span::*;
244 ///
245 /// let span = Span::new_full("foo");
246 /// let empty = span.create(TextRange::empty(1.into()));
247 /// assert_eq!(span.len(), TextSize::new(3));
248 /// assert_eq!(empty.len(), TextSize::new(0));
249 /// ```
250 #[inline]
251 pub fn len(&self) -> TextSize {
252 self.range().len()
253 }
254
255 /// Returns the source before of this [`Span`].
256 ///
257 /// # Examples
258 ///
259 /// ```
260 /// use line_column::span::*;
261 ///
262 /// let span = Span::new("foobarbaz", TextRange::new(3.into(), 6.into()));
263 /// assert_eq!(span.text(), "bar");
264 /// assert_eq!(span.before().text(), "foo");
265 /// ```
266 pub fn before(&self) -> Self {
267 let range = TextRange::up_to(self.range().start());
268 self.create(range)
269 }
270
271 /// Returns the source after of this [`Span`].
272 ///
273 /// # Examples
274 ///
275 /// ```
276 /// use line_column::span::*;
277 ///
278 /// let span = Span::new("foobarbaz", TextRange::new(3.into(), 6.into()));
279 /// assert_eq!(span.text(), "bar");
280 /// assert_eq!(span.after().text(), "baz");
281 /// ```
282 pub fn after(&self) -> Self {
283 let end = TextSize::of(self.source());
284 let range = TextRange::new(self.range().end(), end);
285 self.create(range)
286 }
287
288 /// Returns truncated sub-span.
289 ///
290 /// # Examples
291 ///
292 /// ```
293 /// use line_column::span::*;
294 ///
295 /// let span = Span::new("foobarbaz", TextRange::new(3.into(), 7.into()));
296 /// assert_eq!(span.text(), "barb");
297 /// assert_eq!(span.take(3.into()).text(), "bar");
298 /// ```
299 pub fn take(&self, len: TextSize) -> Self {
300 let range = self.range;
301 let new_len = range.len().min(len);
302 let new_range = TextRange::at(self.range.start(), new_len);
303 self.create(new_range)
304 }
305
306 /// Returns the start of this [`Span`].
307 ///
308 /// # Examples
309 ///
310 /// ```
311 /// use line_column::span::*;
312 ///
313 /// let span = Span::new("abcdef", TextRange::new(1.into(), 4.into()));
314 /// assert_eq!(span.start().range(), TextRange::new(1.into(), 1.into()));
315 /// ```
316 pub fn start(&self) -> Self {
317 self.create(TextRange::empty(self.range.start()))
318 }
319
320 /// Returns the end of this [`Span`].
321 ///
322 /// # Examples
323 ///
324 /// ```
325 /// use line_column::span::*;
326 ///
327 /// let span = Span::new("abcdef", TextRange::new(1.into(), 4.into()));
328 /// assert_eq!(span.end().range(), TextRange::new(4.into(), 4.into()));
329 /// ```
330 pub fn end(&self) -> Self {
331 self.create(TextRange::empty(self.range.end()))
332 }
333
334 /// Returns the start index of this [`Span`] range.
335 ///
336 /// # Examples
337 ///
338 /// ```
339 /// use line_column::span::*;
340 ///
341 /// let span = Span::new("abcdef", TextRange::new(1.into(), 4.into()));
342 /// assert_eq!(span.index(), TextSize::new(1));
343 /// ```
344 #[inline]
345 pub fn index(&self) -> TextSize {
346 self.range().start()
347 }
348
349 /// Returns the source text of the range reference.
350 ///
351 /// # Examples
352 ///
353 /// ```
354 /// use line_column::span::*;
355 ///
356 /// let span = Span::new("abcdef", TextRange::new(1.into(), 4.into()));
357 /// assert_eq!(span.text(), "bcd");
358 /// ```
359 #[doc(alias = "as_str")]
360 pub fn text(&self) -> &str {
361 &self.source()[self.range()]
362 }
363
364 /// Returns the source text of the range reference.
365 ///
366 /// # Examples
367 ///
368 /// ```
369 /// use line_column::span::*;
370 ///
371 /// let span = Span::new("abcdef", TextRange::new(1.into(), 4.into()));
372 /// assert_eq!(span.range(), TextRange::new(1.into(), 4.into()));
373 /// ```
374 pub fn range(&self) -> TextRange {
375 self.range
376 }
377
378 /// Returns the source text.
379 ///
380 /// # Examples
381 ///
382 /// ```
383 /// use line_column::span::*;
384 ///
385 /// let span = Span::new("abcdef", TextRange::new(1.into(), 4.into()));
386 /// assert_eq!(span.source(), "abcdef");
387 /// assert_eq!(span.text(), "bcd");
388 /// ```
389 pub fn source(&self) -> &str {
390 &self.source
391 }
392}
393
394impl Span {
395 /// Use [`line_column`](crate::line_column) calculate line and column
396 ///
397 /// # Examples
398 ///
399 /// ```
400 /// use line_column::span::*;
401 ///
402 /// let span = Span::new("ab\ncdef", TextRange::empty(TextSize::of("ab\ncd")));
403 /// assert_eq!(span.before().text(), "ab\ncd");
404 /// assert_eq!(span.line_column(), (2, 3));
405 /// ```
406 pub fn line_column(&self) -> (u32, u32) {
407 crate::line_column(self.source(), self.index().into())
408 }
409
410 /// Get line from [`Span::line_column`]
411 pub fn line(&self) -> u32 {
412 self.line_column().0
413 }
414
415 /// Get column from [`Span::line_column`]
416 pub fn column(&self) -> u32 {
417 self.line_column().1
418 }
419
420 /// Returns the current line of this [`Span`].
421 ///
422 /// Maybe include end of line char, like `'\n'`.
423 ///
424 /// # Examples
425 ///
426 /// ```
427 /// use line_column::span::*;
428 ///
429 /// let span = Span::new_full("foo\nbar\nbaz");
430 /// let next = span.create(TextRange::at(TextSize::of("foo\n"), 5.into()));
431 /// let tail = span.create(TextRange::at(TextSize::of("foo\nbar\n"), 3.into()));
432 /// let endl = span.create(TextRange::at(TextSize::of("foo"), 3.into()));
433 ///
434 /// assert_eq!(next.text(), "bar\nb");
435 /// assert_eq!(tail.text(), "baz");
436 /// assert_eq!(endl.text(), "\nba");
437 ///
438 /// assert_eq!(span.current_line().text(), "foo\n");
439 /// assert_eq!(next.current_line().text(), "bar\n");
440 /// assert_eq!(tail.current_line().text(), "baz");
441 /// assert_eq!(endl.current_line().text(), "foo\n");
442 /// ```
443 pub fn current_line(&self) -> Self {
444 let before = &self.source[..self.range.start().into()];
445 let line_start = before.rfind('\n').map_or(0, |it| it+1);
446 let rest = &self.source[line_start..];
447
448 let line_len = match rest.split_once('\n') {
449 Some((line, _)) => TextSize::of(line) + TextSize::of('\n'),
450 None => TextSize::of(rest),
451 };
452 let range = TextRange::at(len_size(line_start), line_len);
453 self.create(range)
454 }
455
456 /// Returns the previous line of this [`Span`].
457 ///
458 /// # Examples
459 ///
460 /// ```
461 /// use line_column::span::*;
462 ///
463 /// let span = Span::new_full("foo\nbar\nbaz");
464 /// let next = span.create(TextRange::at(TextSize::of("foo\n"), 5.into()));
465 /// let tail = span.create(TextRange::at(TextSize::of("foo\nbar\n"), 3.into()));
466 /// let endl = span.create(TextRange::at(TextSize::of("foo"), 3.into()));
467 ///
468 /// assert_eq!(next.text(), "bar\nb");
469 /// assert_eq!(tail.text(), "baz");
470 /// assert_eq!(endl.text(), "\nba");
471 ///
472 /// assert_eq!(span.prev_line().text(), "");
473 /// assert_eq!(next.prev_line().text(), "foo\n");
474 /// assert_eq!(tail.prev_line().text(), "bar\n");
475 /// assert_eq!(endl.prev_line().text(), "");
476 /// ```
477 pub fn prev_line(&self) -> Self {
478 let index = self.current_line().index();
479 if let Some(prev_line_offset) = index.checked_sub(TextSize::of('\n')) {
480 self.create(TextRange::empty(prev_line_offset)).current_line()
481 } else {
482 self.create(TextRange::empty(TextSize::new(0)))
483 }
484 }
485
486 /// Returns the next line of this [`Span`].
487 ///
488 /// # Examples
489 ///
490 /// ```
491 /// use line_column::span::*;
492 ///
493 /// let span = Span::new_full("foo\nbar\nbaz");
494 /// let next = span.create(TextRange::at(TextSize::of("foo\n"), 5.into()));
495 /// let tail = span.create(TextRange::at(TextSize::of("foo\nbar\n"), 3.into()));
496 /// let endl = span.create(TextRange::at(TextSize::of("foo"), 3.into()));
497 ///
498 /// assert_eq!(next.text(), "bar\nb");
499 /// assert_eq!(tail.text(), "baz");
500 /// assert_eq!(endl.text(), "\nba");
501 ///
502 /// assert_eq!(span.next_line().text(), "bar\n");
503 /// assert_eq!(next.next_line().text(), "baz");
504 /// assert_eq!(tail.next_line().text(), "");
505 /// assert_eq!(endl.next_line().text(), "bar\n");
506 /// ```
507 pub fn next_line(&self) -> Self {
508 let cur_line_end = self.current_line().range().end();
509 if self.source().len() == cur_line_end.into() {
510 self.create(TextRange::empty(cur_line_end))
511 } else {
512 let range = TextRange::empty(cur_line_end);
513 self.create(range).current_line()
514 }
515 }
516}
517
518impl Span {
519 /// Returns the trim end of this [`Span`] range.
520 ///
521 /// # Examples
522 ///
523 /// ```
524 /// use line_column::span::*;
525 ///
526 /// let span = Span::new("foo bar baz", TextRange::new(4.into(), 9.into()));
527 /// assert_eq!(span.text(), " bar ");
528 /// assert_eq!(span.trim_end().text(), " bar");
529 /// ```
530 pub fn trim_end(&self) -> Self {
531 let text = self.text();
532 let trimmed = text.trim_end();
533 let len = TextSize::of(trimmed);
534 self.create(TextRange::at(self.range.start(), len))
535 }
536
537 /// Returns the trim start of this [`Span`] range.
538 ///
539 /// # Examples
540 ///
541 /// ```
542 /// use line_column::span::*;
543 ///
544 /// let span = Span::new("foo bar baz", TextRange::new(4.into(), 9.into()));
545 /// assert_eq!(span.text(), " bar ");
546 /// assert_eq!(span.trim_start().text(), "bar ");
547 /// ```
548 pub fn trim_start(&self) -> Self {
549 let text = self.text();
550 let trimmed = text.trim_start();
551 let len = TextSize::of(trimmed);
552
553 let offset = TextSize::of(text) - len;
554 let start = self.range.start() + offset;
555 self.create(TextRange::at(start, len))
556 }
557}
558
559#[inline]
560#[track_caller]
561fn len_size(len: usize) -> TextSize {
562 match TextSize::try_from(len) {
563 Ok(source_length) => source_length,
564 _ => panic!("source length {len} overflow TextSize"),
565 }
566}
567
568#[cfg(test)]
569mod tests {
570 use core::iter::successors;
571 use std::{format, vec::Vec};
572
573 use super::*;
574
575 #[track_caller]
576 fn check_texts(spans: impl IntoIterator<Item = Span>, expect: &[&str]) {
577 let spans = Vec::from_iter(spans);
578 let texts = spans.iter().map(|it| it.text()).collect::<Vec<_>>();
579 assert_eq!(texts, expect);
580 }
581
582 #[test]
583 #[should_panic = "range end > source length"]
584 fn new_panic_out_of_source() {
585 let _span = Span::new("x", TextRange::up_to(TextSize::of("xy")));
586 }
587
588 #[test]
589 fn next_lines_without_end_eol() {
590 let source = "foo\nbar\n\nbaz";
591 let span = Span::new_full(source);
592 let lines =
593 successors(span.current_line().into(), |it| Some(it.next_line()))
594 .take_while(|it| !it.is_empty())
595 .collect::<Vec<_>>();
596 check_texts(lines, &[
597 "foo\n",
598 "bar\n",
599 "\n",
600 "baz",
601 ]);
602 }
603
604 #[test]
605 fn next_lines_multi_bytes_char() {
606 let source = "测试\n实现\n\n多字节";
607 let span = Span::new_full(source);
608 let lines =
609 successors(span.current_line().into(), |it| Some(it.next_line()))
610 .take_while(|it| !it.is_empty())
611 .collect::<Vec<_>>();
612 check_texts(lines, &[
613 "测试\n",
614 "实现\n",
615 "\n",
616 "多字节",
617 ]);
618 }
619
620 #[test]
621 fn next_lines_with_end_eol() {
622 let source = "foo\nbar\n\nbaz\n";
623 let span = Span::new_full(source);
624 let lines =
625 successors(span.current_line().into(), |it| Some(it.next_line()))
626 .take_while(|it| !it.is_empty())
627 .collect::<Vec<_>>();
628 check_texts(lines, &[
629 "foo\n",
630 "bar\n",
631 "\n",
632 "baz\n",
633 ]);
634 }
635
636 #[test]
637 fn next_lines_first_empty_line() {
638 let source = "\nfoo\nbar\n\nbaz";
639 let span = Span::new_full(source);
640 let lines =
641 successors(span.current_line().into(), |it| Some(it.next_line()))
642 .take_while(|it| !it.is_empty())
643 .collect::<Vec<_>>();
644 check_texts(lines, &[
645 "\n",
646 "foo\n",
647 "bar\n",
648 "\n",
649 "baz",
650 ]);
651 }
652
653 #[test]
654 fn prev_lines_with_end_eol() {
655 let source = "foo\nbar\n\nbaz\n";
656 let span = Span::new(source, TextRange::empty(TextSize::of(source)));
657 let lines =
658 successors(span.current_line().into(), |it| Some(it.prev_line()))
659 .skip(1)
660 .take_while(|it| !it.is_empty())
661 .collect::<Vec<_>>();
662 check_texts(lines, &[
663 "baz\n",
664 "\n",
665 "bar\n",
666 "foo\n",
667 ]);
668 }
669
670 #[test]
671 fn prev_lines_without_end_eol() {
672 let source = "foo\nbar\n\nbaz";
673 let span = Span::new(source, TextRange::empty(TextSize::of(source)));
674 let lines =
675 successors(span.current_line().into(), |it| Some(it.prev_line()))
676 .take_while(|it| !it.is_empty())
677 .collect::<Vec<_>>();
678 check_texts(lines, &[
679 "baz",
680 "\n",
681 "bar\n",
682 "foo\n",
683 ]);
684 }
685
686 #[test]
687 fn prev_lines_multi_bytes_char() {
688 let source = "测试\n实现\n\n多字节";
689 let span = Span::new(source, TextRange::empty(TextSize::of(source)));
690 let lines =
691 successors(span.current_line().into(), |it| Some(it.prev_line()))
692 .take_while(|it| !it.is_empty())
693 .collect::<Vec<_>>();
694 check_texts(lines, &[
695 "多字节",
696 "\n",
697 "实现\n",
698 "测试\n",
699 ]);
700 }
701
702 #[test]
703 fn test_trim_start() {
704 let datas = [
705 "",
706 "f",
707 "foo",
708 " ",
709 " f",
710 " foo",
711 " ",
712 " f",
713 " foo",
714 " f",
715 " foo",
716 ];
717 for prefix in ["", "x"] {
718 for suffix in ["", "x", " ", " "] {
719 for data in datas {
720 let source = format!("{prefix}{data}{suffix}");
721 let range = TextRange::new(
722 TextSize::of(prefix),
723 TextSize::of(&source),
724 );
725 let span = Span::new(&source, range);
726 assert_eq!(span.trim_start().text(), source[range].trim_start());
727 }
728 }
729 }
730 }
731
732 #[test]
733 fn test_trim_end() {
734 let datas = [
735 "",
736 "f",
737 "foo",
738 " ",
739 " f",
740 "foo ",
741 " ",
742 "f ",
743 "foo ",
744 "f ",
745 "foo ",
746 ];
747 for prefix in ["", "x", " ", " "] {
748 for suffix in ["", "x"] {
749 for data in datas {
750 let source = format!("{prefix}{data}{suffix}");
751 let range = TextRange::new(
752 TextSize::new(0),
753 TextSize::of(&source) - TextSize::of(suffix),
754 );
755 let span = Span::new(&source, range);
756 assert_eq!(span.trim_end().text(), source[range].trim_end());
757 }
758 }
759 }
760 }
761}