jsona_lsp/
query.rs

1//! Cursor queries of a JSONA document.
2
3use jsona::{
4    dom::{node::DomNode, Keys, Node},
5    rowan::{Direction, TextSize, TokenAtOffset, WalkEvent},
6    syntax::{SyntaxKind, SyntaxNode, SyntaxToken},
7};
8
9#[derive(Debug)]
10pub struct Query {
11    /// The offset the query was made for.
12    pub offset: TextSize,
13    /// Before the cursor.
14    pub before: Option<PositionInfo>,
15    /// After the cursor.
16    pub after: Option<PositionInfo>,
17    /// Scope kind
18    pub scope: ScopeKind,
19    /// Query node contains offset
20    pub node_at_offset: TextSize,
21    /// Current property/annotationKey key
22    pub key: Option<SyntaxToken>,
23    /// Current value
24    pub value: Option<SyntaxNode>,
25    /// Whether add value for property/annotationKey completion
26    pub add_value: bool,
27}
28
29impl Query {
30    pub fn at(root: &Node, offset: TextSize, is_completion: bool) -> Self {
31        let syntax = root.node_syntax().cloned().unwrap().into_node().unwrap();
32        let before = offset
33            .checked_sub(TextSize::from(1))
34            .and_then(|offset| Self::position_info_at(&syntax, offset));
35        let after = if offset >= syntax.text_range().end() {
36            None
37        } else {
38            Self::position_info_at(&syntax, offset)
39        };
40
41        let mut kind = ScopeKind::Unknown;
42        let mut node_at_offset = offset;
43        let mut key = None;
44        let mut value = None;
45        let mut add_value = true;
46        if let Some(token) = before
47            .as_ref()
48            .and_then(|t| Query::prev_none_ws_comment(t.syntax.clone()))
49        {
50            let mut fallback = || {
51                if let Some(node) = token.parent_ancestors().find(|v| {
52                    matches!(
53                        v.kind(),
54                        SyntaxKind::KEY
55                            | SyntaxKind::SCALAR
56                            | SyntaxKind::OBJECT
57                            | SyntaxKind::ARRAY
58                    )
59                }) {
60                    node_at_offset = node.text_range().start();
61                    match &node.kind() {
62                        SyntaxKind::KEY => {
63                            key = node
64                                .children_with_tokens()
65                                .find(|v| v.kind().is_key())
66                                .and_then(|v| v.as_token().cloned());
67                            add_value = !node
68                                .siblings_with_tokens(Direction::Next)
69                                .any(|v| v.kind() == SyntaxKind::COLON);
70                            if is_completion {
71                                if let Some(offset) = node
72                                    .parent()
73                                    .and_then(|v| v.parent())
74                                    .and_then(|v| v.text_range().start().checked_add(1.into()))
75                                {
76                                    node_at_offset = offset;
77                                }
78                            }
79                            kind = ScopeKind::PropertyKey;
80                        }
81                        SyntaxKind::SCALAR => {
82                            kind = ScopeKind::Value;
83                            value = Some(node);
84                        }
85                        SyntaxKind::OBJECT => kind = ScopeKind::Object,
86                        SyntaxKind::ARRAY => kind = ScopeKind::Array,
87                        _ => {}
88                    };
89                }
90            };
91            match token.kind() {
92                SyntaxKind::ANNOTATION_KEY => {
93                    let exist_value = token
94                        .siblings_with_tokens(Direction::Next)
95                        .any(|v| v.kind() == SyntaxKind::ANNOTATION_VALUE);
96                    if !exist_value && !token.text_range().contains_inclusive(offset) {
97                        // out a tag annotation
98                        fallback()
99                    } else {
100                        add_value = !exist_value;
101                        kind = ScopeKind::AnnotationKey;
102                        key = Some(token);
103                    }
104                }
105                SyntaxKind::COLON => {
106                    node_at_offset = token.text_range().start();
107                    kind = ScopeKind::Value;
108                    value = token.next_sibling_or_token().and_then(|v| {
109                        if v.kind() == SyntaxKind::VALUE {
110                            v.as_node()
111                                .unwrap()
112                                .children()
113                                .find(|v| v.kind() == SyntaxKind::SCALAR)
114                        } else {
115                            None
116                        }
117                    });
118                }
119                SyntaxKind::PARENTHESES_START => {
120                    kind = ScopeKind::Value;
121                    value = token.next_sibling_or_token().and_then(|v| {
122                        if v.kind() == SyntaxKind::ANNOTATION_VALUE {
123                            v.as_node()
124                                .unwrap()
125                                .children()
126                                .find(|v| v.kind() == SyntaxKind::VALUE)
127                                .and_then(|v| v.children().find(|v| v.kind() == SyntaxKind::SCALAR))
128                        } else {
129                            None
130                        }
131                    });
132                }
133                SyntaxKind::BRACE_START => {
134                    kind = ScopeKind::Object;
135                }
136                SyntaxKind::BRACKET_START => {
137                    kind = ScopeKind::Array;
138                }
139                SyntaxKind::BRACE_END | SyntaxKind::BRACKET_END => {}
140                _ => fallback(),
141            };
142        }
143
144        Query {
145            offset,
146            before,
147            after,
148            scope: kind,
149            node_at_offset,
150            add_value,
151            key,
152            value,
153        }
154    }
155
156    pub fn node_at(root: &Node, offset: TextSize) -> Option<(Keys, Node)> {
157        if !root
158            .node_text_range()
159            .map(|v| v.contains(offset))
160            .unwrap_or_default()
161        {
162            return None;
163        }
164        node_at_impl(root, offset, Keys::default())
165    }
166
167    pub fn index_at(&self) -> Option<usize> {
168        self.before
169            .as_ref()
170            .and_then(|v| {
171                v.syntax
172                    .parent_ancestors()
173                    .find(|v| v.kind() == SyntaxKind::ARRAY)
174            })
175            .map(|v| {
176                let mut index = 0;
177                for child in v.children() {
178                    if child.kind() == SyntaxKind::VALUE {
179                        index += 1;
180                        if child.text_range().contains(self.offset) {
181                            break;
182                        }
183                    }
184                }
185                index
186            })
187    }
188
189    pub fn space_and_comma(&self) -> (&'static str, &'static str) {
190        let mut add_comma = true;
191        let mut add_space = false;
192        if self.scope != ScopeKind::AnnotationKey {
193            if let Some(token) = self.before.as_ref().map(|v| &v.syntax) {
194                if let Some(parent) = token.parent_ancestors().find(|v| {
195                    matches!(
196                        v.kind(),
197                        SyntaxKind::ANNOTATION_VALUE | SyntaxKind::OBJECT | SyntaxKind::ARRAY
198                    )
199                }) {
200                    let mut found = false;
201                    let mut exist_new_line = false;
202                    let mut prev_token = None;
203                    let mut next_token = None;
204                    for event in parent.preorder_with_tokens() {
205                        if let WalkEvent::Enter(ele) = event {
206                            if let Some(t) = ele.as_token() {
207                                if t.text().contains(|p| p == '\r' || p == '\n') {
208                                    exist_new_line = true;
209                                }
210                                if found && !t.kind().is_ws_or_comment() {
211                                    next_token = Some(t.clone());
212                                    break;
213                                }
214                                if !found {
215                                    if t.text_range().contains_inclusive(self.offset) {
216                                        found = true;
217                                    } else {
218                                        prev_token = Some(t.clone())
219                                    }
220                                }
221                            }
222                        }
223                    }
224
225                    if let Some(t) = next_token {
226                        if matches!(
227                            t.kind(),
228                            SyntaxKind::COMMA
229                                | SyntaxKind::BRACE_END
230                                | SyntaxKind::BRACKET_END
231                                | SyntaxKind::PARENTHESES_END
232                        ) {
233                            add_comma = false;
234                        }
235                    }
236                    if exist_new_line {
237                        match self.scope {
238                            ScopeKind::Array => {}
239                            ScopeKind::Value => {
240                                if !(self
241                                    .before
242                                    .as_ref()
243                                    .map(|v| v.syntax.kind().is_ws_or_comment())
244                                    .unwrap_or_default()
245                                    || prev_token
246                                        .map(|v| v.kind().is_ws_or_comment())
247                                        .unwrap_or_default())
248                                {
249                                    add_space = true
250                                }
251                            }
252                            _ => {
253                                add_space = true;
254                            }
255                        }
256                    }
257                }
258            }
259        }
260        let space = if add_space { " " } else { "" };
261        let comma = if add_comma { "," } else { "" };
262        (space, comma)
263    }
264
265    fn position_info_at(syntax: &SyntaxNode, offset: TextSize) -> Option<PositionInfo> {
266        let syntax = match syntax.token_at_offset(offset) {
267            TokenAtOffset::None => return None,
268            TokenAtOffset::Single(s) => s,
269            TokenAtOffset::Between(_, right) => right,
270        };
271
272        Some(PositionInfo { syntax })
273    }
274
275    fn prev_none_ws_comment(token: SyntaxToken) -> Option<SyntaxToken> {
276        if token.kind().is_ws_or_comment() {
277            token.prev_token().and_then(Query::prev_none_ws_comment)
278        } else {
279            Some(token)
280        }
281    }
282}
283
284#[derive(Debug, Clone, Copy, PartialEq, Eq)]
285pub enum ScopeKind {
286    Unknown,
287    Object,
288    Array,
289    AnnotationKey,
290    PropertyKey,
291    Value,
292}
293
294#[derive(Debug, Clone)]
295pub struct PositionInfo {
296    /// The narrowest syntax element that contains the position.
297    pub syntax: SyntaxToken,
298}
299
300fn node_at_impl(node: &Node, offset: TextSize, keys: Keys) -> Option<(Keys, Node)> {
301    if let Some(annotations) = node.annotations() {
302        let map = annotations.value().read();
303        for (key, value) in map.iter() {
304            if map
305                .syntax(key)
306                .map(|v| v.text_range().contains(offset))
307                .unwrap_or_default()
308            {
309                return node_at_impl(value, offset, keys.join(key.clone()));
310            }
311        }
312    }
313    match node {
314        Node::Array(arr) => {
315            for (index, value) in arr.value().read().iter().enumerate() {
316                if value
317                    .node_syntax()
318                    .map(|v| v.text_range().contains(offset))
319                    .unwrap_or_default()
320                {
321                    return node_at_impl(value, offset, keys.join(index));
322                }
323            }
324        }
325        Node::Object(obj) => {
326            let map = obj.value().read();
327            for (key, value) in map.iter() {
328                if map
329                    .syntax(key)
330                    .map(|v| v.text_range().contains(offset))
331                    .unwrap_or_default()
332                {
333                    if value.syntax().is_none()
334                        && key
335                            .syntax()
336                            .map(|v| v.text_range().contains(offset))
337                            .unwrap_or_default()
338                    {
339                        return Some((keys, node.clone()));
340                    }
341                    return node_at_impl(value, offset, keys.join(key.clone()));
342                }
343            }
344        }
345        _ => {}
346    }
347    Some((keys, node.clone()))
348}