runestick/
source.rs

1use crate::Span;
2use std::fmt;
3use std::fs;
4use std::io;
5use std::path::{Path, PathBuf};
6use std::slice;
7
8/// A single source file.
9#[derive(Default, Clone)]
10pub struct Source {
11    /// The name of the source.
12    name: String,
13    /// The source string.
14    source: String,
15    /// The path the source was loaded from.
16    path: Option<PathBuf>,
17    /// The starting byte indices in the source code.
18    line_starts: Vec<usize>,
19}
20
21impl Source {
22    /// Construct a new source with the given name.
23    pub fn new<N, S>(name: N, source: S) -> Self
24    where
25        N: AsRef<str>,
26        S: AsRef<str>,
27    {
28        let source = source.as_ref();
29        let line_starts = line_starts(source).collect::<Vec<_>>();
30
31        Self {
32            name: name.as_ref().to_owned(),
33            source: source.to_owned(),
34            path: None,
35            line_starts,
36        }
37    }
38
39    /// Access all line starts in the source.
40    pub fn line_starts(&self) -> &[usize] {
41        &self.line_starts
42    }
43
44    /// Load a source from a path.
45    pub fn from_path(path: &Path) -> io::Result<Self> {
46        let name = path.display().to_string();
47        let path = &path.canonicalize()?;
48
49        let source = fs::read_to_string(path)?;
50        let line_starts = line_starts(&source).collect::<Vec<_>>();
51
52        Ok(Self {
53            name,
54            source,
55            path: Some(path.to_owned()),
56            line_starts,
57        })
58    }
59
60    /// Test if the source is empty.
61    pub fn is_empty(&self) -> bool {
62        self.source.is_empty()
63    }
64
65    /// Get the length of the source.
66    pub fn len(&self) -> usize {
67        self.source.len()
68    }
69
70    /// Get the name of the source.
71    pub fn name(&self) -> &str {
72        &self.name
73    }
74
75    /// Fetch source for the given span.
76    pub fn source(&self, span: Span) -> Option<&'_ str> {
77        self.get(span.range())
78    }
79
80    ///  et the given range from the source.
81    pub fn get<I>(&self, i: I) -> Option<&I::Output>
82    where
83        I: slice::SliceIndex<str>,
84    {
85        self.source.get(i)
86    }
87
88    /// Get the end of the source.
89    pub fn end(&self) -> usize {
90        self.source.len()
91    }
92
93    /// Access the underlying string for the source.
94    pub fn as_str(&self) -> &str {
95        &self.source
96    }
97
98    /// Get the (optional) path of the source.
99    pub fn path(&self) -> Option<&Path> {
100        self.path.as_deref()
101    }
102
103    /// Get a mutable path.
104    pub fn path_mut(&mut self) -> &mut Option<PathBuf> {
105        &mut self.path
106    }
107
108    /// Convert the given offset to a utf-16 line and character.
109    pub fn position_to_utf16cu_line_char(&self, offset: usize) -> Option<(usize, usize)> {
110        if offset == 0 {
111            return Some((0, 0));
112        }
113
114        let line = match self.line_starts.binary_search(&offset) {
115            Ok(exact) => exact,
116            Err(0) => return None,
117            Err(n) => n - 1,
118        };
119
120        let line_start = self.line_starts[line];
121
122        let rest = &self.source[line_start..];
123        let offset = offset - line_start;
124        let mut line_count = 0;
125
126        for (n, c) in rest.char_indices() {
127            if n == offset {
128                return Some((line, line_count));
129            }
130
131            if n > offset {
132                break;
133            }
134
135            line_count += c.encode_utf16(&mut [0u16; 2]).len();
136        }
137
138        Some((line, line_count))
139    }
140
141    /// Convert the given offset to a utf-16 line and character.
142    pub fn position_to_unicode_line_char(&self, offset: usize) -> (usize, usize) {
143        if offset == 0 {
144            return (0, 0);
145        }
146
147        let line = match self.line_starts.binary_search(&offset) {
148            Ok(exact) => exact,
149            Err(0) => return (0, 0),
150            Err(n) => n - 1,
151        };
152
153        let line_start = self.line_starts[line];
154
155        let rest = &self.source[line_start..];
156        let offset = offset - line_start;
157        let mut line_count = 0;
158
159        for (n, _) in rest.char_indices() {
160            if n == offset {
161                return (line, line_count);
162            }
163
164            if n > offset {
165                break;
166            }
167
168            line_count += 1;
169        }
170
171        (line, line_count)
172    }
173}
174
175impl fmt::Debug for Source {
176    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177        f.debug_struct("Source")
178            .field("name", &self.name)
179            .field("path", &self.path)
180            .finish()
181    }
182}
183
184fn line_starts(source: &str) -> impl Iterator<Item = usize> + '_ {
185    std::iter::once(0).chain(source.match_indices('\n').map(|(i, _)| i + 1))
186}