Line data Source code
1 : //! Completion inside a `CALL` of a namespace with client functions: its functions, options and nested attributes.
2 : //!
3 : //! The text being typed usually doesn't parse, so the position comes from the tokens, not the tree. A walk from the
4 : //! start of the statement keeps a stack with the schema of every open `(` and `[`. A wrong decision here only affects
5 : //! suggestions, since nothing of this is part of parsing.
6 :
7 : use crate::catalog::clfn::{ClientFunctionAttribute, ClientFunctionEffect, ClientFunctionField, ClientFunction, ClientFunctionSchema, ClientFunctionSpec, ClientFunctionUnknownPolicy, OPTION_NEXT, OPTION_PAGES, OPTION_POLL, OPTION_WAIT};
8 : use crate::catalog::CatalogSnapshot;
9 : use crate::completion::{Candidate, CandidateField, CandidateInfo, CandidateTags, Completion, Score};
10 : use crate::utils::quote_identifier;
11 : use dashql_buffers::analyzer::NameTag;
12 : use dashql_buffers::completion::CandidateTag;
13 : use dashql_buffers::parser::SymbolSpan;
14 : use dashql_parser::scanner::Symbol;
15 : use dashql_parser::symbol_kind::*;
16 : use dashql_parser::keywords::Keyword;
17 : use dashql_parser::SymbolKind;
18 : use std::sync::Arc;
19 :
20 : /// The score of a prefix match, it ranks before every substring match
21 : const PREFIX_BONUS: Score = 1 << 20;
22 : /// The score of a required option over an optional one that matches equally well
23 : const REQUIRED_BONUS: Score = 1 << 18;
24 : /// The score of an option of the spec over a standard option
25 : const SPEC_OPTION_BONUS: Score = 1;
26 : /// The score of a function that only reads over one that changes something
27 : const READ_FUNCTION_BONUS: Score = 1;
28 :
29 : /// Can a token be the name of a function, an option or an attribute?
30 128 : fn is_name(kind: SymbolKind) -> bool {
31 128 : kind == S_IDENT || Keyword::name_of(kind).is_some_and(|name| !name.is_empty())
32 128 : }
33 :
34 : /// What an open bracket holds
35 : enum Frame<'s> {
36 : /// The options of the function
37 : Options(&'s ClientFunction),
38 : /// The fields of an object: its schema, None for a free-form object
39 : Fields(Option<&'s ClientFunctionSchema>),
40 : /// The map of an attribute: its keys are free, the attribute holds for its values
41 : Map(&'s ClientFunctionAttribute),
42 : /// The items of an array of the attribute, and is the array a value of its map?
43 : Items(Option<&'s ClientFunctionAttribute>, bool),
44 : /// Anything else, such as the arguments of a function
45 : Opaque,
46 : }
47 :
48 : /// A frame and its names so far
49 : struct Scope<'s> {
50 : frame: Frame<'s>,
51 : /// The names that are used already in lower case, with their values if they are strings
52 : used: Vec<ClientFunctionField>,
53 : /// The attribute of the value being read
54 : attribute: Option<&'s ClientFunctionAttribute>,
55 : /// Is the next token a key?
56 : at_key: bool,
57 : }
58 :
59 : impl<'s> Scope<'s> {
60 49 : fn new(frame: Frame<'s>) -> Self {
61 49 : let at_key = matches!(frame, Frame::Options(_) | Frame::Fields(_) | Frame::Map(_));
62 49 : Self { frame, used: Vec::new(), attribute: None, at_key }
63 49 : }
64 :
65 : /// The attributes that a key selects from and if they are required. A union offers what its variants that still
66 : /// fit take, and an attribute is only required if all of them need it.
67 52 : fn attributes(&self, spec: &'s ClientFunctionSpec) -> Vec<(&'s ClientFunctionAttribute, bool)> {
68 15 : match self.frame {
69 202 : Frame::Options(function) => function.options.iter().map(|o| (o, o.required)).collect(),
70 15 : Frame::Fields(Some(schema)) => variant_attributes(&spec.fit(schema, &self.used).variants),
71 3 : _ => Vec::new(),
72 : }
73 52 : }
74 : }
75 :
76 : /// What variants take, and if each attribute is required: an attribute is only required if all of them need it
77 22 : fn variant_attributes<'s>(variants: &[&'s ClientFunctionSchema]) -> Vec<(&'s ClientFunctionAttribute, bool)> {
78 22 : let mut out: Vec<(&'s ClientFunctionAttribute, bool)> = Vec::new();
79 50 : for attribute in variants.iter().flat_map(|v| v.attributes.iter()) {
80 50 : if !out.iter().any(|(a, _)| a.name == attribute.name) {
81 85 : let required = variants.iter().all(|v| v.attributes.iter().any(|a| a.name == attribute.name && a.required));
82 48 : out.push((attribute, required));
83 2 : }
84 : }
85 22 : out
86 22 : }
87 :
88 : /// Where the cursor is
89 : enum Position<'s> {
90 : /// After `CALL`: the namespaces
91 : Namespace,
92 : /// After the namespace: its functions
93 : Function(&'s ClientFunctionSpec),
94 : /// A key of a scope
95 : Key(&'s ClientFunctionSpec, Scope<'s>),
96 : /// Somewhere that takes no candidates
97 : Nothing,
98 : /// Not in a call of a namespace with a spec
99 : Outside,
100 : }
101 :
102 : /// Does an opening parenthesis start embedded SQL? `(select = 1)` is an object, `(select 1)` a query.
103 18 : fn starts_embedded_sql(symbols: &[Symbol], paren: usize) -> bool {
104 18 : let mut next = paren + 1;
105 18 : while symbols.get(next).is_some_and(|s| s.kind == S_LRB) {
106 0 : next += 1;
107 0 : }
108 18 : let Some(first) = symbols.get(next) else { return false };
109 10 : let follower = symbols.get(next + 1).map(|s| s.kind);
110 10 : match first.kind {
111 3 : S_SELECT | S_WITH | S_WITH_LA | S_TABLE => follower != Some(S_EQUALS),
112 0 : S_VALUES => follower == Some(S_LRB),
113 7 : _ => false,
114 : }
115 18 : }
116 :
117 : /// The text of a token in lower case
118 101 : fn token_text(completion: &Completion, symbol: &Symbol) -> String {
119 101 : let text = completion.text_at(symbol.location.offset, symbol.location.length);
120 101 : String::from_utf8_lossy(text).trim_matches('"').to_ascii_lowercase()
121 101 : }
122 :
123 : /// Find the position in the tokens of a statement before the target token
124 42 : fn find_position<'s>(completion: &Completion, specs: &'s CatalogSnapshot, symbols: &[Symbol]) -> Position<'s> {
125 42 : let mut i = 0;
126 42 : if symbols.get(i).is_some_and(|s| s.kind == S_EXPLAIN) {
127 1 : i += 1;
128 41 : }
129 42 : if symbols.get(i).is_none_or(|s| s.kind != S_CALL_P) {
130 2 : return Position::Outside;
131 40 : }
132 40 : i += 1;
133 : // The namespace
134 40 : if i == symbols.len() {
135 2 : return Position::Namespace;
136 38 : }
137 38 : let Some(namespace) = symbols.get(i).filter(|s| is_name(s.kind)) else { return Position::Outside };
138 38 : let Some(spec) = specs.client_function_spec(&token_text(completion, namespace)) else { return Position::Outside };
139 37 : i += 1;
140 37 : let Some(dot) = symbols.get(i) else { return Position::Outside };
141 37 : if dot.kind != S_DOT && dot.kind != S_DOT_TRAILING {
142 0 : return Position::Outside;
143 37 : }
144 37 : i += 1;
145 : // The function
146 37 : let Some(function) = symbols.get(i) else { return Position::Function(spec) };
147 30 : let Some(function) = Some(function).filter(|s| is_name(s.kind)).and_then(|s| spec.function(&token_text(completion, s))) else {
148 0 : return Position::Nothing;
149 : };
150 30 : i += 1;
151 30 : let Some(open) = symbols.get(i) else { return Position::Nothing };
152 30 : if open.kind != S_LRB {
153 0 : return Position::Nothing;
154 30 : }
155 30 : i += 1;
156 :
157 30 : let mut stack = vec![Scope::new(Frame::Options(function))];
158 122 : while let Some(symbol) = symbols.get(i) {
159 93 : let Some(top) = stack.last_mut() else { return Position::Outside };
160 18 : match symbol.kind {
161 : S_RRB | S_RSB => {
162 1 : stack.pop();
163 1 : if stack.is_empty() {
164 0 : return Position::Outside;
165 1 : }
166 : }
167 : S_COMMA => {
168 9 : top.attribute = top.attribute.filter(|_| matches!(top.frame, Frame::Items(..)));
169 9 : top.at_key = matches!(top.frame, Frame::Options(_) | Frame::Fields(_) | Frame::Map(_));
170 : }
171 18 : S_LRB if matches!(top.frame, Frame::Opaque) => stack.push(Scope::new(Frame::Opaque)),
172 0 : S_LRB if top.at_key => stack.push(Scope::new(Frame::Opaque)),
173 : S_LRB => {
174 : // A name before the parenthesis is a function, the analyzer rejects it
175 18 : if i > 0 && is_name(symbols[i - 1].kind) {
176 0 : stack.push(Scope::new(Frame::Opaque));
177 18 : } else if starts_embedded_sql(symbols, i) {
178 : // Skip the query. If the cursor is inside it, core SQL completes it.
179 2 : let mut depth = 0;
180 11 : while let Some(s) = symbols.get(i) {
181 10 : match s.kind {
182 3 : S_LRB => depth += 1,
183 2 : S_RRB => depth -= 1,
184 5 : _ => {}
185 : }
186 10 : if depth == 0 {
187 1 : break;
188 9 : }
189 9 : i += 1;
190 : }
191 2 : if depth != 0 {
192 1 : return Position::Outside;
193 1 : }
194 : } else {
195 16 : let (attribute, in_map) = match top.frame {
196 3 : Frame::Items(attribute, in_map) => (attribute, in_map),
197 3 : Frame::Map(attribute) => (Some(attribute), true),
198 10 : _ => (top.attribute, false),
199 : };
200 16 : match attribute.filter(|a| a.map && !in_map) {
201 4 : Some(map) => stack.push(Scope::new(Frame::Map(map))),
202 : None => {
203 12 : let schema = attribute.and_then(|a| a.schema.as_deref()).and_then(|s| spec.schema(s));
204 12 : stack.push(Scope::new(Frame::Fields(schema)));
205 : }
206 : }
207 : }
208 : }
209 : S_LSB => {
210 3 : let (attribute, in_map) = match top.frame {
211 0 : Frame::Items(attribute, in_map) => (attribute, in_map),
212 0 : Frame::Map(attribute) => (Some(attribute), true),
213 0 : Frame::Opaque => (None, false),
214 3 : _ => (top.attribute, false),
215 : };
216 3 : stack.push(Scope::new(Frame::Items(attribute, in_map)));
217 : }
218 27 : S_EQUALS | S_EQUALS_GREATER => {}
219 27 : _ if top.at_key => {
220 : // A key: remember its attribute for the value that follows
221 27 : let text = token_text(completion, symbol);
222 27 : top.used.push(ClientFunctionField { name: text.clone(), ..Default::default() });
223 81 : top.attribute = top.attributes(spec).into_iter().map(|(a, _)| a).find(|a| a.name.eq_ignore_ascii_case(&text));
224 27 : top.at_key = false;
225 : }
226 : // The value of the key, it can select the variant of a union
227 : S_SCONST => {
228 6 : if let Some(field) = top.used.last_mut() {
229 6 : field.text = Some(token_text(completion, symbol).trim_matches('\'').to_string());
230 6 : }
231 : }
232 2 : _ => {}
233 : }
234 92 : i += 1;
235 : }
236 29 : let Some(top) = stack.pop() else { return Position::Outside };
237 29 : if top.at_key { Position::Key(spec, top) } else { Position::Nothing }
238 42 : }
239 :
240 : /// A name as it is inserted in a script
241 101 : fn insert_text(name: &str) -> Arc<str> {
242 : // Salesforce-style names keep their case, they scan fine without quotes
243 693 : let plain = name.bytes().enumerate().all(|(i, c)| c.is_ascii_alphabetic() || c == b'_' || (i > 0 && c.is_ascii_digit()));
244 101 : if plain { Arc::from(name) } else { Arc::from(quote_identifier(name)) }
245 101 : }
246 :
247 : /// What a candidate of a call is
248 : #[derive(Clone, Copy)]
249 : enum Source<'s> {
250 : Namespace(&'s ClientFunctionSpec),
251 : Function(&'s ClientFunctionSpec, &'s ClientFunction),
252 : /// An option or attribute, and is it required here? In a union that depends on the variants that still fit.
253 : Attribute(&'s ClientFunctionSpec, &'s ClientFunctionAttribute, bool),
254 : StandardOption(&'static str),
255 : }
256 :
257 : impl Source<'_> {
258 101 : fn name_tags(self) -> u8 {
259 101 : match self {
260 2 : Source::Namespace(_) => NameTag::SCHEMA_NAME.0,
261 31 : Source::Function(..) => NameTag::FUNCTION_NAME.0,
262 68 : Source::Attribute(..) | Source::StandardOption(_) => NameTag::OPTION_NAME.0,
263 : }
264 101 : }
265 :
266 : /// What a list shows about the candidate
267 88 : fn info(self) -> CandidateInfo {
268 88 : match self {
269 2 : Source::Namespace(spec) => CandidateInfo { detail: spec.title.clone(), ..Default::default() },
270 25 : Source::Function(spec, function) => function_info(spec, function),
271 50 : Source::Attribute(spec, attribute, required) => attribute_info(spec, attribute, required),
272 11 : Source::StandardOption(option) => standard_option_info(option),
273 : }
274 88 : }
275 : }
276 :
277 : /// A fact of the details: a label and a value
278 27 : fn fact(label: &str, value: impl Into<String>) -> CandidateField {
279 27 : CandidateField { name: label.to_string(), detail: Some(value.into()), description: None }
280 27 : }
281 :
282 : /// The table of options or attributes, the required ones first
283 32 : fn attribute_fields(attributes: &[(&ClientFunctionAttribute, bool)]) -> Vec<CandidateField> {
284 32 : let required = attributes.iter().filter(|(_, required)| *required);
285 53 : let optional = attributes.iter().filter(|(_, required)| !*required);
286 32 : required
287 32 : .chain(optional)
288 53 : .map(|(a, required)| CandidateField { name: a.name.clone(), detail: attribute_detail(a, *required), description: a.description.clone() })
289 32 : .collect()
290 32 : }
291 :
292 : /// The signature of a function: the options of the spec with the required ones first, `?` marks an optional one. A
293 : /// function without options has none, and neither does the escape hatch, which takes any option.
294 25 : fn function_signature(function: &ClientFunction) -> Option<String> {
295 25 : if function.raw || function.options.is_empty() {
296 8 : return None;
297 17 : }
298 17 : let required = function.options.iter().filter(|o| o.required);
299 39 : let optional = function.options.iter().filter(|o| !o.required);
300 17 : let options: Vec<String> = required
301 17 : .chain(optional)
302 39 : .map(|option| {
303 39 : let name = if option.required { option.name.clone() } else { format!("{}?", option.name) };
304 39 : match attribute_detail(option, false) {
305 34 : Some(value) => format!("{name} {value}"),
306 5 : None => name,
307 : }
308 39 : })
309 17 : .collect();
310 17 : Some(format!("({})", options.join(", ")))
311 25 : }
312 :
313 : /// What a list shows about a function
314 25 : fn function_info(spec: &ClientFunctionSpec, function: &ClientFunction) -> CandidateInfo {
315 39 : let options: Vec<(&ClientFunctionAttribute, bool)> = function.options.iter().map(|o| (o, o.required)).collect();
316 25 : let mut parameters = attribute_fields(&options);
317 25 : parameters.extend(function.standard_options(spec).into_iter().map(|option| {
318 12 : let info = standard_option_info(option);
319 12 : CandidateField { name: option.to_string(), detail: info.detail, description: info.description }
320 12 : }));
321 25 : let columns = function
322 25 : .columns
323 25 : .iter()
324 25 : .map(|column| CandidateField { name: column.name.clone(), detail: column.column_type.clone(), description: None })
325 25 : .collect();
326 25 : let mut facts = Vec::new();
327 25 : if let Some(family) = &function.family {
328 6 : facts.push(fact("Family", family));
329 19 : }
330 25 : if let (Some(method), Some(path)) = (&function.method, &function.path) {
331 9 : facts.push(fact("Endpoint", format!("{method} {path}")));
332 20 : }
333 25 : if let Some(paging) = &function.paging {
334 6 : facts.push(fact("Paging", &paging.kind));
335 19 : }
336 25 : CandidateInfo {
337 25 : detail: function_signature(function),
338 25 : description: function.description.clone(),
339 25 : parameters,
340 25 : result_description: function.result_description.clone(),
341 25 : columns,
342 25 : example: function.example.clone(),
343 25 : facts,
344 25 : }
345 25 : }
346 :
347 : /// What a list shows about an option or attribute. A schema lists its attributes one level deep, the list of the object
348 : /// shows the next level when someone writes it.
349 50 : fn attribute_info(spec: &ClientFunctionSpec, attribute: &ClientFunctionAttribute, required: bool) -> CandidateInfo {
350 50 : let schema = attribute.schema.as_deref().and_then(|name| spec.schema(name));
351 50 : let mut facts = Vec::new();
352 50 : if !attribute.values.is_empty() {
353 6 : let values: Vec<String> = attribute.values.iter().map(|v| v.as_str().map_or_else(|| v.to_string(), str::to_string)).collect();
354 3 : facts.push(fact("Values", values.join(", ")));
355 47 : }
356 50 : if let Some(format) = &attribute.format {
357 3 : facts.push(fact("Format", format));
358 47 : }
359 : CandidateInfo {
360 50 : detail: attribute_detail(attribute, required),
361 50 : description: attribute.description.clone(),
362 50 : parameters: schema.map_or_else(Vec::new, |s| attribute_fields(&variant_attributes(&spec.fit(s, &[]).variants))),
363 50 : facts,
364 50 : ..Default::default()
365 : }
366 50 : }
367 :
368 : /// The type of an option or attribute. A schema has none to show: its name says nothing in a script, and a list shows
369 : /// its attributes instead.
370 142 : fn attribute_detail(attribute: &ClientFunctionAttribute, required: bool) -> Option<String> {
371 142 : let arrays = "array of ".repeat(attribute.array as usize);
372 142 : let value = match attribute.value_type.as_deref().filter(|_| attribute.schema.is_none()) {
373 116 : Some(value) => Some(format!("{arrays}{value}")),
374 26 : None if attribute.array > 0 => Some(arrays.trim_end_matches(" of ").to_string()),
375 15 : None => None,
376 : };
377 142 : let value = match (attribute.map, value) {
378 12 : (true, Some(value)) => Some(format!("map of {value}")),
379 0 : (true, None) => Some("map".to_string()),
380 130 : (false, value) => value,
381 : };
382 142 : match (value, required) {
383 28 : (Some(value), true) => Some(format!("{value} ยท required")),
384 0 : (None, true) => Some("required".to_string()),
385 114 : (value, false) => value,
386 : }
387 142 : }
388 :
389 : /// What a list shows about a standard option, no spec describes them
390 23 : fn standard_option_info(option: &str) -> CandidateInfo {
391 23 : let (detail, description) = match option {
392 23 : OPTION_NEXT => ("string", "The page to read, the `_next` column of an earlier result. `null` means the first page."),
393 13 : OPTION_PAGES => ("integer", "The number of pages to read, 1 without this option."),
394 2 : OPTION_POLL => ("string", "The pending result to poll, the `_poll` column of an earlier result."),
395 1 : OPTION_WAIT => ("integer", "The seconds to poll a pending result for, no polling without this option."),
396 0 : _ => return CandidateInfo::default(),
397 : };
398 23 : CandidateInfo { detail: Some(detail.to_string()), description: Some(description.to_string()), ..Default::default() }
399 23 : }
400 :
401 : /// The candidates at a cursor
402 : struct Candidates<'s> {
403 : /// The typed prefix in lower case
404 : prefix: String,
405 : /// The text range that a candidate replaces
406 : target: SymbolSpan,
407 : /// The candidates and what they are
408 : out: Vec<(Candidate, Source<'s>)>,
409 : }
410 :
411 : impl<'s> Candidates<'s> {
412 : /// Add a candidate if it matches the prefix, the bonus ranks the candidates that match equally well
413 144 : fn add(&mut self, name: &str, bonus: Score, source: Source<'s>) {
414 144 : let lower = name.to_ascii_lowercase();
415 144 : let (tags, bonus): (CandidateTags, Score) = if self.prefix.is_empty() || lower.starts_with(&self.prefix) {
416 99 : (CandidateTag::PREFIX_MATCH.0, bonus + PREFIX_BONUS)
417 45 : } else if lower.contains(&self.prefix) {
418 2 : (CandidateTag::SUBSTRING_MATCH.0, bonus)
419 : } else {
420 43 : return;
421 : };
422 101 : let mut candidate = Candidate::new(insert_text(name), source.name_tags(), tags, self.target, self.target);
423 101 : candidate.completion_text_is_verbatim = true;
424 101 : candidate.score = bonus;
425 101 : self.out.push((candidate, source));
426 144 : }
427 :
428 : /// The top k candidates: by score, then by name. Only these get their info, a namespace can have many functions.
429 38 : fn finish(mut self, k: usize) -> Vec<Candidate> {
430 98 : self.out.sort_by(|(l, _), (r, _)| r.score.cmp(&l.score).then_with(|| l.completion_text.cmp(&r.completion_text)));
431 38 : self.out.truncate(k);
432 38 : self.out
433 38 : .into_iter()
434 88 : .map(|(mut candidate, source)| {
435 88 : candidate.info = source.info();
436 88 : candidate
437 88 : })
438 38 : .collect()
439 38 : }
440 : }
441 :
442 : /// Complete at a cursor in a `CALL` of a namespace with client functions. Returns false if the cursor is elsewhere, or in a
443 : /// query inside the call, which core SQL completes.
444 507 : pub(crate) fn complete_client_function_call(completion: &mut Completion, k: usize) -> bool {
445 507 : let specs = completion.catalog();
446 507 : if specs.client_function_specs().is_empty() {
447 465 : return false;
448 42 : }
449 42 : let symbols = completion.symbols();
450 42 : let offset = completion.cursor.text_offset as u32;
451 : // The tokens that end before the cursor, and the name that the cursor is in or right behind
452 42 : let before = symbols
453 42 : .iter()
454 342 : .take_while(|s| s.kind != S_YYEOF && s.location.offset + s.location.length <= offset)
455 42 : .count();
456 42 : let containing = symbols.get(before).filter(|s| s.location.offset < offset && s.kind != S_YYEOF);
457 42 : if containing.is_some_and(|s| !is_name(s.kind)) {
458 0 : return false;
459 42 : }
460 42 : let (end, target) = match (containing, before.checked_sub(1).map(|i| &symbols[i])) {
461 0 : (Some(symbol), _) => (before, Some(symbol)),
462 : // Right behind a name
463 42 : (None, Some(previous)) if is_name(previous.kind) && previous.location.offset + previous.location.length == offset => {
464 12 : (before - 1, Some(previous))
465 : }
466 30 : _ => (before, None),
467 : };
468 279 : let statement_begin = symbols[..end].iter().rposition(|s| s.kind == S_SEMICOLON).map_or(0, |i| i + 1);
469 42 : let position = find_position(completion, specs, &symbols[statement_begin..end]);
470 42 : let (prefix, span) = match target {
471 12 : Some(symbol) => {
472 12 : let length = offset.saturating_sub(symbol.location.offset);
473 12 : let typed = completion.text_at(symbol.location.offset, length);
474 12 : let prefix = String::from_utf8_lossy(typed).trim_matches('"').to_ascii_lowercase();
475 12 : (prefix, SymbolSpan::new(symbol.location.offset, symbol.location.length))
476 : }
477 30 : None => (String::new(), SymbolSpan::new(offset, 0)),
478 : };
479 42 : let mut candidates = Candidates { prefix, target: span, out: Vec::new() };
480 42 : completion.client_function_name = matches!(position, Position::Function(_) | Position::Key(..));
481 42 : match position {
482 4 : Position::Outside => return false,
483 1 : Position::Nothing => {}
484 : // A procedure of the backend is possible as well, core SQL completes it if no namespace matches
485 : Position::Namespace => {
486 2 : for (namespace, spec) in specs.client_function_specs() {
487 2 : candidates.add(namespace, 0, Source::Namespace(spec));
488 2 : }
489 2 : if candidates.out.is_empty() {
490 0 : return false;
491 2 : }
492 : }
493 7 : Position::Function(spec) => {
494 43 : for function in &spec.functions {
495 43 : candidates.add(
496 43 : &function.name,
497 43 : if function.effect() == ClientFunctionEffect::Read { READ_FUNCTION_BONUS } else { 0 },
498 43 : Source::Function(spec, function),
499 : );
500 : }
501 : }
502 28 : Position::Key(spec, scope) => {
503 28 : let free_form = match scope.frame {
504 2 : Frame::Fields(None) | Frame::Map(_) => true,
505 10 : Frame::Fields(Some(schema)) => schema.attributes.is_empty() && schema.unknown == ClientFunctionUnknownPolicy::Allow,
506 16 : _ => false,
507 : };
508 28 : if !free_form {
509 105 : let used = |name: &str| scope.used.iter().any(|u| u.name.eq_ignore_ascii_case(name));
510 93 : for (attribute, required) in scope.attributes(spec).into_iter().filter(|(a, _)| !used(&a.name)) {
511 87 : candidates.add(
512 87 : &attribute.name,
513 87 : if required { REQUIRED_BONUS } else { SPEC_OPTION_BONUS },
514 87 : Source::Attribute(spec, attribute, required),
515 : );
516 : }
517 25 : if let Frame::Options(function) = scope.frame {
518 16 : for option in function.standard_options(spec).iter().filter(|o| !used(o)) {
519 12 : candidates.add(option, 0, Source::StandardOption(option));
520 12 : }
521 9 : }
522 3 : }
523 : }
524 : }
525 38 : completion.top_candidates = candidates.finish(k);
526 38 : true
527 507 : }
|