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 range from exist span.
148    ///
149    /// # Panics
150    ///
151    /// - Panics if `range+at` 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        let start = self.range.start();
174        let end = self.range.end();
175        let point = start + len;
176        (
177            self.create(TextRange::new(start, point)),
178            self.create(TextRange::new(point, end)),
179        )
180    }
181
182    #[inline]
183    #[track_caller]
184    fn checked_new(source: Arc<String>, range: TextRange) -> Self {
185        let source_length = len_size(source.len());
186
187        assert!(range.end() <= source_length, "range end > source length ({:?} > {source_length:?})", range.end());
188
189        Self { source, range }
190    }
191
192    /// Returns the is empty of this [`Span`] range.
193    ///
194    /// # Examples
195    ///
196    /// ```
197    /// use line_column::span::*;
198    ///
199    /// let span = Span::new_full("foo");
200    /// let empty = span.create(TextRange::empty(1.into()));
201    /// assert_eq!(span.is_empty(),  false);
202    /// assert_eq!(empty.is_empty(), true);
203    /// assert_eq!(empty.range(),    TextRange::new(1.into(), 1.into()));
204    /// ```
205    #[inline]
206    pub fn is_empty(&self) -> bool {
207        self.range().is_empty()
208    }
209
210    /// Returns the length of this [`Span`] range.
211    ///
212    /// # Examples
213    ///
214    /// ```
215    /// use line_column::span::*;
216    ///
217    /// let span = Span::new_full("foo");
218    /// let empty = span.create(TextRange::empty(1.into()));
219    /// assert_eq!(span.len(),  TextSize::new(3));
220    /// assert_eq!(empty.len(), TextSize::new(0));
221    /// ```
222    #[inline]
223    pub fn len(&self) -> TextSize {
224        self.range().len()
225    }
226
227    /// Returns the source before of this [`Span`].
228    ///
229    /// # Examples
230    ///
231    /// ```
232    /// use line_column::span::*;
233    ///
234    /// let span = Span::new("foobarbaz", TextRange::new(3.into(), 6.into()));
235    /// assert_eq!(span.text(),          "bar");
236    /// assert_eq!(span.before().text(), "foo");
237    /// ```
238    pub fn before(&self) -> Self {
239        let range = TextRange::up_to(self.range().start());
240        self.create(range)
241    }
242
243    /// Returns the source after of this [`Span`].
244    ///
245    /// # Examples
246    ///
247    /// ```
248    /// use line_column::span::*;
249    ///
250    /// let span = Span::new("foobarbaz", TextRange::new(3.into(), 6.into()));
251    /// assert_eq!(span.text(),          "bar");
252    /// assert_eq!(span.after().text(),  "baz");
253    /// ```
254    pub fn after(&self) -> Self {
255        let end = TextSize::of(self.source());
256        let range = TextRange::new(self.range().end(), end);
257        self.create(range)
258    }
259
260    /// Returns truncated sub-span.
261    ///
262    /// # Examples
263    ///
264    /// ```
265    /// use line_column::span::*;
266    ///
267    /// let span = Span::new("foobarbaz", TextRange::new(3.into(), 7.into()));
268    /// assert_eq!(span.text(), "barb");
269    /// assert_eq!(span.take(3.into()).text(), "bar");
270    /// ```
271    pub fn take(&self, len: TextSize) -> Self {
272        let range = self.range;
273        let new_len = range.len().min(len);
274        let new_range = TextRange::at(self.range.start(), new_len);
275        self.create(new_range)
276    }
277
278    /// Returns the start of this [`Span`].
279    ///
280    /// # Examples
281    ///
282    /// ```
283    /// use line_column::span::*;
284    ///
285    /// let span = Span::new("abcdef", TextRange::new(1.into(), 4.into()));
286    /// assert_eq!(span.start().range(), TextRange::new(1.into(), 1.into()));
287    /// ```
288    pub fn start(&self) -> Self {
289        self.create(TextRange::empty(self.range.start()))
290    }
291
292    /// Returns the end of this [`Span`].
293    ///
294    /// # Examples
295    ///
296    /// ```
297    /// use line_column::span::*;
298    ///
299    /// let span = Span::new("abcdef", TextRange::new(1.into(), 4.into()));
300    /// assert_eq!(span.end().range(), TextRange::new(4.into(), 4.into()));
301    /// ```
302    pub fn end(&self) -> Self {
303        self.create(TextRange::empty(self.range.end()))
304    }
305
306    /// Returns the start index of this [`Span`] range.
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.index(), TextSize::new(1));
315    /// ```
316    #[inline]
317    pub fn index(&self) -> TextSize {
318        self.range().start()
319    }
320
321    /// Returns the source text of the range reference.
322    ///
323    /// # Examples
324    ///
325    /// ```
326    /// use line_column::span::*;
327    ///
328    /// let span = Span::new("abcdef", TextRange::new(1.into(), 4.into()));
329    /// assert_eq!(span.text(), "bcd");
330    /// ```
331    #[doc(alias = "as_str")]
332    pub fn text(&self) -> &str {
333        &self.source()[self.range()]
334    }
335
336    /// Returns the source text of the range reference.
337    ///
338    /// # Examples
339    ///
340    /// ```
341    /// use line_column::span::*;
342    ///
343    /// let span = Span::new("abcdef", TextRange::new(1.into(), 4.into()));
344    /// assert_eq!(span.range(),       TextRange::new(1.into(), 4.into()));
345    /// ```
346    pub fn range(&self) -> TextRange {
347        self.range
348    }
349
350    /// Returns the source text.
351    ///
352    /// # Examples
353    ///
354    /// ```
355    /// use line_column::span::*;
356    ///
357    /// let span = Span::new("abcdef", TextRange::new(1.into(), 4.into()));
358    /// assert_eq!(span.source(), "abcdef");
359    /// assert_eq!(span.text(),   "bcd");
360    /// ```
361    pub fn source(&self) -> &str {
362        &self.source
363    }
364}
365
366impl Span {
367    pub fn line_column(&self) -> (u32, u32) {
368        crate::line_column(self.source(), self.index().into())
369    }
370
371    pub fn line(&self) -> u32 {
372        self.line_column().0
373    }
374
375    pub fn column(&self) -> u32 {
376        self.line_column().1
377    }
378
379    /// Returns the current line of this [`Span`].
380    ///
381    /// Maybe include end of line char, like `'\n'`.
382    ///
383    /// # Examples
384    ///
385    /// ```
386    /// use line_column::span::*;
387    ///
388    /// let span = Span::new_full("foo\nbar\nbaz");
389    /// let next = span.create(TextRange::at(TextSize::of("foo\n"), 5.into()));
390    /// let tail = span.create(TextRange::at(TextSize::of("foo\nbar\n"), 3.into()));
391    /// let endl = span.create(TextRange::at(TextSize::of("foo"), 3.into()));
392    ///
393    /// assert_eq!(next.text(), "bar\nb");
394    /// assert_eq!(tail.text(), "baz");
395    /// assert_eq!(endl.text(), "\nba");
396    ///
397    /// assert_eq!(span.current_line().text(), "foo\n");
398    /// assert_eq!(next.current_line().text(), "bar\n");
399    /// assert_eq!(tail.current_line().text(), "baz");
400    /// assert_eq!(endl.current_line().text(), "foo\n");
401    /// ```
402    pub fn current_line(&self) -> Self {
403        let before = &self.source[..self.range.start().into()];
404        let line_start = before.rfind('\n').map_or(0, |it| it+1);
405        let rest = &self.source[line_start..];
406
407        let line_len = match rest.split_once('\n') {
408            Some((line, _)) => TextSize::of(line) + TextSize::of('\n'),
409            None => TextSize::of(rest),
410        };
411        let range = TextRange::at(len_size(line_start), line_len);
412        self.create(range)
413    }
414
415    /// Returns the previous line of this [`Span`].
416    ///
417    /// # Examples
418    ///
419    /// ```
420    /// use line_column::span::*;
421    ///
422    /// let span = Span::new_full("foo\nbar\nbaz");
423    /// let next = span.create(TextRange::at(TextSize::of("foo\n"), 5.into()));
424    /// let tail = span.create(TextRange::at(TextSize::of("foo\nbar\n"), 3.into()));
425    /// let endl = span.create(TextRange::at(TextSize::of("foo"), 3.into()));
426    ///
427    /// assert_eq!(next.text(), "bar\nb");
428    /// assert_eq!(tail.text(), "baz");
429    /// assert_eq!(endl.text(), "\nba");
430    ///
431    /// assert_eq!(span.prev_line().text(), "");
432    /// assert_eq!(next.prev_line().text(), "foo\n");
433    /// assert_eq!(tail.prev_line().text(), "bar\n");
434    /// assert_eq!(endl.prev_line().text(), "");
435    /// ```
436    pub fn prev_line(&self) -> Self {
437        let index = self.current_line().index();
438        if let Some(prev_line_offset) = index.checked_sub(TextSize::of('\n')) {
439            self.create(TextRange::empty(prev_line_offset)).current_line()
440        } else {
441            self.create(TextRange::empty(TextSize::new(0)))
442        }
443    }
444
445    /// Returns the next line of this [`Span`].
446    ///
447    /// # Examples
448    ///
449    /// ```
450    /// use line_column::span::*;
451    ///
452    /// let span = Span::new_full("foo\nbar\nbaz");
453    /// let next = span.create(TextRange::at(TextSize::of("foo\n"), 5.into()));
454    /// let tail = span.create(TextRange::at(TextSize::of("foo\nbar\n"), 3.into()));
455    /// let endl = span.create(TextRange::at(TextSize::of("foo"), 3.into()));
456    ///
457    /// assert_eq!(next.text(), "bar\nb");
458    /// assert_eq!(tail.text(), "baz");
459    /// assert_eq!(endl.text(), "\nba");
460    ///
461    /// assert_eq!(span.next_line().text(), "bar\n");
462    /// assert_eq!(next.next_line().text(), "baz");
463    /// assert_eq!(tail.next_line().text(), "");
464    /// assert_eq!(endl.next_line().text(), "bar\n");
465    /// ```
466    pub fn next_line(&self) -> Self {
467        let cur_line_end = self.current_line().range().end();
468        if self.source().len() == cur_line_end.into() {
469            self.create(TextRange::empty(cur_line_end))
470        } else {
471            let range = TextRange::empty(cur_line_end);
472            self.create(range).current_line()
473        }
474    }
475}
476
477impl Span {
478    /// Returns the trim end of this [`Span`] range.
479    ///
480    /// # Examples
481    ///
482    /// ```
483    /// use line_column::span::*;
484    ///
485    /// let span = Span::new("foo  bar  baz", TextRange::new(4.into(), 9.into()));
486    /// assert_eq!(span.text(), " bar ");
487    /// assert_eq!(span.trim_end().text(), " bar");
488    /// ```
489    pub fn trim_end(&self) -> Self {
490        let text = self.text();
491        let trimmed = text.trim_end();
492        let len = TextSize::of(trimmed);
493        self.create(TextRange::at(self.range.start(), len))
494    }
495
496    /// Returns the trim start of this [`Span`] range.
497    ///
498    /// # Examples
499    ///
500    /// ```
501    /// use line_column::span::*;
502    ///
503    /// let span = Span::new("foo  bar  baz", TextRange::new(4.into(), 9.into()));
504    /// assert_eq!(span.text(), " bar ");
505    /// assert_eq!(span.trim_start().text(), "bar ");
506    /// ```
507    pub fn trim_start(&self) -> Self {
508        let text = self.text();
509        let trimmed = text.trim_start();
510        let len = TextSize::of(trimmed);
511
512        let offset = TextSize::of(text) - len;
513        let start = self.range.start() + offset;
514        self.create(TextRange::at(start, len))
515    }
516}
517
518#[inline]
519#[track_caller]
520fn len_size(len: usize) -> TextSize {
521    match TextSize::try_from(len) {
522        Ok(source_length) => source_length,
523        _ => panic!("source length {len} overflow TextSize"),
524    }
525}
526
527#[cfg(test)]
528mod tests {
529    use core::iter::successors;
530    use std::{format, vec::Vec};
531
532    use super::*;
533
534    #[track_caller]
535    fn check_texts(spans: impl IntoIterator<Item = Span>, expect: &[&str]) {
536        let spans = Vec::from_iter(spans);
537        let texts = spans.iter().map(|it| it.text()).collect::<Vec<_>>();
538        assert_eq!(texts, expect);
539    }
540
541    #[test]
542    #[should_panic = "range end > source length"]
543    fn new_panic_out_of_source() {
544        let _span = Span::new("x", TextRange::up_to(TextSize::of("xy")));
545    }
546
547    #[test]
548    fn next_lines_without_end_eol() {
549        let source = "foo\nbar\n\nbaz";
550        let span = Span::new_full(source);
551        let lines =
552            successors(span.current_line().into(), |it| Some(it.next_line()))
553                .take_while(|it| !it.is_empty())
554                .collect::<Vec<_>>();
555        check_texts(lines, &[
556            "foo\n",
557            "bar\n",
558            "\n",
559            "baz",
560        ]);
561    }
562
563    #[test]
564    fn next_lines_multi_bytes_char() {
565        let source = "测试\n实现\n\n多字节";
566        let span = Span::new_full(source);
567        let lines =
568            successors(span.current_line().into(), |it| Some(it.next_line()))
569                .take_while(|it| !it.is_empty())
570                .collect::<Vec<_>>();
571        check_texts(lines, &[
572            "测试\n",
573            "实现\n",
574            "\n",
575            "多字节",
576        ]);
577    }
578
579    #[test]
580    fn next_lines_with_end_eol() {
581        let source = "foo\nbar\n\nbaz\n";
582        let span = Span::new_full(source);
583        let lines =
584            successors(span.current_line().into(), |it| Some(it.next_line()))
585                .take_while(|it| !it.is_empty())
586                .collect::<Vec<_>>();
587        check_texts(lines, &[
588            "foo\n",
589            "bar\n",
590            "\n",
591            "baz\n",
592        ]);
593    }
594
595    #[test]
596    fn next_lines_first_empty_line() {
597        let source = "\nfoo\nbar\n\nbaz";
598        let span = Span::new_full(source);
599        let lines =
600            successors(span.current_line().into(), |it| Some(it.next_line()))
601                .take_while(|it| !it.is_empty())
602                .collect::<Vec<_>>();
603        check_texts(lines, &[
604            "\n",
605            "foo\n",
606            "bar\n",
607            "\n",
608            "baz",
609        ]);
610    }
611
612    #[test]
613    fn prev_lines_with_end_eol() {
614        let source = "foo\nbar\n\nbaz\n";
615        let span = Span::new(source, TextRange::empty(TextSize::of(source)));
616        let lines =
617            successors(span.current_line().into(), |it| Some(it.prev_line()))
618                .skip(1)
619                .take_while(|it| !it.is_empty())
620                .collect::<Vec<_>>();
621        check_texts(lines, &[
622            "baz\n",
623            "\n",
624            "bar\n",
625            "foo\n",
626        ]);
627    }
628
629    #[test]
630    fn prev_lines_without_end_eol() {
631        let source = "foo\nbar\n\nbaz";
632        let span = Span::new(source, TextRange::empty(TextSize::of(source)));
633        let lines =
634            successors(span.current_line().into(), |it| Some(it.prev_line()))
635                .take_while(|it| !it.is_empty())
636                .collect::<Vec<_>>();
637        check_texts(lines, &[
638            "baz",
639            "\n",
640            "bar\n",
641            "foo\n",
642        ]);
643    }
644
645    #[test]
646    fn prev_lines_multi_bytes_char() {
647        let source = "测试\n实现\n\n多字节";
648        let span = Span::new(source, TextRange::empty(TextSize::of(source)));
649        let lines =
650            successors(span.current_line().into(), |it| Some(it.prev_line()))
651                .take_while(|it| !it.is_empty())
652                .collect::<Vec<_>>();
653        check_texts(lines, &[
654            "多字节",
655            "\n",
656            "实现\n",
657            "测试\n",
658        ]);
659    }
660
661    #[test]
662    fn test_trim_start() {
663        let datas = [
664            "",
665            "f",
666            "foo",
667            " ",
668            " f",
669            " foo",
670            "  ",
671            "  f",
672            "  foo",
673            "  f",
674            "  foo",
675        ];
676        for prefix in ["", "x"] {
677            for suffix in ["", "x", " ", "  "] {
678                for data in datas {
679                    let source = format!("{prefix}{data}{suffix}");
680                    let range = TextRange::new(
681                        TextSize::of(prefix),
682                        TextSize::of(&source),
683                    );
684                    let span = Span::new(&source, range);
685                    assert_eq!(span.trim_start().text(), source[range].trim_start());
686                }
687            }
688        }
689    }
690
691    #[test]
692    fn test_trim_end() {
693        let datas = [
694            "",
695            "f",
696            "foo",
697            " ",
698            " f",
699            "foo ",
700            "  ",
701            "f  ",
702            "foo  ",
703            "f  ",
704            "foo  ",
705        ];
706        for prefix in ["", "x", " ", "  "] {
707            for suffix in ["", "x"] {
708                for data in datas {
709                    let source = format!("{prefix}{data}{suffix}");
710                    let range = TextRange::new(
711                        TextSize::new(0),
712                        TextSize::of(&source) - TextSize::of(suffix),
713                    );
714                    let span = Span::new(&source, range);
715                    assert_eq!(span.trim_end().text(), source[range].trim_end());
716                }
717            }
718        }
719    }
720}