line_span/
lib.rs

1//! This crate features utilities for finding the start, end, and range of
2//! lines from a byte index. Further also being able to find the next and
3//! previous line, from an arbitrary byte index.
4//!
5//! This is provided via the <code>trait [LineSpanExt]</code>, which is
6//! implemented for [`str`], and provides the following methods:
7//!
8//! **Current Line:**
9//!
10//! - [`find_line_start()`](LineSpanExt::find_line_start) to find the start of the current line.
11//! - [`find_line_end()`](LineSpanExt::find_line_end) to find the end of the current line.
12//! - [`find_line_range()`](LineSpanExt::find_line_range) to find the start and end current line.
13//!
14//! **Next Line:**
15//!
16//! - [`find_next_line_start()`](LineSpanExt::find_next_line_start) to find the start of the next line.
17//! - [`find_next_line_end()`](LineSpanExt::find_next_line_end) to find the end of the next line.
18//! - [`find_next_line_range()`](LineSpanExt::find_next_line_range) to find the start and end of the next line.
19//!
20//! **Previous Line:**
21//!
22//! - [`find_prev_line_start()`](LineSpanExt::find_prev_line_start) to find the start of the previous line.
23//! - [`find_prev_line_end()`](LineSpanExt::find_prev_line_end) to find the end of the previous line.
24//! - [`find_prev_line_range()`](LineSpanExt::find_prev_line_range) to find both start and end of the previous line.
25//!
26//! **Iterator:**
27//!
28//! - [`line_spans()`](LineSpanExt::line_spans) an iterator over [`LineSpan`]s, i.e. [`str::lines()`] but including the
29//! start and end byte indicies (in the form of a [`Range<usize>`]).
30//!
31//! **Utilities:**
32//!
33//! - [`str_to_range()`] to get the range of a substring in a string.
34//! - [`str_to_range_unchecked()`] unchecked version of [`str_to_range()`].
35//!
36//! # [`LineSpan`] and [`LineSpanIter`]
37//!
38//! The crate includes the [`LineSpanIter`] iterator. It is functionally equivalent to [`str::lines()`],
39//! with the addition that it includes the ability to get the start and end byte indices of each line.
40//! Additionally, it also includes the ability to get the end including and excluding the line ending (`\n` or `\r\n`).
41//!
42//! An [`LineSpanIter`] can be created by calling [`line_spans()`](LineSpans::line_spans),
43//! implemented in the [`LineSpans`] trait.
44//! The crate implements the [`LineSpans`] trait for [`str`] and [`String`].
45//!
46//! Note, [`LineSpan`] implements [`Deref`] to [`&str`], so in general,
47//! swapping [`lines()`] to [`line_spans()`](LineSpans::line_spans) would not cause many issues.
48//!
49//! ```rust
50//! use line_span::LineSpanExt;
51//!
52//! let text = "foo\nbar\r\nbaz";
53//!
54//! for span in text.line_spans() {
55//!     println!(
56//!         "{:>2?}: {:?} {:?} {:?}",
57//!         span.range(),
58//!         span.as_str(),
59//!         span.as_str_with_ending(),
60//!         span.ending_str(),
61//!     );
62//! }
63//! ```
64//!
65//! This will output the following:
66//!
67//! _(Manually aligned for better readability)_
68//!
69//! ```text
70//! 0.. 3: "foo" "foo\n"   "\n"
71//! 4.. 7: "bar" "bar\r\n" "\r\n"
72//! 9..12: "baz" "baz"     ""
73//! ```
74//!
75//! # Current Line, Previous Line, and Next Line
76//!
77//! ```rust
78//! use line_span::LineSpanExt;
79//!
80//! let text = "foo\nbar\r\nbaz";
81//! //                ^
82//! let i = 5; // 'a' in "bar"
83//!
84//! let curr_range = text.find_line_range(i);
85//! let next_range = text.find_next_line_range(i).unwrap();
86//! let prev_range = text.find_prev_line_range(i).unwrap();
87//!
88//! assert_eq!(curr_range, 4..7);
89//! assert_eq!(&text[curr_range], "bar");
90//!
91//! assert_eq!(prev_range, 0..3);
92//! assert_eq!(&text[prev_range], "foo");
93//!
94//! assert_eq!(next_range, 9..12);
95//! assert_eq!(&text[next_range], "baz");
96//! ```
97//!
98//! # Range of Substring in String
99//!
100//! Use [`str_to_range`] (or [`str_to_range_unchecked`]) to get the
101//! range of a substring in a string.
102//!
103//! ```rust
104//! # use line_span::str_to_range;
105//! let string1 = "Foo Bar Baz";
106//! let string2 = "Hello World";
107//!
108//! let substring = &string1[4..7]; // "Bar"
109//! # assert_eq!(substring, "Bar");
110//!
111//! // Returns `Some` as `substring` is a part of `string1`
112//! assert_eq!(str_to_range(string1, substring), Some(4..7));
113//!
114//! // Returns `None` as `substring` is not a part of `string2`
115//! assert_eq!(str_to_range(string2, substring), None);
116//! ```
117//!
118//! [`Deref`]: core::ops::Deref
119//! [`&str`]: core::str
120//! [`lines()`]: str::lines
121//! [`str::lines()`]: str::lines
122//! [`String`]: https://doc.rust-lang.org/std/string/struct.String.html
123
124#![forbid(unsafe_code)]
125#![forbid(elided_lifetimes_in_paths)]
126#![deny(missing_docs)]
127// #![deny(missing_doc_code_examples)]
128#![deny(missing_debug_implementations)]
129#![warn(clippy::all)]
130#![no_std]
131
132#[cfg(feature = "alloc")]
133extern crate alloc;
134
135use core::fmt;
136use core::iter::FusedIterator;
137use core::ops::{Deref, Range};
138use core::str::Lines;
139
140/// Trait implementing utility methods for finding the start, end, and range of
141/// lines from a byte index. Further also being able to find the next and
142/// previous line, from an arbitrary byte index.
143pub trait LineSpanExt {
144    /// Creates a [`LineSpanIter`].
145    ///
146    /// # Example
147    ///
148    /// ```rust
149    /// use line_span::LineSpanExt;
150    ///
151    /// let text = "foo\nbar\r\nbaz";
152    ///
153    /// for span in text.line_spans() {
154    ///     println!(
155    ///         "{:>2?}: {:?} {:?} {:?}",
156    ///         span.range(),
157    ///         span.as_str(),
158    ///         span.as_str_with_ending(),
159    ///         span.ending_str(),
160    ///     );
161    /// }
162    /// # let mut spans = text.line_spans();
163    /// # assert!(matches!(spans.next(), Some(span) if span.range() == (0..3)));
164    /// # assert!(matches!(spans.next(), Some(span) if span.range() == (4..7)));
165    /// # assert!(matches!(spans.next(), Some(span) if span.range() == (9..12)));
166    /// # assert_eq!(spans.next(), None);
167    /// ```
168    ///
169    /// This will output the following:
170    ///
171    /// _(Manually aligned for better readability)_
172    ///
173    /// ```text
174    /// 0.. 3: "foo" "foo\n"   "\n"
175    /// 4.. 7: "bar" "bar\r\n" "\r\n"
176    /// 9..12: "baz" "baz"     ""
177    /// ```
178    fn line_spans(&self) -> LineSpanIter<'_>;
179
180    /// Find the start (byte index) of the line, which `index` is within.
181    ///
182    /// # Panics
183    ///
184    /// Panics if `index` is out of bounds.
185    ///
186    /// # Example
187    ///
188    /// ```rust
189    /// # use line_span::LineSpanExt;
190    /// let text = "foo\nbar\nbaz";
191    /// let i = 5; // 'a'
192    ///
193    /// let start = text.find_line_start(i);
194    ///
195    /// assert_eq!(start, 4);
196    /// assert_eq!(&text[start..], "bar\nbaz");
197    /// ```
198    fn find_line_start(&self, index: usize) -> usize;
199
200    /// Find the end (byte index) of the line, which `index` is within.
201    ///
202    /// Note the end is the last character, excluding `\n` and `\r\n`.
203    ///
204    /// # Panics
205    ///
206    /// Panics if `index` is out of bounds.
207    ///
208    /// # Example
209    ///
210    /// ```rust
211    /// # use line_span::LineSpanExt;
212    /// let text = "foo\nbar\nbaz";
213    /// let i = 5; // 'a'
214    ///
215    /// let end = text.find_line_end(i);
216    ///
217    /// assert_eq!(end, 7);
218    /// assert_eq!(&text[..end], "foo\nbar");
219    /// ```
220    fn find_line_end(&self, index: usize) -> usize;
221
222    /// Find the start and end (byte index) of the line, which `index` is within.
223    ///
224    /// Note the end is the last character, excluding `\n` and `\r\n`.
225    ///
226    /// # Panics
227    ///
228    /// Panics if `index` is out of bounds.
229    ///
230    /// # Example
231    ///
232    /// ```rust
233    /// # use line_span::LineSpanExt;
234    /// let text = "foo\nbar\nbaz";
235    /// //                ^
236    /// let i = 5; // 'a'
237    ///
238    /// let range = text.find_line_range(i);
239    /// assert_eq!(range, 4..7);
240    ///
241    /// let line = &text[range];
242    /// assert_eq!(line, "bar");
243    /// ```
244    fn find_line_range(&self, index: usize) -> Range<usize>;
245
246    /// Find the start (byte index) of the next line, the line after the one which `index` is within.
247    ///
248    /// Returns [`None`] if there is no next line.
249    ///
250    /// # Panics
251    ///
252    /// Panics if `index` is out of bounds.
253    ///
254    /// # Example
255    ///
256    /// ```rust
257    /// # use line_span::LineSpanExt;
258    /// let text = "foo\nbar\nbaz";
259    /// //           ^
260    /// let i = 1; // 'o'
261    ///
262    /// let start = text.find_next_line_start(i).unwrap();
263    ///
264    /// assert_eq!(start, 4);
265    /// assert_eq!(&text[start..], "bar\nbaz");
266    /// ```
267    fn find_next_line_start(&self, index: usize) -> Option<usize>;
268
269    /// Find the end (byte index) of the next line, the line after the one which `index` is within.
270    ///
271    /// Note the end is the last character, excluding `\n` and `\r\n`.
272    ///
273    /// Returns [`None`] if there is no next line.
274    ///
275    /// # Panics
276    ///
277    /// Panics if `index` is out of bounds.
278    ///
279    /// # Example
280    ///
281    /// ```rust
282    /// # use line_span::LineSpanExt;
283    /// let text = "foo\nbar\nbaz";
284    /// //           ^
285    /// let i = 1; // 'o'
286    ///
287    /// let end = text.find_next_line_end(i).unwrap();
288    ///
289    /// assert_eq!(end, 7);
290    /// assert_eq!(&text[..end], "foo\nbar");
291    /// ```
292    fn find_next_line_end(&self, index: usize) -> Option<usize>;
293
294    /// Find the start and end (byte index) of the next line, the line after the one which `index` is within.
295    ///
296    /// Note the end is the last character, excluding `\n` and `\r\n`.
297    ///
298    /// Returns [`None`] if there is no next line.
299    ///
300    /// # Panics
301    ///
302    /// Panics if `index` is out of bounds.
303    ///
304    /// # Example
305    ///
306    /// ```rust
307    /// # use line_span::LineSpanExt;
308    /// let text = "foo\nbar\nbaz";
309    /// //           ^
310    /// let i = 1; // 'o'
311    ///
312    /// let range = text.find_next_line_range(i).unwrap();
313    /// assert_eq!(range, 4..7);
314    ///
315    /// let line = &text[range];
316    /// assert_eq!(line, "bar");
317    /// ```
318    fn find_next_line_range(&self, index: usize) -> Option<Range<usize>>;
319
320    /// Find the start (byte index) of the previous line, the line before the one which `index` is within.
321    ///
322    /// Returns [`None`] if there is no previous line.
323    ///
324    /// # Panics
325    ///
326    /// Panics if `index` is out of bounds.
327    ///
328    /// # Example
329    ///
330    /// ```rust
331    /// # use line_span::LineSpanExt;
332    /// let text = "foo\nbar\nbaz";
333    /// //                     ^
334    /// let i = 9; // 'a'
335    ///
336    /// let start = text.find_prev_line_start(i).unwrap();
337    ///
338    /// assert_eq!(start, 4);
339    /// assert_eq!(&text[start..], "bar\nbaz");
340    /// ```
341    fn find_prev_line_start(&self, index: usize) -> Option<usize>;
342
343    /// Find the end (byte index) of the previous line, the line before the one which `index` is within.
344    ///
345    /// Note the end is the last character, excluding `\n` and `\r\n`.
346    ///
347    /// Returns [`None`] if there is no previous line.
348    ///
349    /// # Panics
350    ///
351    /// Panics if `index` is out of bounds.
352    ///
353    /// # Example
354    ///
355    /// ```rust
356    /// # use line_span::LineSpanExt;
357    /// let text = "foo\nbar\nbaz";
358    /// //                     ^
359    /// let i = 9; // 'a'
360    ///
361    /// let end = text.find_prev_line_end(i).unwrap();
362    ///
363    /// assert_eq!(end, 7);
364    /// assert_eq!(&text[..end], "foo\nbar");
365    /// ```
366    fn find_prev_line_end(&self, index: usize) -> Option<usize>;
367
368    /// Find the start and end (byte index) of the previous line, the line before the one which `index` is within.
369    ///
370    /// Note the end is the last character, excluding `\n` and `\r\n`.
371    ///
372    /// Returns [`None`] if there is no previous line.
373    ///
374    /// # Panics
375    ///
376    /// Panics if `index` is out of bounds.
377    ///
378    /// # Example
379    ///
380    /// ```rust
381    /// # use line_span::LineSpanExt;
382    /// let text = "foo\nbar\nbaz";
383    /// //                     ^
384    /// let i = 9; // 'a'
385    ///
386    /// let range = text.find_prev_line_range(i).unwrap();
387    /// assert_eq!(range, 4..7);
388    ///
389    /// let line = &text[range];
390    /// assert_eq!(line, "bar");
391    /// ```
392    fn find_prev_line_range(&self, index: usize) -> Option<Range<usize>>;
393}
394
395impl LineSpanExt for str {
396    #[inline]
397    fn line_spans(&self) -> LineSpanIter<'_> {
398        LineSpanIter::from(self)
399    }
400
401    #[inline]
402    fn find_line_start(&self, index: usize) -> usize {
403        self[..index].rfind('\n').map_or(0, |i| i + 1)
404    }
405
406    #[inline]
407    fn find_line_end(&self, index: usize) -> usize {
408        let end: usize = self[index..]
409            .find('\n')
410            .map_or_else(|| self.len(), |i| index + i);
411        if (end > 0) && (self.as_bytes()[end - 1] == b'\r') {
412            end - 1
413        } else {
414            end
415        }
416    }
417
418    #[inline]
419    fn find_line_range(&self, index: usize) -> Range<usize> {
420        let start = self.find_line_start(index);
421        let end = self.find_line_end(index);
422        start..end
423    }
424
425    #[inline]
426    fn find_next_line_start(&self, index: usize) -> Option<usize> {
427        let i = self[index..].find('\n')?;
428        Some(index + i + 1)
429    }
430
431    #[inline]
432    fn find_next_line_end(&self, index: usize) -> Option<usize> {
433        let index = self.find_next_line_start(index)?;
434        let index = self.find_line_end(index);
435        Some(index)
436    }
437
438    #[inline]
439    fn find_next_line_range(&self, index: usize) -> Option<Range<usize>> {
440        let start = self.find_next_line_start(index)?;
441        let end = self.find_line_end(start);
442        Some(start..end)
443    }
444
445    #[inline]
446    fn find_prev_line_start(&self, index: usize) -> Option<usize> {
447        let index = self.find_prev_line_end(index)?;
448        let index = self.find_line_start(index);
449        Some(index)
450    }
451
452    #[inline]
453    fn find_prev_line_end(&self, index: usize) -> Option<usize> {
454        let mut end: usize = self[..index].rfind('\n')?;
455        if (end > 0) && (self.as_bytes()[end - 1] == b'\r') {
456            end -= 1;
457        }
458        Some(end)
459    }
460
461    #[inline]
462    fn find_prev_line_range(&self, index: usize) -> Option<Range<usize>> {
463        let end = self.find_prev_line_end(index)?;
464        let start = self.find_line_start(end);
465        Some(start..end)
466    }
467}
468
469/// Use [`LineSpanExt::find_line_start()`] instead.
470#[inline]
471pub fn find_line_start(text: &str, index: usize) -> usize {
472    text.find_line_start(index)
473}
474
475/// Use [`LineSpanExt::find_line_end()`] instead.
476pub fn find_line_end(text: &str, index: usize) -> usize {
477    text.find_line_end(index)
478}
479
480/// Use [`LineSpanExt::find_line_range()`] instead.
481#[inline]
482pub fn find_line_range(text: &str, index: usize) -> Range<usize> {
483    text.find_line_range(index)
484}
485
486/// Use [`LineSpanExt::find_next_line_start()`] instead.
487#[inline]
488pub fn find_next_line_start(text: &str, index: usize) -> Option<usize> {
489    text.find_next_line_start(index)
490}
491
492/// Use [`LineSpanExt::find_next_line_end()`] instead.
493#[inline]
494pub fn find_next_line_end(text: &str, index: usize) -> Option<usize> {
495    text.find_next_line_end(index)
496}
497
498/// Use [`LineSpanExt::find_next_line_range()`] instead.
499#[inline]
500pub fn find_next_line_range(text: &str, index: usize) -> Option<Range<usize>> {
501    text.find_next_line_range(index)
502}
503
504/// Use [`LineSpanExt::find_prev_line_start()`] instead.
505#[inline]
506pub fn find_prev_line_start(text: &str, index: usize) -> Option<usize> {
507    text.find_prev_line_start(index)
508}
509
510/// Use [`LineSpanExt::find_prev_line_end()`] instead.
511#[inline]
512pub fn find_prev_line_end(text: &str, index: usize) -> Option<usize> {
513    text.find_prev_line_end(index)
514}
515
516/// Use [`LineSpanExt::find_prev_line_range()`] instead.
517#[inline]
518pub fn find_prev_line_range(text: &str, index: usize) -> Option<Range<usize>> {
519    text.find_prev_line_range(index)
520}
521
522/// Get the start and end (byte index) range (`Range<usize>`), where `substring`
523/// is located in `string`.
524/// The returned range is relative to `string`.
525///
526/// Returns `Some` if `substring` is a part of `string`, otherwise `None`.
527///
528/// *For an unchecked version, check out [`str_to_range_unchecked()`].*
529///
530/// # Example
531///
532/// ```rust
533/// # use line_span::str_to_range;
534/// let string1 = "Foo Bar Baz";
535/// let string2 = "Hello World";
536///
537/// let substring = &string1[4..7]; // "Bar"
538/// # assert_eq!(substring, "Bar");
539///
540/// // Returns `Some` as `substring` is a part of `string1`
541/// assert_eq!(str_to_range(string1, substring), Some(4..7));
542///
543/// // Returns `None` as `substring` is not a part of `string2`
544/// assert_eq!(str_to_range(string2, substring), None);
545/// ```
546///
547/// # Example - Substring of Substring
548///
549/// Since the resulting range is relative to `string`, that implies
550/// `substring` can be a substring of a substring of a substring of ...
551/// and so on.
552///
553/// ```rust
554/// # use line_span::str_to_range;
555/// let s1 = "Foo Bar Baz";
556///
557/// // Substring of `s1`
558/// let s2 = &s1[4..11]; // "Bar Baz"
559///
560/// // Substring of `s1`
561/// let s3 = &s1[4..7]; // "Bar"
562///
563/// // Substring of `s2`, which is a substring of `s1`
564/// let s4 = &s2[0..3]; // "Bar"
565///
566/// // Get the range of `s2` relative to `s1`
567/// assert_eq!(str_to_range(s1, s2), Some(4..11));
568///
569/// // Get the range of `s3` relative to `s1`
570/// assert_eq!(str_to_range(s1, s3), Some(4..7));
571///
572/// // Get the range of `s4` relative to `s1`
573/// assert_eq!(str_to_range(s1, s4), Some(4..7));
574///
575/// // Get the range of `s4` relative to `s2`
576/// assert_eq!(str_to_range(s2, s4), Some(0..3));
577/// ```
578#[inline]
579pub fn str_to_range(string: &str, substring: &str) -> Option<Range<usize>> {
580    let str_start = string.as_ptr() as usize;
581    let sub_start = substring.as_ptr() as usize;
582
583    if str_start <= sub_start {
584        let start = sub_start - str_start;
585        let end = start + substring.len();
586
587        if (sub_start + substring.len()) <= (str_start + string.len()) {
588            return Some(start..end);
589        }
590    }
591
592    None
593}
594
595/// Get the start and end (byte index) range (`Range<usize>`), where `substring`
596/// is located in `string`.
597/// The returned range is relative to `string`.
598///
599/// If `substring` is not a part of `string`, it either panics or returns an
600/// invalid range.
601///
602/// *For a safe version, check out [`str_to_range()`].*
603///
604/// # Panics
605///
606/// Panics if `substring` is not a substring of `string`. \*
607///
608/// \* Panicking depends on where the strings are located in memory. It might
609/// not panic but instead return an invalid range.
610///
611/// # Example
612///
613/// ```
614/// # use line_span::str_to_range_unchecked;
615/// let string = "Foo Bar Baz";
616///
617/// let substring = &string[4..7]; // "Bar"
618/// # assert_eq!(substring, "Bar");
619///
620/// assert_eq!(str_to_range_unchecked(string, substring), 4..7);
621/// ```
622///
623/// # Example - Substring of Substring
624///
625/// Since the resulting range is relative to `string`, that implies
626/// `substring` can be a substring of a substring of a substring of ...
627/// and so on.
628///
629/// ```
630/// # use line_span::str_to_range_unchecked;
631/// let s1 = "Foo Bar Baz";
632///
633/// // Substring of `s1`
634/// let s2 = &s1[4..11]; // "Bar Baz"
635///
636/// // Substring of `s1`
637/// let s3 = &s1[4..7]; // "Bar"
638///
639/// // Substring of `s2`, which is a substring of `s1`
640/// let s4 = &s2[0..3]; // "Bar"
641///
642/// // Get the range of `s2` relative to `s1`
643/// assert_eq!(str_to_range_unchecked(s1, s2), 4..11);
644///
645/// // Get the range of `s3` relative to `s1`
646/// assert_eq!(str_to_range_unchecked(s1, s3), 4..7);
647///
648/// // Get the range of `s4` relative to `s1`
649/// assert_eq!(str_to_range_unchecked(s1, s4), 4..7);
650///
651/// // Get the range of `s4` relative to `s2`
652/// assert_eq!(str_to_range_unchecked(s2, s4), 0..3);
653/// ```
654#[inline]
655pub fn str_to_range_unchecked(string: &str, substring: &str) -> Range<usize> {
656    let start = (substring.as_ptr() as usize) - (string.as_ptr() as usize);
657    let end = start + substring.len();
658    start..end
659}
660
661/// `LineSpan` represents a single line. It is possible to
662/// get a `&str` of the line both including and excluding
663/// `\n` and `\r\n`.
664///
665/// ```no_run
666/// use line_span::LineSpans;
667///
668/// let text = "foo\nbar\r\nbaz";
669///
670/// for span in text.line_spans() {
671///     println!(
672///         "{:>2?}: {:?} {:?}",
673///         span.range(),
674///         span.as_str(),
675///         span.as_str_with_ending(),
676///     );
677/// }
678/// ```
679///
680/// This will output the following:
681///
682/// ```text
683/// 0.. 3: "foo" "foo\n"
684/// 4.. 7: "bar" "bar\r\n"
685/// 9..12: "baz" "baz"
686/// ```
687#[derive(PartialEq, Eq, Clone, Copy)]
688pub struct LineSpan<'a> {
689    text: &'a str,
690    start: usize,
691    end: usize,
692    ending: usize,
693}
694
695impl<'a> LineSpan<'a> {
696    /// Returns the byte index of the start of the line.
697    #[inline]
698    pub fn start(&self) -> usize {
699        self.start
700    }
701
702    /// Returns the byte index of the end of the line,
703    /// excluding the line ending part `\n` or `\r\n`.
704    ///
705    /// To include the line ending part, then use [`ending()`].
706    ///
707    /// [`ending()`]: Self::ending
708    #[inline]
709    pub fn end(&self) -> usize {
710        self.end
711    }
712
713    /// Returns the byte index of the end of the line,
714    /// including the line ending part `\n` or `\r\n`.
715    ///
716    /// To exclude the line ending part, then use [`end()`].
717    ///
718    /// [`end()`]: Self::end
719    #[inline]
720    pub fn ending(&self) -> usize {
721        self.ending
722    }
723
724    /// Returns the byte index range of the start and
725    /// end of the line, excluding the line ending
726    /// part `\n` or `\r\n`.
727    ///
728    /// To include the line ending part, then use [`range_with_ending()`].
729    ///
730    /// [`range_with_ending()`]: Self::range_with_ending
731    #[inline]
732    pub fn range(&self) -> Range<usize> {
733        self.start..self.end
734    }
735
736    /// Returns the byte index range of the start and
737    /// end of the line, including the line ending
738    /// part `\n` or `\r\n`.
739    ///
740    /// To exclude the line ending part, then use [`range()`].
741    ///
742    /// [`range()`]: Self::range
743    #[inline]
744    pub fn range_with_ending(&self) -> Range<usize> {
745        self.start..self.ending
746    }
747
748    /// Returns the start and end byte index range of
749    /// the ending part of the line, i.e. just the
750    /// `\n` or `\r\n` part.
751    #[inline]
752    pub fn ending_range(&self) -> Range<usize> {
753        self.end..self.ending
754    }
755
756    /// Returns `&str` of the line, excluding `\n` and `\r\n`.
757    ///
758    /// To include the line ending part, then use [`as_str_with_ending()`].
759    ///
760    /// [`as_str_with_ending()`]: Self::as_str_with_ending
761    #[inline]
762    pub fn as_str(&self) -> &'a str {
763        &self.text[self.range()]
764    }
765
766    /// Returns `&str` of the line, including `\n` and `\r\n`.
767    ///
768    /// To exclude the line ending part, then use [`as_str()`].
769    ///
770    /// [`as_str()`]: Self::as_str
771    #[inline]
772    pub fn as_str_with_ending(&self) -> &'a str {
773        &self.text[self.range_with_ending()]
774    }
775
776    /// Returns the string slice of the line ending, i.e.
777    /// just the `"\n"` or `"\r\n"` part or nothing, if
778    /// there is no line ending.
779    #[inline]
780    pub fn ending_str(&self) -> &'a str {
781        &self.text[self.ending_range()]
782    }
783}
784
785impl<'a> Deref for LineSpan<'a> {
786    type Target = str;
787
788    /// Returns [`as_str()`].
789    ///
790    /// [`as_str()`]: Self::as_str
791    #[inline]
792    fn deref(&self) -> &Self::Target {
793        self.as_str()
794    }
795}
796
797impl<'a> From<LineSpan<'a>> for &'a str {
798    /// Returns [`as_str()`].
799    ///
800    /// [`as_str()`]: LineSpan::as_str
801    #[inline]
802    fn from(span: LineSpan<'a>) -> &'a str {
803        span.as_str()
804    }
805}
806
807impl<'a> From<LineSpan<'a>> for Range<usize> {
808    /// Returns [`range()`].
809    ///
810    /// [`range()`]: LineSpan::range
811    #[inline]
812    fn from(span: LineSpan<'a>) -> Range<usize> {
813        span.range()
814    }
815}
816
817impl<'a> fmt::Debug for LineSpan<'a> {
818    /// Renders [`start()`], [`end()`], and [`ending()`]
819    /// of [`LineSpan`] and [`as_str()`] as `"line"`.
820    ///
821    /// [`start()`]: Self::start
822    /// [`end()`]: Self::end
823    /// [`ending()`]: Self::ending
824    /// [`as_str()`]: Self::as_str
825    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
826        fmt.debug_struct("LineSpan")
827            .field("start", &self.start)
828            .field("end", &self.end)
829            .field("ending", &self.ending)
830            .field("line", &self.as_str())
831            .finish()
832    }
833}
834
835impl<'a> fmt::Display for LineSpan<'a> {
836    /// Renders [`as_str`].
837    ///
838    /// [`as_str`]: struct.LineSpan.html#method.as_str
839    #[inline]
840    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
841        self.as_str().fmt(fmt)
842    }
843}
844
845/// An iterator over [`LineSpan`]s.
846///
847/// This struct is created with the [`line_spans()`] method in the [`LineSpans`] trait.
848/// See its documentation for more.
849///
850/// [`line_spans()`]: LineSpans::line_spans
851#[allow(missing_debug_implementations)]
852#[derive(Clone)]
853pub struct LineSpanIter<'a> {
854    text: &'a str,
855    iter: Lines<'a>,
856}
857
858impl<'a> LineSpanIter<'a> {
859    #[inline]
860    fn from(text: &'a str) -> Self {
861        Self {
862            text,
863            iter: text.lines(),
864        }
865    }
866}
867
868impl<'a> Iterator for LineSpanIter<'a> {
869    type Item = LineSpan<'a>;
870
871    #[inline]
872    fn next(&mut self) -> Option<Self::Item> {
873        if let Some(line) = self.iter.next() {
874            let Range { start, end } = str_to_range_unchecked(self.text, line);
875            let ending = find_next_line_start(self.text, end).unwrap_or(self.text.len());
876
877            Some(LineSpan {
878                text: self.text,
879                start,
880                end,
881                ending,
882            })
883        } else {
884            None
885        }
886    }
887}
888
889impl<'a> FusedIterator for LineSpanIter<'a> {}
890
891/// Trait which implements [`line_spans()`] to get a [`LineSpanIter`].
892///
893/// ```no_run
894/// use line_span::LineSpans;
895///
896/// let text = "foo\nbar\r\nbaz";
897///
898/// for span in text.line_spans() {
899///     println!("{:>2?}: {:?}", span.range(), span.as_str());
900/// }
901/// ```
902///
903/// This will output the following:
904///
905/// ```text
906///  0.. 3: "foo"
907///  4.. 7: "bar"
908///  9..12: "baz"
909/// ```
910///
911/// [`line_spans()`]: LineSpans::line_spans
912pub trait LineSpans {
913    /// Creates a [`LineSpanIter`].
914    fn line_spans(&self) -> LineSpanIter<'_>;
915}
916
917impl LineSpans for str {
918    #[inline]
919    fn line_spans(&self) -> LineSpanIter<'_> {
920        LineSpanIter::from(self)
921    }
922}
923
924#[cfg(feature = "alloc")]
925impl LineSpans for alloc::string::String {
926    #[inline]
927    fn line_spans(&self) -> LineSpanIter<'_> {
928        LineSpanIter::from(self.as_str())
929    }
930}
931
932#[cfg(test)]
933mod tests {
934    use super::{LineSpan, LineSpanExt};
935
936    #[test]
937    fn test_line_spans() {
938        let text = "\r\nfoo\nbar\r\nbaz\nqux\r\n\r";
939
940        let mut it = text.line_spans();
941        assert_eq!(Some(""), it.next().as_deref());
942        assert_eq!(Some("foo"), it.next().as_deref());
943        assert_eq!(Some("bar"), it.next().as_deref());
944        assert_eq!(Some("baz"), it.next().as_deref());
945        assert_eq!(Some("qux"), it.next().as_deref());
946        assert_eq!(Some("\r"), it.next().as_deref());
947        assert_eq!(None, it.next().as_deref());
948
949        let mut it = text.line_spans().map(|span| span.range());
950        assert_eq!(Some(0..0), it.next());
951        assert_eq!(Some(2..5), it.next());
952        assert_eq!(Some(6..9), it.next());
953        assert_eq!(Some(11..14), it.next());
954        assert_eq!(Some(15..18), it.next());
955        assert_eq!(Some(20..21), it.next());
956        assert_eq!(None, it.next());
957    }
958
959    #[test]
960    fn test_line_spans_vs_lines() {
961        let text = "\r\nfoo\nbar\r\nbaz\nqux\r\n\r";
962
963        let mut iter_spans = text.line_spans();
964        let mut iter_lines = text.lines();
965
966        loop {
967            let span = iter_spans.next();
968            let line = iter_lines.next();
969
970            assert_eq!(span.map(|s| s.as_str()), line);
971
972            if span.is_none() {
973                break;
974            }
975        }
976    }
977
978    #[test]
979    fn test_line_span_ending() {
980        let text = "\r\nfoo\nbar\r\nbaz\nqux\r\n\r";
981
982        let lines = [
983            ("", "\r\n"),
984            ("foo", "foo\n"),
985            ("bar", "bar\r\n"),
986            ("baz", "baz\n"),
987            ("qux", "qux\r\n"),
988            ("\r", "\r"),
989        ];
990
991        let mut iter = text.line_spans();
992
993        for &expected in lines.iter() {
994            let actual = iter.next();
995            let actual = actual.map(|span| (span.as_str(), span.as_str_with_ending()));
996
997            assert_eq!(Some(expected), actual);
998        }
999
1000        assert_eq!(None, iter.next());
1001    }
1002
1003    #[test]
1004    fn lib_example() {
1005        // If this example is changed, then update both the
1006        // code and the output in lib.rs and README.md.
1007
1008        let text = "foo\nbar\r\nbaz";
1009
1010        let spans = [
1011            LineSpan {
1012                text,
1013                start: 0,
1014                end: 3,
1015                ending: 4,
1016            },
1017            LineSpan {
1018                text,
1019                start: 4,
1020                end: 7,
1021                ending: 9,
1022            },
1023            LineSpan {
1024                text,
1025                start: 9,
1026                end: 12,
1027                ending: 12,
1028            },
1029        ];
1030
1031        #[rustfmt::skip]
1032        let lines = [
1033            ("foo", "foo\n"),
1034            ("bar", "bar\r\n"),
1035            ("baz", "baz"),
1036        ];
1037
1038        assert_eq!(spans.len(), lines.len());
1039
1040        let mut iter = text.line_spans();
1041
1042        for (expected, (line_end, line_ending)) in spans.iter().zip(&lines) {
1043            let actual = iter.next();
1044            assert_eq!(Some(*expected), actual);
1045
1046            let actual = actual.unwrap();
1047            assert_eq!(*line_end, actual.as_str());
1048            assert_eq!(*line_ending, actual.as_str_with_ending());
1049        }
1050
1051        assert_eq!(None, iter.next());
1052    }
1053}