Skip to main content

swls_core/util/
token.rs

1use std::ops::Range;
2
3use bevy_ecs::prelude::*;
4use derive_more::{AsMut, AsRef, Deref, DerefMut};
5use tracing::{debug, instrument};
6
7use crate::{components::*, prelude::*};
8
9/// [`Component`] used to indicate the currently targeted token during a request.
10#[derive(Component, Debug)]
11pub struct TokenComponent {
12    pub range: crate::lsp_types::Range,
13    pub text: String,
14    pub source_span: Range<usize>,
15    /// `true` when the cursor is on an error/invalid token.
16    pub is_error: bool,
17}
18
19/// [`Component`] that contains CST leaf tokens from the rowan syntax tree.
20#[derive(Component, AsRef, Deref, AsMut, DerefMut, Debug)]
21pub struct CstTokens(pub Vec<Spanned<rowan::SyntaxKind>>);
22
23#[instrument(skip(query, commands))]
24pub fn get_current_cst_token(
25    query: Query<(Entity, &CstTokens, &PositionComponent, &RopeC, &Source)>,
26    mut commands: Commands,
27) {
28    for (entity, cst_tokens, position, rope, source) in &query {
29        commands.entity(entity).remove::<TokenComponent>();
30        let Some(offset) = position_to_offset(position.0, &rope.0) else {
31            debug!("Couldn't transform to an offset ({:?})", position.0);
32            continue;
33        };
34
35        let token = cst_tokens
36            .0
37            .iter()
38            .filter(|x| x.span().contains(&offset))
39            .min_by_key(|x| x.span().end - x.span().start);
40
41        let Some(token) = token else {
42            let closest = cst_tokens.0.iter().min_by_key(|x| {
43                let d_start = offset.abs_diff(x.span().start);
44                let d_end = offset.abs_diff(x.span().end);
45                d_start.min(d_end)
46            });
47            let Some(token) = closest else {
48                debug!("No CST tokens found");
49                continue;
50            };
51            let span = token.span().clone();
52            if span.start >= source.0.len() || span.end > source.0.len() {
53                continue;
54            }
55            // Cursor is strictly past the token's end: it is in whitespace between tokens.
56            // Use an empty-text placeholder at the cursor position so completion filters
57            // (e.g. `to_beat.starts_with(&token.text)`) pass for all candidates.
58            if offset > span.end {
59                let empty_span = offset..offset;
60                let Some(range) = range_to_range(&empty_span, &rope.0) else {
61                    debug!("Failed to transform offset to range");
62                    continue;
63                };
64                debug!("Cursor in whitespace after token, inserting empty TokenComponent");
65                commands.entity(entity).insert(TokenComponent {
66                    text: String::new(),
67                    range,
68                    source_span: empty_span,
69                    is_error: false,
70                });
71                continue;
72            }
73            let text = source.0[span.clone()].to_string();
74            let Some(range) = range_to_range(&span, &rope.0) else {
75                debug!("Failed to transform span to range");
76                continue;
77            };
78            debug!("Closest CST token {:?} '{}'", token.value(), text);
79            commands.entity(entity).insert(TokenComponent {
80                text,
81                range,
82                source_span: span,
83                is_error: false,
84            });
85            continue;
86        };
87
88        let span = token.span().clone();
89        if span.start >= source.0.len() || span.end > source.0.len() {
90            continue;
91        }
92        let text = source.0[span.clone()].to_string();
93        let Some(range) = range_to_range(&span, &rope.0) else {
94            debug!("Failed to transform span to range");
95            continue;
96        };
97        debug!("CST token at cursor {:?} '{}'", token.value(), text);
98        commands.entity(entity).insert(TokenComponent {
99            text,
100            range,
101            source_span: span,
102            is_error: false,
103        });
104    }
105}
106
107pub mod semantic_token {
108    use crate::lsp_types::SemanticTokenType as STT;
109    pub const BOOLEAN: STT = STT::new("boolean");
110    pub const LANG_TAG: STT = STT::new("langTag");
111}