1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
use super::*;

impl Debug for SourceSpan {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("FileSpan").field("start", &self.start).field("end", &self.end).field("file", &self.file).finish()
    }
}

impl Display for SourceSpan {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "FileSpan(0x{:X}, {}..{})", self.file.hash, self.start, self.end)
    }
}

impl<S: Into<String>> From<S> for SourceText {
    /// Generate a [`SourceText`] from the given [`str`].
    ///
    /// Note that this function can be expensive for long strings. Use an implementor of [`Cache`] where possible.
    fn from(source: S) -> Self {
        let text = source.into();
        let mut offset = 0;
        // (Last line, last line ends with CR)
        let mut last_line: Option<(SourceLine, bool)> = None;
        let mut lines: Vec<SourceLine> = text
            .split_inclusive([
                '\r',       // Carriage return
                '\n',       // Line feed
                '\x0B',     // Vertical tab
                '\x0C',     // Form feed
                '\u{0085}', // Next line
                '\u{2028}', // Line separator
                '\u{2029}', // Paragraph separator
            ])
            .flat_map(|line| {
                // Returns last line and set `last_line` to current `line`
                // A hack that makes `flat_map` deals with consecutive lines

                if let Some((last, ends_with_cr)) = last_line.as_mut() {
                    if *ends_with_cr && line == "\n" {
                        last.length += 1;
                        offset += 1;
                        return core::mem::replace(&mut last_line, None).map(|(l, _)| l);
                    }
                }

                let len = line.len();
                let ends_with_cr = line.ends_with('\r');
                let line = SourceLine { offset, length: len as u32, text: line.trim_end().to_owned() };
                offset += line.length;
                core::mem::replace(&mut last_line, Some((line, ends_with_cr))).map(|(l, _)| l)
            })
            .collect();

        if let Some((l, _)) = last_line {
            lines.push(l);
        }

        Self { path: SourcePath::Anonymous, raw: text, lines, length: offset, dirty: false }
    }
}