1use std::{cmp::Ordering, ops::Range, sync::Arc};
2
3use tracing::debug;
4
5use crate::project::file::FileContents;
6
7#[derive(Debug)]
8pub struct OriginTable {
9 table: Vec<Origin>,
11}
12
13impl OriginTable {
14 pub fn new(root: Arc<FileContents>) -> Self {
15 Self {
16 table: vec![
17 Origin {
18 origin: 0,
19 span: FileSpan::default(),
20 file: root.clone(),
21 reason: OriginReason::Root,
22 },
23 Origin {
24 origin: 0,
25 span: FileSpan::default(),
26 file: root.clone(),
27 reason: OriginReason::Builtin,
28 },
29 Origin {
30 origin: 0,
31 span: FileSpan::default(),
32 file: root,
33 reason: OriginReason::Cli,
34 },
35 ],
36 }
37 }
38
39 pub fn get(&self, id: OriginId) -> &Origin {
40 &self.table[id as usize]
41 }
42
43 pub fn add(&mut self, origin: Origin) -> OriginId {
44 let id = self.table.len() as u32;
45 self.table.push(origin);
46 id
47 }
48
49 pub fn root(&self) -> Arc<FileContents> {
50 self.get(ROOT_ORIGIN).file.clone()
51 }
52}
53
54pub type OriginId = u32;
56pub const ROOT_ORIGIN: OriginId = 0;
57pub const BUILTIN_ORIGIN: OriginId = 1;
58pub const CLI_ORIGIN: OriginId = 2;
59
60#[derive(Debug)]
61pub struct Origin {
62 pub origin: OriginId,
63
64 pub span: FileSpan,
66
67 pub file: Arc<FileContents>,
71
72 pub reason: OriginReason,
73}
74
75#[derive(Debug, Clone, Copy)]
76pub enum OriginReason {
77 Root,
79 Macro,
81 MacroInterp,
83 Include,
85 Builtin,
87 Cli,
89}
90
91#[derive(Debug, Clone, Copy, Default)]
92pub struct Span {
93 pub origin: OriginId,
94 pub text_span: FileSpan,
95}
96
97impl Span {
98 pub fn cross_origin(l: Pos, r: Pos, ot: &OriginTable) -> Self {
99 if l.origin != r.origin {
102 debug!("TODO: unhandled cross-origin span between: {l:?}, {r:?}");
104 return Self {
105 origin: l.origin,
106 text_span: FileSpan::empty_at(l.text_pos),
107 };
108 }
109
110 let file = &ot.get(l.origin).file;
111
112 Self {
113 origin: l.origin,
114 text_span: FileSpan::from_range(l.text_pos..r.text_pos, file),
115 }
116 }
117
118 pub fn empty_at(pos: Pos) -> Self {
119 Self {
120 origin: pos.origin,
121 text_span: FileSpan::empty_at(pos.text_pos),
122 }
123 }
124
125 pub fn empty_at_start(self) -> Self {
126 Self {
127 origin: self.origin,
128 text_span: self.text_span.empty_at_start(),
129 }
130 }
131
132 pub fn empty_at_end(self, ot: &OriginTable) -> Self {
133 Self {
134 origin: self.origin,
135 text_span: self.text_span.empty_at_end(&ot.get(self.origin).file),
136 }
137 }
138
139 pub fn start(&self) -> Pos {
140 Pos {
141 origin: self.origin,
142 text_pos: self.text_span.start,
143 }
144 }
145
146 pub fn end(&self, ot: &OriginTable) -> Pos {
147 Pos {
148 origin: self.origin,
149 text_pos: self.text_span.end(&ot.get(self.origin).file),
150 }
151 }
152
153 pub fn contains(&self, pos: &Pos, ot: &OriginTable) -> Option<bool> {
154 let start = self.start();
155 let end = self.end(ot);
156
157 Some(start.try_cmp(pos)?.is_le() && pos.try_cmp(&end)?.is_le())
158 }
159
160 pub fn parent_span(&self, ot: &OriginTable) -> Option<Self> {
161 if self.origin == ROOT_ORIGIN {
162 None
163 } else {
164 let origin = ot.get(self.origin);
165 Some(Span {
166 origin: origin.origin,
167 text_span: origin.span,
168 })
169 }
170 }
171
172 pub fn to_lsp(&self, ot: &OriginTable) -> lsp_types::Range {
173 let mut span = *self;
174 while let Some(parent) = span.parent_span(ot) {
175 span = parent;
176 }
177 span.text_span.to_lsp(&ot.get(span.origin).file)
178 }
179
180 pub fn root_span(&self) -> Option<FileSpan> {
181 if self.origin != ROOT_ORIGIN {
182 return None;
183 }
184 Some(self.text_span)
185 }
186}
187
188#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
189pub struct Pos {
190 pub origin: OriginId,
191 pub text_pos: FilePos,
192}
193
194impl Pos {
195 pub const fn zero() -> Self {
196 Pos {
197 origin: ROOT_ORIGIN,
198 text_pos: FilePos {
199 line: 0,
200 col: LspCol(0),
201 },
202 }
203 }
204
205 pub fn from_lsp(lsp: lsp_types::Position) -> Self {
206 Self {
207 origin: ROOT_ORIGIN,
208 text_pos: FilePos::from_lsp(lsp),
209 }
210 }
211
212 pub fn parent_span(&self, ot: &OriginTable) -> Option<Self> {
213 if self.origin == ROOT_ORIGIN {
214 None
215 } else {
216 let origin = ot.get(self.origin);
217 Some(Pos {
218 origin: origin.origin,
219 text_pos: origin.span.start,
220 })
221 }
222 }
223
224 pub fn to_lsp(&self, ot: &OriginTable) -> lsp_types::Position {
225 let mut pos = *self;
226 while let Some(parent) = pos.parent_span(ot) {
227 pos = parent;
228 }
229 pos.text_pos.to_lsp()
230 }
231
232 pub fn at(origin: OriginId, line: u32, col: u32) -> Self {
233 Self {
234 origin,
235 text_pos: FilePos {
236 line,
237 col: LspCol::from_raw(col),
238 },
239 }
240 }
241
242 pub fn try_cmp(&self, other: &Self) -> Option<Ordering> {
243 if self.origin != other.origin {
245 None
246 } else {
247 Some(self.text_pos.cmp(&other.text_pos))
248 }
249 }
250}
251
252#[derive(Debug, Clone, Copy, Default)]
253pub struct FileSpan {
254 pub start: FilePos,
255 pub len: LspCol,
256}
257
258impl FileSpan {
259 pub fn empty_at(start: FilePos) -> Self {
260 Self {
261 start,
262 len: LspCol::from_raw(0),
263 }
264 }
265
266 pub fn from_range(range: Range<FilePos>, file: &FileContents) -> Self {
267 let start = range.start;
268 let len = file.pos_diff(range);
269 Self { start, len }
270 }
271
272 pub fn to_lsp(&self, file: &FileContents) -> lsp_types::Range {
273 let start = self.start;
274 let end = self.end(file);
275 lsp_types::Range {
276 start: start.to_lsp(),
277 end: end.to_lsp(),
278 }
279 }
280
281 pub fn end(&self, file: &FileContents) -> FilePos {
282 file.pos_add(self.start, self.len)
283 }
284
285 pub fn empty_at_start(&self) -> Self {
286 FileSpan {
287 start: self.start,
288 len: LspCol::from_raw(0),
289 }
290 }
291
292 pub fn empty_at_end(&self, file: &FileContents) -> Self {
293 FileSpan {
294 start: self.end(file),
295 len: LspCol::from_raw(0),
296 }
297 }
298
299 pub fn overlaps(&self, other: &Self, file: &FileContents) -> bool {
300 let self_end = self.end(file);
301 let other_end = other.end(file);
302 self.start < other_end && self_end > other.start
303 }
304}
305
306#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
307pub struct FilePos {
308 pub line: u32,
309 pub col: LspCol,
310}
311
312impl FilePos {
313 pub fn to_lsp(&self) -> lsp_types::Position {
314 lsp_types::Position {
315 line: self.line,
316 character: self.col.to_lsp(),
317 }
318 }
319
320 pub fn from_lsp(lsp: lsp_types::Position) -> Self {
321 Self {
322 line: lsp.line,
323 col: LspCol::from_raw(lsp.character),
324 }
325 }
326
327 pub fn advance(&mut self, char: char) {
332 match char {
339 '\r' => unreachable!(),
340 '\n' => {
341 self.line += 1;
342 self.col = LspCol::start();
343 }
344 c => self.col.advance(c),
345 }
346 }
347}
348
349impl PartialOrd for FilePos {
350 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
351 Some(self.cmp(other))
352 }
353}
354
355impl Ord for FilePos {
356 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
357 self.line.cmp(&other.line).then(self.col.cmp(&other.col))
358 }
359}
360
361#[derive(Debug, Clone, Copy, Default)]
362pub struct LineColCounter {
363 pos: FilePos,
364 post_cr: bool,
365}
366
367impl LineColCounter {
368 pub fn start_at(pos: FilePos) -> Self {
369 Self {
370 pos,
371 post_cr: false,
372 }
373 }
374
375 pub fn pos(&self, next: Option<char>) -> FilePos {
376 let mut pos = self.pos;
377 if self.post_cr && next != Some('\n') {
378 pos.advance('\n');
379 }
380 pos
381 }
382
383 pub fn advance(&mut self, c: char) {
384 if self.post_cr && c != '\n' {
385 self.pos.advance('\n');
386 }
387 self.post_cr = false;
388 match c {
389 '\r' => self.post_cr = true,
390 c => self.pos.advance(c),
391 }
392 }
393}
394
395#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
399pub struct LspCol(u32);
400
401impl LspCol {
402 pub fn from_raw(col: u32) -> Self {
403 LspCol(col)
404 }
405
406 pub fn to_raw(self) -> u32 {
407 self.0
408 }
409
410 pub fn start() -> Self {
411 LspCol(0)
412 }
413
414 pub fn advance(&mut self, char: char) {
415 self.0 += char.len_utf16() as u32;
416 }
417
418 pub fn to_lsp(&self) -> u32 {
419 self.0
420 }
421}