1use crate::snapshot::{DocumentSnapshot, FenceSnapshot};
2use merman_analysis::{
3 ByteSpan, EditorSymbolKind, FenceSemanticItem, FenceSemanticRole, FenceTextIndexSource,
4 SourceMap,
5};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub struct SemanticToken {
9 pub line: u32,
10 pub start: u32,
11 pub length: u32,
12 pub kind: SemanticTokenKind,
13 pub modifier: SemanticTokenModifier,
14 pub fact_source: FenceTextIndexSource,
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum SemanticTokenKind {
19 Namespace,
20 Class,
21 Struct,
22 Variable,
23 Property,
24 Event,
25 Function,
26 String,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum SemanticTokenModifier {
31 Entity,
32 Outline,
33 Payload,
34}
35
36#[derive(Debug, Clone, PartialEq, Eq)]
37pub struct SemanticTokenLegend {
38 pub token_types: Vec<SemanticTokenKind>,
39 pub token_modifiers: Vec<SemanticTokenModifier>,
40}
41
42pub fn semantic_token_legend() -> SemanticTokenLegend {
43 SemanticTokenLegend {
44 token_types: vec![
45 SemanticTokenKind::Namespace,
46 SemanticTokenKind::Class,
47 SemanticTokenKind::Struct,
48 SemanticTokenKind::Variable,
49 SemanticTokenKind::Property,
50 SemanticTokenKind::Event,
51 SemanticTokenKind::Function,
52 SemanticTokenKind::String,
53 ],
54 token_modifiers: vec![
55 SemanticTokenModifier::Entity,
56 SemanticTokenModifier::Outline,
57 SemanticTokenModifier::Payload,
58 ],
59 }
60}
61
62pub fn semantic_tokens_for_snapshot(snapshot: &DocumentSnapshot) -> Vec<SemanticToken> {
63 semantic_tokens_for_fences(&snapshot.source_map, &snapshot.fences, None)
64}
65
66pub fn semantic_tokens_for_snapshot_range(
67 snapshot: &DocumentSnapshot,
68 start_line: u32,
69 end_line: u32,
70) -> Vec<SemanticToken> {
71 let Some(line_range) = RequestedLineRange::new(&snapshot.source_map, start_line, end_line)
72 else {
73 return Vec::new();
74 };
75 semantic_tokens_for_fences(&snapshot.source_map, &snapshot.fences, Some(line_range))
76}
77
78fn semantic_tokens_for_fences<'a>(
79 source_map: &SourceMap,
80 fences: impl IntoIterator<Item = &'a FenceSnapshot>,
81 line_range: Option<RequestedLineRange>,
82) -> Vec<SemanticToken> {
83 let mut tokens = Vec::new();
84
85 for fence in fences {
86 if line_range.is_some_and(|range| !fence_overlaps_line_range(source_map, fence, range)) {
87 continue;
88 }
89 for item in fence.text_index.semantic_items() {
90 let Some(span) = absolute_span_for_item(fence, item) else {
91 continue;
92 };
93 if line_range.is_some_and(|range| !range.overlaps_byte_span(span)) {
94 continue;
95 }
96 tokens.extend(tokens_for_item(source_map, fence, item, span, line_range));
97 }
98 }
99
100 tokens.sort_by(|left, right| {
101 (
102 left.line,
103 left.start,
104 left.length,
105 token_type_index(left.kind),
106 token_modifier_index(left.modifier),
107 )
108 .cmp(&(
109 right.line,
110 right.start,
111 right.length,
112 token_type_index(right.kind),
113 token_modifier_index(right.modifier),
114 ))
115 });
116 tokens.dedup();
117 tokens
118}
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121struct RequestedLineRange {
122 start_line: u32,
123 end_line: u32,
124 byte_span: ByteSpan,
125}
126
127impl RequestedLineRange {
128 fn new(source_map: &SourceMap, start_line: u32, end_line: u32) -> Option<Self> {
129 let requested_start_line = start_line.min(end_line);
130 let requested_end_line = start_line.max(end_line);
131 let line_count = source_map.line_starts().len();
132 if line_count == 0 || requested_start_line as usize >= line_count {
133 return None;
134 }
135
136 let end_line = requested_end_line.min((line_count - 1) as u32);
137 let (byte_start, _) = source_map.line_bounds(requested_start_line as usize)?;
138 let (_, byte_end) = source_map.line_bounds(end_line as usize)?;
139 Some(Self {
140 start_line: requested_start_line,
141 end_line,
142 byte_span: ByteSpan {
143 start: byte_start,
144 end: byte_end,
145 },
146 })
147 }
148
149 fn contains_line(self, line: u32) -> bool {
150 line >= self.start_line && line <= self.end_line
151 }
152
153 fn overlaps_byte_span(self, span: ByteSpan) -> bool {
154 span.start < self.byte_span.end && span.end > self.byte_span.start
155 }
156}
157
158fn fence_overlaps_line_range(
159 source_map: &SourceMap,
160 fence: &FenceSnapshot,
161 line_range: RequestedLineRange,
162) -> bool {
163 let Ok(fence_start) = source_map.utf16_position(fence.body_start) else {
164 return true;
165 };
166 let Ok(fence_end) = source_map.utf16_position(fence.body_end) else {
167 return true;
168 };
169
170 (fence_start.line as u32) <= line_range.end_line
171 && (fence_end.line as u32) >= line_range.start_line
172}
173
174fn absolute_span_for_item(fence: &FenceSnapshot, item: &FenceSemanticItem) -> Option<ByteSpan> {
175 let span = ByteSpan {
176 start: fence.body_start + item.selection.start,
177 end: fence.body_start + item.selection.end,
178 };
179 if span.start >= span.end {
180 None
181 } else {
182 Some(span)
183 }
184}
185
186fn tokens_for_item(
187 source_map: &SourceMap,
188 fence: &FenceSnapshot,
189 item: &FenceSemanticItem,
190 span: ByteSpan,
191 line_range: Option<RequestedLineRange>,
192) -> Vec<SemanticToken> {
193 token_pieces_for_span(source_map, span)
194 .into_iter()
195 .filter(|piece| line_range.is_none_or(|range| range.contains_line(piece.line)))
196 .map(|piece| SemanticToken {
197 line: piece.line,
198 start: piece.start,
199 length: piece.length,
200 kind: token_kind_for_symbol(item.kind),
201 modifier: token_modifier_for_role(item.role),
202 fact_source: fence.text_index.source(),
203 })
204 .collect()
205}
206
207#[derive(Debug, Clone, Copy, PartialEq, Eq)]
208struct TokenPiece {
209 line: u32,
210 start: u32,
211 length: u32,
212}
213
214fn token_pieces_for_span(source_map: &SourceMap, span: ByteSpan) -> Vec<TokenPiece> {
215 let Ok(start) = source_map.utf16_position(span.start) else {
216 return Vec::new();
217 };
218 let Ok(end) = source_map.utf16_position(span.end) else {
219 return Vec::new();
220 };
221
222 let mut pieces = Vec::new();
223 for line in start.line..=end.line {
224 let Some((line_start, line_end)) = source_map.line_bounds(line) else {
225 continue;
226 };
227 let segment_start = span.start.max(line_start);
228 let segment_end = span.end.min(line_end);
229 if segment_start >= segment_end {
230 continue;
231 }
232
233 let Ok(segment_start_pos) = source_map.utf16_position(segment_start) else {
234 continue;
235 };
236 let Ok(segment_end_pos) = source_map.utf16_position(segment_end) else {
237 continue;
238 };
239 if segment_start_pos.line != segment_end_pos.line
240 || segment_end_pos.character <= segment_start_pos.character
241 {
242 continue;
243 }
244
245 pieces.push(TokenPiece {
246 line: segment_start_pos.line as u32,
247 start: segment_start_pos.character as u32,
248 length: (segment_end_pos.character - segment_start_pos.character) as u32,
249 });
250 }
251
252 pieces
253}
254
255fn token_kind_for_symbol(kind: EditorSymbolKind) -> SemanticTokenKind {
256 match kind {
257 EditorSymbolKind::Class => SemanticTokenKind::Class,
258 EditorSymbolKind::Event => SemanticTokenKind::Event,
259 EditorSymbolKind::Function => SemanticTokenKind::Function,
260 EditorSymbolKind::Module | EditorSymbolKind::Namespace | EditorSymbolKind::Package => {
261 SemanticTokenKind::Namespace
262 }
263 EditorSymbolKind::Object | EditorSymbolKind::Variable => SemanticTokenKind::Variable,
264 EditorSymbolKind::Property => SemanticTokenKind::Property,
265 EditorSymbolKind::String => SemanticTokenKind::String,
266 EditorSymbolKind::Struct => SemanticTokenKind::Struct,
267 }
268}
269
270fn token_modifier_for_role(role: FenceSemanticRole) -> SemanticTokenModifier {
271 match role {
272 FenceSemanticRole::Entity => SemanticTokenModifier::Entity,
273 FenceSemanticRole::Outline => SemanticTokenModifier::Outline,
274 FenceSemanticRole::Payload => SemanticTokenModifier::Payload,
275 }
276}
277
278pub fn token_type_index(kind: SemanticTokenKind) -> u32 {
279 match kind {
280 SemanticTokenKind::Namespace => 0,
281 SemanticTokenKind::Class => 1,
282 SemanticTokenKind::Struct => 2,
283 SemanticTokenKind::Variable => 3,
284 SemanticTokenKind::Property => 4,
285 SemanticTokenKind::Event => 5,
286 SemanticTokenKind::Function => 6,
287 SemanticTokenKind::String => 7,
288 }
289}
290
291pub fn token_modifier_index(modifier: SemanticTokenModifier) -> u32 {
292 match modifier {
293 SemanticTokenModifier::Entity => 0,
294 SemanticTokenModifier::Outline => 1,
295 SemanticTokenModifier::Payload => 2,
296 }
297}