Skip to main content

merman_editor_core/
semantic_tokens.rs

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}