1use nifty_check::elements::{self, TrailingBlock};
6use nifty_syntax::{Token, TokenKind};
7
8use crate::document::Document;
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub(crate) enum BlockKind {
13 TopLevel,
14 Function,
16 Ui,
18 Action,
20 Menu,
22 Struct,
24 Other,
26}
27
28#[derive(Clone, Debug, PartialEq, Eq)]
30pub(crate) struct Call {
31 pub callee: Option<String>,
33 pub positional: usize,
35 pub labels: Vec<String>,
37 pub label: Option<String>,
39}
40
41#[derive(Clone, Debug, PartialEq, Eq)]
43pub(crate) enum Slot {
44 Statement(BlockKind),
46 AfterIf,
48 Expression,
50 ArgumentStart(Call),
52 ArgumentValue(Call),
54 ImplicitMember(Call),
56 Type,
58 ForIn,
60 Nothing,
63}
64
65#[derive(Debug)]
67pub(crate) struct Context {
68 pub slot: Slot,
69 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
100const LOOK_BACK: usize = 4;
102
103#[derive(Debug)]
104enum Frame {
105 Block {
106 kind: BlockKind,
107 from_if: bool,
108 },
109 Call(Call),
110 Params,
112 ListType,
114 Group,
115 String,
116 Interpolation,
117}
118
119struct Scanner {
120 frames: Vec<Frame>,
121 recent: Vec<Token>,
123 last: Option<Token>,
124 last_name: Option<String>,
125 name_before_last: Option<String>,
128 newline_after_last: bool,
129 pending: Option<(BlockKind, usize, bool)>,
131 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 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 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
353fn 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
391fn 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}