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