Skip to main content

nifty_lsp/
context.rs

1//! What kind of code the cursor is in, worked out from the tokens before it. Code being
2//! typed rarely parses, so this reads tokens rather than the syntax tree: it only needs to
3//! know which blocks, calls and brackets are open and what came last.
4
5use nifty_check::elements::{self, TrailingBlock};
6use nifty_syntax::{Token, TokenKind};
7
8use crate::document::Document;
9
10/// What a `{ … }` holds, which decides the statements allowed in it.
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub(crate) enum BlockKind {
13    TopLevel,
14    /// A function body, where `return` works.
15    Function,
16    /// A window or view body, or a layout element's children.
17    Ui,
18    /// A button's action.
19    Action,
20    /// A menu's items.
21    Menu,
22    /// A struct's fields.
23    Struct,
24    /// A block nothing more is known about, such as one after a call that takes none.
25    Other,
26}
27
28/// The call whose argument list the cursor is directly in.
29#[derive(Clone, Debug, PartialEq, Eq)]
30pub(crate) struct Call {
31    /// The name called, when it is a plain name rather than a method or expression.
32    pub callee: Option<String>,
33    /// Positional arguments before the current one.
34    pub positional: usize,
35    /// Labels already given, including the current argument's.
36    pub labels: Vec<String>,
37    /// The current argument's label, once its `:` is typed.
38    pub label: Option<String>,
39}
40
41/// Where the cursor is, and so what may be typed there.
42#[derive(Clone, Debug, PartialEq, Eq)]
43pub(crate) enum Slot {
44    /// The start of a statement in a block.
45    Statement(BlockKind),
46    /// Just after the `}` of an `if`, where `else` may follow.
47    AfterIf,
48    /// Anywhere an expression may start.
49    Expression,
50    /// The start of an argument, which may be a label or a value.
51    ArgumentStart(Call),
52    /// After `label:`.
53    ArgumentValue(Call),
54    /// After `label: .`.
55    ImplicitMember(Call),
56    /// After a `:` or `->` that a type follows.
57    Type,
58    /// Between `for item` (or `for index, item`) and `in`.
59    ForIn,
60    /// Where nothing can be completed: naming something new, after a value, after a `.`
61    /// on a value, or in a comment or string.
62    Nothing,
63}
64
65/// The cursor's context, and the call being written around it, if any.
66#[derive(Debug)]
67pub(crate) struct Context {
68    pub slot: Slot,
69    /// The innermost call whose argument list contains the cursor, even inside nested
70    /// brackets, for signature help.
71    pub call: Option<Call>,
72}
73
74pub(crate) fn at(document: &Document, word_start: usize) -> Context {
75    let text = document.source();
76    let tokens = nifty_syntax::tokens(text, document.file);
77    let mut scanner = Scanner::new();
78    let before = tokens
79        .iter()
80        .take_while(|token| token.kind != TokenKind::Eof && token.span.end <= word_start);
81    for token in before {
82        scanner.token(text, *token);
83    }
84    let inside_token = tokens
85        .iter()
86        .any(|token| token.span.start < word_start && word_start < token.span.end);
87    let last_end = scanner.last.map_or(0, |last| last.span.end);
88    let in_comment = text[last_end..word_start].contains("//");
89    let slot = if inside_token || in_comment || scanner.in_string() {
90        Slot::Nothing
91    } else {
92        scanner.slot()
93    };
94    Context {
95        slot,
96        call: scanner.innermost_call(),
97    }
98}
99
100/// How many recent tokens the scanner keeps: enough for `for index, item`.
101const LOOK_BACK: usize = 4;
102
103#[derive(Debug)]
104enum Frame {
105    Block {
106        kind: BlockKind,
107        from_if: bool,
108    },
109    Call(Call),
110    /// A function or view's parameter list.
111    Params,
112    /// The brackets of a list type, such as `[Todo]`.
113    ListType,
114    Group,
115    String,
116    Interpolation,
117}
118
119struct Scanner {
120    frames: Vec<Frame>,
121    /// Tokens other than line breaks, most recent last, up to [`LOOK_BACK`].
122    recent: Vec<Token>,
123    last: Option<Token>,
124    last_name: Option<String>,
125    /// The name scanned before `last_name`, which is an element's when the two are a
126    /// top-level element and its name, as in `window settings(`.
127    name_before_last: Option<String>,
128    newline_after_last: bool,
129    /// The block the next `{` at this depth opens, set by `fn`, `if` and the like.
130    pending: Option<(BlockKind, usize, bool)>,
131    /// What a `{` right after the call just closed would open.
132    closed_call_block: Option<BlockKind>,
133    closed_if: bool,
134}
135
136impl Scanner {
137    fn new() -> Self {
138        Self {
139            frames: vec![Frame::Block {
140                kind: BlockKind::TopLevel,
141                from_if: false,
142            }],
143            recent: Vec::new(),
144            last: None,
145            last_name: None,
146            name_before_last: None,
147            newline_after_last: false,
148            pending: None,
149            closed_call_block: None,
150            closed_if: false,
151        }
152    }
153
154    fn token(&mut self, text: &str, token: Token) {
155        if token.kind == TokenKind::Newline {
156            self.newline_after_last = true;
157            return;
158        }
159        let closed_call_block = self.closed_call_block.take();
160        self.closed_if = false;
161        match token.kind {
162            TokenKind::Ident => {
163                let name = text[token.span.range()].to_owned();
164                self.name_before_last = self.last_name.replace(name);
165            }
166            TokenKind::Fn => self.expect_block(BlockKind::Function, false),
167            TokenKind::View => self.expect_block(BlockKind::Ui, false),
168            TokenKind::Struct => self.expect_block(BlockKind::Struct, false),
169            TokenKind::If | TokenKind::Else | TokenKind::For => {
170                let kind = self.enclosing_block();
171                let from_if = token.kind != TokenKind::For;
172                self.expect_block(kind, from_if);
173            }
174            TokenKind::LBrace => self.open_block(closed_call_block),
175            TokenKind::RBrace => {
176                if let Some(Frame::Block { from_if, .. }) = self.pop() {
177                    self.closed_if = from_if;
178                }
179            }
180            TokenKind::LParen => self.open_paren(),
181            TokenKind::RParen => {
182                if let Some(Frame::Call(call)) = self.pop() {
183                    self.closed_call_block = Some(trailing_block(call.callee.as_deref()));
184                }
185            }
186            TokenKind::LBracket => {
187                let frame = if self.slot() == Slot::Type {
188                    Frame::ListType
189                } else {
190                    Frame::Group
191                };
192                self.frames.push(frame);
193            }
194            TokenKind::RBracket | TokenKind::InterpolationEnd | TokenKind::StringEnd => {
195                self.pop();
196            }
197            TokenKind::StringStart => self.frames.push(Frame::String),
198            TokenKind::InterpolationStart => self.frames.push(Frame::Interpolation),
199            TokenKind::Comma => {
200                if let Some(Frame::Call(call)) = self.frames.last_mut()
201                    && call.label.take().is_none()
202                {
203                    call.positional += 1;
204                }
205            }
206            TokenKind::Colon => self.label_argument(),
207            _ => {}
208        }
209        self.recent.push(token);
210        if self.recent.len() > LOOK_BACK {
211            self.recent.remove(0);
212        }
213        self.last = Some(token);
214        self.newline_after_last = false;
215    }
216
217    /// Closes the innermost frame. The top level is never closed, so a stray closing
218    /// bracket leaves the cursor at the top level.
219    fn pop(&mut self) -> Option<Frame> {
220        if self.frames.len() > 1 {
221            self.frames.pop()
222        } else {
223            None
224        }
225    }
226
227    fn expect_block(&mut self, kind: BlockKind, from_if: bool) {
228        self.pending = Some((kind, self.frames.len(), from_if));
229    }
230
231    fn open_block(&mut self, closed_call_block: Option<BlockKind>) {
232        let depth = self.frames.len();
233        let (kind, from_if) = match self.pending.take() {
234            Some((kind, at, from_if)) if at == depth => (kind, from_if),
235            pending => {
236                self.pending = pending;
237                (closed_call_block.unwrap_or(BlockKind::Other), false)
238            }
239        };
240        self.frames.push(Frame::Block { kind, from_if });
241    }
242
243    fn open_paren(&mut self) {
244        let frame = match self.kinds().as_slice() {
245            [.., TokenKind::Fn | TokenKind::View, TokenKind::Ident] => Frame::Params,
246            [.., TokenKind::Ident, TokenKind::Ident]
247                if self.enclosing_block() == BlockKind::TopLevel =>
248            {
249                Frame::Call(Call::new(self.name_before_last.clone()))
250            }
251            [.., TokenKind::Dot, TokenKind::Ident]
252            | [.., TokenKind::RParen | TokenKind::RBracket] => Frame::Call(Call::new(None)),
253            [.., TokenKind::Ident] => Frame::Call(Call::new(self.last_name())),
254            _ => Frame::Group,
255        };
256        self.frames.push(frame);
257    }
258
259    fn label_argument(&mut self) {
260        let label = match self.kinds().as_slice() {
261            [.., TokenKind::LParen | TokenKind::Comma, TokenKind::Ident] => self.last_name(),
262            _ => None,
263        };
264        if let (Some(label), Some(Frame::Call(call))) = (label, self.frames.last_mut()) {
265            call.labels.push(label.clone());
266            call.label = Some(label);
267        }
268    }
269
270    fn enclosing_block(&self) -> BlockKind {
271        match self.frames.last() {
272            Some(Frame::Block { kind, .. }) => *kind,
273            _ => BlockKind::Other,
274        }
275    }
276
277    fn kinds(&self) -> Vec<TokenKind> {
278        self.recent.iter().map(|token| token.kind).collect()
279    }
280
281    /// The name just scanned; callers check the last token is a name.
282    fn last_name(&self) -> Option<String> {
283        self.last_name.clone()
284    }
285
286    fn in_string(&self) -> bool {
287        matches!(self.frames.last(), Some(Frame::String))
288    }
289
290    fn innermost_call(&self) -> Option<Call> {
291        self.frames.iter().rev().find_map(|frame| match frame {
292            Frame::Call(call) => Some(call.clone()),
293            _ => None,
294        })
295    }
296
297    fn slot(&self) -> Slot {
298        let kinds = self.kinds();
299        match self.frames.last() {
300            Some(Frame::Block { kind, .. }) => self.block_slot(*kind, &kinds),
301            Some(Frame::Call(call)) => call_slot(call, &kinds),
302            Some(Frame::Params) => match kinds.as_slice() {
303                [.., TokenKind::Colon] => Slot::Type,
304                _ => Slot::Nothing,
305            },
306            Some(Frame::ListType) => match kinds.as_slice() {
307                [.., TokenKind::LBracket] => Slot::Type,
308                _ => Slot::Nothing,
309            },
310            Some(Frame::Group | Frame::Interpolation) => expression_slot(&kinds),
311            Some(Frame::String) | None => Slot::Nothing,
312        }
313    }
314
315    fn block_slot(&self, kind: BlockKind, kinds: &[TokenKind]) -> Slot {
316        if kind == BlockKind::Struct {
317            return match kinds {
318                [.., TokenKind::Colon] => Slot::Type,
319                _ => Slot::Nothing,
320            };
321        }
322        let at_line_start = self.newline_after_last;
323        match kinds {
324            [] | [.., TokenKind::LBrace] => Slot::Statement(kind),
325            _ if at_line_start => Slot::Statement(kind),
326            [.., TokenKind::RBrace] if self.closed_if => Slot::AfterIf,
327            [.., TokenKind::Colon | TokenKind::Arrow] => Slot::Type,
328            [.., TokenKind::For, TokenKind::Ident]
329            | [
330                ..,
331                TokenKind::For,
332                TokenKind::Ident,
333                TokenKind::Comma,
334                TokenKind::Ident,
335            ] => Slot::ForIn,
336            [.., TokenKind::For, TokenKind::Ident, TokenKind::Comma] => Slot::Nothing,
337            _ => expression_slot(kinds),
338        }
339    }
340}
341
342fn call_slot(call: &Call, kinds: &[TokenKind]) -> Slot {
343    match kinds {
344        [.., TokenKind::LParen | TokenKind::Comma] => Slot::ArgumentStart(call.clone()),
345        [.., TokenKind::Colon] if call.label.is_some() => Slot::ArgumentValue(call.clone()),
346        [.., TokenKind::Colon, TokenKind::Dot] if call.label.is_some() => {
347            Slot::ImplicitMember(call.clone())
348        }
349        _ => expression_slot(kinds),
350    }
351}
352
353/// After an operator or opening bracket an expression may start; after a value, a name
354/// being declared or a member `.`, nothing from the completion list fits.
355fn expression_slot(kinds: &[TokenKind]) -> Slot {
356    match kinds.last() {
357        Some(&kind) if ends_value(kind) || declares_name(kind) || kind == TokenKind::Dot => {
358            Slot::Nothing
359        }
360        None | Some(_) => Slot::Expression,
361    }
362}
363
364fn ends_value(kind: TokenKind) -> bool {
365    matches!(
366        kind,
367        TokenKind::Ident
368            | TokenKind::Int
369            | TokenKind::Float
370            | TokenKind::True
371            | TokenKind::False
372            | TokenKind::StringEnd
373            | TokenKind::RParen
374            | TokenKind::RBracket
375            | TokenKind::RBrace
376    )
377}
378
379fn declares_name(kind: TokenKind) -> bool {
380    matches!(
381        kind,
382        TokenKind::State
383            | TokenKind::Let
384            | TokenKind::Fn
385            | TokenKind::View
386            | TokenKind::Struct
387            | TokenKind::For
388    )
389}
390
391/// What the block after a call to `callee` holds.
392fn trailing_block(callee: Option<&str>) -> BlockKind {
393    match callee
394        .and_then(elements::element)
395        .map(|element| element.block)
396    {
397        Some(TrailingBlock::Children) => BlockKind::Ui,
398        Some(TrailingBlock::Action | TrailingBlock::OptionalAction) => BlockKind::Action,
399        Some(TrailingBlock::Menu) => BlockKind::Menu,
400        Some(TrailingBlock::None) | None => BlockKind::Other,
401    }
402}
403
404impl Call {
405    fn new(callee: Option<String>) -> Self {
406        Self {
407            callee,
408            positional: 0,
409            labels: Vec::new(),
410            label: None,
411        }
412    }
413}