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}