1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3use tower_lsp::lsp_types::{Position as LspPosition, Range as LspRange};
4
5use crate::modules::ModuleId;
6
7#[derive(Debug, Default, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS, JsonSchema, Hash, Eq)]
15#[ts(export, type = "[number, number, number]")]
16pub struct SourceRange([usize; 3]);
17
18impl From<[usize; 3]> for SourceRange {
19 fn from(value: [usize; 3]) -> Self {
20 Self(value)
21 }
22}
23
24impl Ord for SourceRange {
25 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
26 let module_id_cmp = self.module_id().cmp(&other.module_id());
28 if module_id_cmp != std::cmp::Ordering::Equal {
29 return module_id_cmp;
30 }
31 let start_cmp = self.start().cmp(&other.start());
32 if start_cmp != std::cmp::Ordering::Equal {
33 return start_cmp;
34 }
35 self.end().cmp(&other.end())
36 }
37}
38
39impl PartialOrd for SourceRange {
40 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
41 Some(self.cmp(other))
42 }
43}
44
45impl From<&SourceRange> for miette::SourceSpan {
46 fn from(source_range: &SourceRange) -> Self {
47 let length = source_range.end() - source_range.start();
48 let start = miette::SourceOffset::from(source_range.start());
49 Self::new(start, length)
50 }
51}
52
53impl From<SourceRange> for miette::SourceSpan {
54 fn from(source_range: SourceRange) -> Self {
55 Self::from(&source_range)
56 }
57}
58
59impl SourceRange {
60 pub fn new(start: usize, end: usize, module_id: ModuleId) -> Self {
62 Self([start, end, module_id.as_usize()])
63 }
64
65 pub fn synthetic() -> Self {
67 Self::default()
68 }
69
70 pub fn is_synthetic(&self) -> bool {
73 self.start() == 0 && self.end() == 0
74 }
75
76 pub fn start(&self) -> usize {
78 self.0[0]
79 }
80
81 pub fn start_as_range(&self) -> Self {
84 Self([self.0[0], self.0[0], self.0[2]])
85 }
86
87 pub fn end(&self) -> usize {
89 self.0[1]
90 }
91
92 pub fn module_id(&self) -> ModuleId {
94 ModuleId::from_usize(self.0[2])
95 }
96
97 pub fn is_top_level_module(&self) -> bool {
99 self.module_id().is_top_level()
100 }
101
102 pub fn contains(&self, pos: usize) -> bool {
104 pos >= self.start() && pos <= self.end()
105 }
106
107 pub(crate) fn contains_range(&self, other: &Self) -> bool {
109 self.module_id() == other.module_id() && self.start() <= other.start() && self.end() >= other.end()
110 }
111
112 pub fn start_to_lsp_position(&self, code: &str) -> LspPosition {
113 let mut line = code.get(..self.start()).unwrap_or_default().lines().count();
116 if line > 0 {
117 line = line.saturating_sub(1);
118 }
119 let column = code[..self.start()].lines().last().map(|l| l.len()).unwrap_or_default();
120
121 LspPosition {
122 line: line as u32,
123 character: column as u32,
124 }
125 }
126
127 pub fn end_to_lsp_position(&self, code: &str) -> LspPosition {
128 let lines = code.get(..self.end()).unwrap_or_default().lines();
129 if lines.clone().count() == 0 {
130 return LspPosition { line: 0, character: 0 };
131 }
132
133 let line = lines.clone().count() - 1;
136 let column = lines.last().map(|l| l.len()).unwrap_or_default();
137
138 LspPosition {
139 line: line as u32,
140 character: column as u32,
141 }
142 }
143
144 pub fn to_lsp_range(&self, code: &str) -> LspRange {
145 let start = self.start_to_lsp_position(code);
146 let end = self.end_to_lsp_position(code);
147 LspRange { start, end }
148 }
149}