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}