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}