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