|
| 1 | +mod line_index; |
| 2 | +// mod locator; |
| 3 | +// pub mod newline; |
| 4 | + |
| 5 | +pub use crate::line_index::{LineIndex, OneIndexed}; |
| 6 | +// TODO: RUSTPYTHON; import it later |
| 7 | +// pub use locator::Locator; |
| 8 | +use ruff_text_size::{TextRange, TextSize}; |
| 9 | +#[cfg(feature = "serde")] |
| 10 | +use serde::{Deserialize, Serialize}; |
| 11 | +use std::fmt::{Debug, Formatter}; |
| 12 | +use std::sync::Arc; |
| 13 | + |
| 14 | +/// Gives access to the source code of a file and allows mapping between [`TextSize`] and [`SourceLocation`]. |
| 15 | +#[derive(Debug)] |
| 16 | +pub struct SourceCode<'src, 'index> { |
| 17 | + text: &'src str, |
| 18 | + index: &'index LineIndex, |
| 19 | +} |
| 20 | + |
| 21 | +impl<'src, 'index> SourceCode<'src, 'index> { |
| 22 | + pub fn new(content: &'src str, index: &'index LineIndex) -> Self { |
| 23 | + Self { |
| 24 | + text: content, |
| 25 | + index, |
| 26 | + } |
| 27 | + } |
| 28 | + |
| 29 | + /// Computes the one indexed row and column numbers for `offset`. |
| 30 | + #[inline] |
| 31 | + pub fn source_location(&self, offset: TextSize) -> SourceLocation { |
| 32 | + self.index.source_location(offset, self.text) |
| 33 | + } |
| 34 | + |
| 35 | + #[inline] |
| 36 | + pub fn line_index(&self, offset: TextSize) -> OneIndexed { |
| 37 | + self.index.line_index(offset) |
| 38 | + } |
| 39 | + |
| 40 | + /// Take the source code up to the given [`TextSize`]. |
| 41 | + #[inline] |
| 42 | + pub fn up_to(&self, offset: TextSize) -> &'src str { |
| 43 | + &self.text[TextRange::up_to(offset)] |
| 44 | + } |
| 45 | + |
| 46 | + /// Take the source code after the given [`TextSize`]. |
| 47 | + #[inline] |
| 48 | + pub fn after(&self, offset: TextSize) -> &'src str { |
| 49 | + &self.text[usize::from(offset)..] |
| 50 | + } |
| 51 | + |
| 52 | + /// Take the source code between the given [`TextRange`]. |
| 53 | + pub fn slice(&self, range: TextRange) -> &'src str { |
| 54 | + &self.text[range] |
| 55 | + } |
| 56 | + |
| 57 | + pub fn line_start(&self, line: OneIndexed) -> TextSize { |
| 58 | + self.index.line_start(line, self.text) |
| 59 | + } |
| 60 | + |
| 61 | + pub fn line_end(&self, line: OneIndexed) -> TextSize { |
| 62 | + self.index.line_end(line, self.text) |
| 63 | + } |
| 64 | + |
| 65 | + pub fn line_range(&self, line: OneIndexed) -> TextRange { |
| 66 | + self.index.line_range(line, self.text) |
| 67 | + } |
| 68 | + |
| 69 | + /// Returns the source text of the line with the given index |
| 70 | + #[inline] |
| 71 | + pub fn line_text(&self, index: OneIndexed) -> &'src str { |
| 72 | + let range = self.index.line_range(index, self.text); |
| 73 | + &self.text[range] |
| 74 | + } |
| 75 | + |
| 76 | + /// Returns the source text |
| 77 | + pub fn text(&self) -> &'src str { |
| 78 | + self.text |
| 79 | + } |
| 80 | + |
| 81 | + /// Returns the number of lines |
| 82 | + #[inline] |
| 83 | + pub fn line_count(&self) -> usize { |
| 84 | + self.index.line_count() |
| 85 | + } |
| 86 | +} |
| 87 | + |
| 88 | +impl PartialEq<Self> for SourceCode<'_, '_> { |
| 89 | + fn eq(&self, other: &Self) -> bool { |
| 90 | + self.text == other.text |
| 91 | + } |
| 92 | +} |
| 93 | + |
| 94 | +impl Eq for SourceCode<'_, '_> {} |
| 95 | + |
| 96 | +/// A Builder for constructing a [`SourceFile`] |
| 97 | +pub struct SourceFileBuilder { |
| 98 | + name: Box<str>, |
| 99 | + code: Box<str>, |
| 100 | + index: Option<LineIndex>, |
| 101 | +} |
| 102 | + |
| 103 | +impl SourceFileBuilder { |
| 104 | + /// Creates a new builder for a file named `name`. |
| 105 | + pub fn new<Name: Into<Box<str>>, Code: Into<Box<str>>>(name: Name, code: Code) -> Self { |
| 106 | + Self { |
| 107 | + name: name.into(), |
| 108 | + code: code.into(), |
| 109 | + index: None, |
| 110 | + } |
| 111 | + } |
| 112 | + |
| 113 | + #[must_use] |
| 114 | + pub fn line_index(mut self, index: LineIndex) -> Self { |
| 115 | + self.index = Some(index); |
| 116 | + self |
| 117 | + } |
| 118 | + |
| 119 | + pub fn set_line_index(&mut self, index: LineIndex) { |
| 120 | + self.index = Some(index); |
| 121 | + } |
| 122 | + |
| 123 | + /// Consumes `self` and returns the [`SourceFile`]. |
| 124 | + pub fn finish(self) -> SourceFile { |
| 125 | + let index = if let Some(index) = self.index { |
| 126 | + once_cell::sync::OnceCell::with_value(index) |
| 127 | + } else { |
| 128 | + once_cell::sync::OnceCell::new() |
| 129 | + }; |
| 130 | + |
| 131 | + SourceFile { |
| 132 | + inner: Arc::new(SourceFileInner { |
| 133 | + name: self.name, |
| 134 | + code: self.code, |
| 135 | + line_index: index, |
| 136 | + }), |
| 137 | + } |
| 138 | + } |
| 139 | +} |
| 140 | + |
| 141 | +/// A source file that is identified by its name. Optionally stores the source code and [`LineIndex`]. |
| 142 | +/// |
| 143 | +/// Cloning a [`SourceFile`] is cheap, because it only requires bumping a reference count. |
| 144 | +#[derive(Clone, Eq, PartialEq)] |
| 145 | +pub struct SourceFile { |
| 146 | + inner: Arc<SourceFileInner>, |
| 147 | +} |
| 148 | + |
| 149 | +impl Debug for SourceFile { |
| 150 | + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { |
| 151 | + f.debug_struct("SourceFile") |
| 152 | + .field("name", &self.name()) |
| 153 | + .field("code", &self.source_text()) |
| 154 | + .finish() |
| 155 | + } |
| 156 | +} |
| 157 | + |
| 158 | +impl SourceFile { |
| 159 | + /// Returns the name of the source file (filename). |
| 160 | + #[inline] |
| 161 | + pub fn name(&self) -> &str { |
| 162 | + &self.inner.name |
| 163 | + } |
| 164 | + |
| 165 | + #[inline] |
| 166 | + pub fn slice(&self, range: TextRange) -> &str { |
| 167 | + &self.source_text()[range] |
| 168 | + } |
| 169 | + |
| 170 | + pub fn to_source_code(&self) -> SourceCode { |
| 171 | + SourceCode { |
| 172 | + text: self.source_text(), |
| 173 | + index: self.index(), |
| 174 | + } |
| 175 | + } |
| 176 | + |
| 177 | + fn index(&self) -> &LineIndex { |
| 178 | + self.inner |
| 179 | + .line_index |
| 180 | + .get_or_init(|| LineIndex::from_source_text(self.source_text())) |
| 181 | + } |
| 182 | + |
| 183 | + /// Returns `Some` with the source text if set, or `None`. |
| 184 | + #[inline] |
| 185 | + pub fn source_text(&self) -> &str { |
| 186 | + &self.inner.code |
| 187 | + } |
| 188 | +} |
| 189 | + |
| 190 | +struct SourceFileInner { |
| 191 | + name: Box<str>, |
| 192 | + code: Box<str>, |
| 193 | + line_index: once_cell::sync::OnceCell<LineIndex>, |
| 194 | +} |
| 195 | + |
| 196 | +impl PartialEq for SourceFileInner { |
| 197 | + fn eq(&self, other: &Self) -> bool { |
| 198 | + self.name == other.name && self.code == other.code |
| 199 | + } |
| 200 | +} |
| 201 | + |
| 202 | +impl Eq for SourceFileInner {} |
| 203 | + |
| 204 | +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Copy)] |
| 205 | +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
| 206 | +pub struct SourceLocation { |
| 207 | + pub row: OneIndexed, |
| 208 | + pub column: OneIndexed, |
| 209 | +} |
| 210 | + |
| 211 | +impl Default for SourceLocation { |
| 212 | + fn default() -> Self { |
| 213 | + Self { |
| 214 | + row: OneIndexed::MIN, |
| 215 | + column: OneIndexed::MIN, |
| 216 | + } |
| 217 | + } |
| 218 | +} |
| 219 | + |
| 220 | +impl Debug for SourceLocation { |
| 221 | + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { |
| 222 | + f.debug_struct("SourceLocation") |
| 223 | + .field("row", &self.row.get()) |
| 224 | + .field("column", &self.column.get()) |
| 225 | + .finish() |
| 226 | + } |
| 227 | +} |
0 commit comments