1use std::cmp::Ordering;
2use std::fmt::{Debug, Display, Formatter};
3use std::hash::Hash;
4use std::sync::{Arc, OnceLock};
5
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Serialize};
8
9use ruff_text_size::{Ranged, TextSize};
10
11pub use crate::line_index::{LineIndex, OneIndexed, PositionEncoding};
12pub use crate::line_ranges::LineRanges;
13pub use crate::newlines::{
14 Line, LineEnding, NewlineWithTrailingNewline, UniversalNewlineIterator, UniversalNewlines,
15 find_newline,
16};
17
18mod line_index;
19mod line_ranges;
20mod newlines;
21
22#[derive(Debug)]
24pub struct SourceCode<'src, 'index> {
25 text: &'src str,
26 index: &'index LineIndex,
27}
28
29impl<'src, 'index> SourceCode<'src, 'index> {
30 pub fn new(content: &'src str, index: &'index LineIndex) -> Self {
31 Self {
32 text: content,
33 index,
34 }
35 }
36
37 #[inline]
39 pub fn line_column(&self, offset: TextSize) -> LineColumn {
40 self.index.line_column(offset, self.text)
41 }
42
43 #[inline]
44 pub fn source_location(
45 &self,
46 offset: TextSize,
47 position_encoding: PositionEncoding,
48 ) -> SourceLocation {
49 self.index
50 .source_location(offset, self.text, position_encoding)
51 }
52
53 #[inline]
54 pub fn line_index(&self, offset: TextSize) -> OneIndexed {
55 self.index.line_index(offset)
56 }
57
58 pub fn slice<T: Ranged>(&self, ranged: T) -> &'src str {
60 &self.text[ranged.range()]
61 }
62
63 pub fn line_start(&self, line: OneIndexed) -> TextSize {
64 self.index.line_start(line, self.text)
65 }
66
67 pub fn line_end(&self, line: OneIndexed) -> TextSize {
68 self.index.line_end(line, self.text)
69 }
70
71 pub fn line_end_exclusive(&self, line: OneIndexed) -> TextSize {
72 self.index.line_end_exclusive(line, self.text)
73 }
74
75 #[inline]
77 pub fn line_text(&self, index: OneIndexed) -> &'src str {
78 let range = self.index.line_range(index, self.text);
79 &self.text[range]
80 }
81
82 pub fn text(&self) -> &'src str {
84 self.text
85 }
86
87 #[inline]
89 pub fn line_count(&self) -> usize {
90 self.index.line_count()
91 }
92}
93
94impl PartialEq<Self> for SourceCode<'_, '_> {
95 fn eq(&self, other: &Self) -> bool {
96 self.text == other.text
97 }
98}
99
100impl Eq for SourceCode<'_, '_> {}
101
102pub struct SourceFileBuilder {
104 name: Box<str>,
105 code: Box<str>,
106 index: Option<LineIndex>,
107}
108
109impl SourceFileBuilder {
110 pub fn new<Name: Into<Box<str>>, Code: Into<Box<str>>>(name: Name, code: Code) -> Self {
112 Self {
113 name: name.into(),
114 code: code.into(),
115 index: None,
116 }
117 }
118
119 pub fn finish(self) -> SourceFile {
121 let index = if let Some(index) = self.index {
122 OnceLock::from(index)
123 } else {
124 OnceLock::new()
125 };
126
127 SourceFile {
128 inner: Arc::new(SourceFileInner {
129 name: self.name,
130 code: self.code,
131 line_index: index,
132 }),
133 }
134 }
135}
136
137#[derive(Clone, Eq, PartialEq, Hash)]
141#[cfg_attr(feature = "get-size", derive(get_size2::GetSize))]
142pub struct SourceFile {
143 inner: Arc<SourceFileInner>,
144}
145
146impl Debug for SourceFile {
147 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
148 f.debug_struct("SourceFile")
149 .field("name", &self.name())
150 .field("code", &self.source_text())
151 .finish()
152 }
153}
154
155impl SourceFile {
156 #[inline]
158 pub fn name(&self) -> &str {
159 &self.inner.name
160 }
161
162 pub fn to_source_code(&self) -> SourceCode<'_, '_> {
163 SourceCode {
164 text: self.source_text(),
165 index: self.index(),
166 }
167 }
168
169 pub fn index(&self) -> &LineIndex {
170 self.inner
171 .line_index
172 .get_or_init(|| LineIndex::from_source_text(self.source_text()))
173 }
174
175 #[inline]
177 pub fn source_text(&self) -> &str {
178 &self.inner.code
179 }
180}
181
182impl PartialOrd for SourceFile {
183 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
184 Some(self.cmp(other))
185 }
186}
187
188impl Ord for SourceFile {
189 fn cmp(&self, other: &Self) -> Ordering {
190 if Arc::ptr_eq(&self.inner, &other.inner) {
192 Ordering::Equal
193 } else {
194 self.inner.name.cmp(&other.inner.name)
195 }
196 }
197}
198
199#[cfg_attr(feature = "get-size", derive(get_size2::GetSize))]
200struct SourceFileInner {
201 name: Box<str>,
202 code: Box<str>,
203 line_index: OnceLock<LineIndex>,
204}
205
206impl PartialEq for SourceFileInner {
207 fn eq(&self, other: &Self) -> bool {
208 self.name == other.name && self.code == other.code
209 }
210}
211
212impl Eq for SourceFileInner {}
213
214impl Hash for SourceFileInner {
215 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
216 self.name.hash(state);
217 self.code.hash(state);
218 }
219}
220
221#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
225#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
226pub struct LineColumn {
227 pub line: OneIndexed,
229 pub column: OneIndexed,
232}
233
234impl Default for LineColumn {
235 fn default() -> Self {
236 Self {
237 line: OneIndexed::MIN,
238 column: OneIndexed::MIN,
239 }
240 }
241}
242
243impl Debug for LineColumn {
244 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
245 f.debug_struct("LineColumn")
246 .field("line", &self.line.get())
247 .field("column", &self.column.get())
248 .finish()
249 }
250}
251
252impl std::fmt::Display for LineColumn {
253 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
254 write!(f, "{line}:{column}", line = self.line, column = self.column)
255 }
256}
257
258#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
260#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
261pub struct SourceLocation {
262 pub line: OneIndexed,
264 pub character_offset: OneIndexed,
269}
270
271impl Default for SourceLocation {
272 fn default() -> Self {
273 Self {
274 line: OneIndexed::MIN,
275 character_offset: OneIndexed::MIN,
276 }
277 }
278}
279
280#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
281pub enum SourceRow {
282 Notebook { cell: OneIndexed, line: OneIndexed },
284 SourceFile { line: OneIndexed },
286}
287
288impl Display for SourceRow {
289 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
290 match self {
291 SourceRow::Notebook { cell, line } => write!(f, "cell {cell}, line {line}"),
292 SourceRow::SourceFile { line } => write!(f, "line {line}"),
293 }
294 }
295}