1use crate::functions::registry::FunctionRegistry;
2use crate::functions::DateMethod;
3use crate::functions::{
4 ClosureFunction, DeprecatedFunction, FunctionKind, InternalFunction, MethodKind, MethodRegistry,
5};
6use crate::intellisense::IntelliSenseToken;
7use crate::variable::VariableType;
8use serde::Serialize;
9use strum::IntoEnumIterator;
10
11#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
12#[serde(rename_all = "camelCase")]
13pub enum CompletionKind {
14 Variable,
15 Function,
16 Method,
17 Property,
18}
19
20#[derive(Debug, Clone, Serialize)]
21#[serde(rename_all = "camelCase")]
22pub struct Completion {
23 pub label: String,
24 pub kind: CompletionKind,
25 pub detail: String,
26 pub info: String,
27 #[serde(skip_serializing_if = "Option::is_none")]
28 pub boost: Option<i32>,
29 #[serde(skip_serializing_if = "Option::is_none")]
30 pub method_for: Option<VariableType>,
31}
32
33pub struct Completions;
34
35impl Completions {
36 pub fn build(
37 source: &str,
38 pos: u32,
39 data: &VariableType,
40 tokens: &[IntelliSenseToken],
41 ) -> Vec<Completion> {
42 let before = source.get(..pos as usize).unwrap_or(source);
43 let prefix = Self::extract_prefix(before);
44
45 let completions = match Self::find_property_dot(before) {
46 Some(dot) => {
47 let target_type = tokens
48 .iter()
49 .filter(|t| t.span.1 <= dot as u32 && t.span.1 > 0)
50 .max_by(|a, b| {
51 a.span
52 .1
53 .cmp(&b.span.1)
54 .then_with(|| (a.span.1 - a.span.0).cmp(&(b.span.1 - b.span.0)))
55 })
56 .map(|t| t.kind.clone())
57 .unwrap_or(VariableType::Any);
58
59 Self::build_property(&target_type)
60 }
61 None => Self::build_scope(data),
62 };
63
64 Self::filter(completions, prefix)
65 }
66
67 pub fn build_property(vt: &VariableType) -> Vec<Completion> {
68 let mut completions = Vec::new();
69 let resolved = match vt {
70 VariableType::Nullable(inner) => inner.as_ref(),
71 other => other,
72 };
73
74 if let VariableType::Object(obj) = resolved {
75 let obj = obj.borrow();
76 for (key, val) in obj.iter() {
77 completions.push(Completion {
78 label: key.to_string(),
79 kind: CompletionKind::Property,
80 detail: val.to_string(),
81 info: String::new(),
82 boost: Some(10),
83 method_for: None,
84 });
85 }
86 }
87
88 for mk in DateMethod::iter().map(MethodKind::DateMethod) {
89 let def = MethodRegistry::get_definition(&mk);
90 let applies = def
91 .as_ref()
92 .and_then(|d| d.param_type(0))
93 .map(|pt| vt.satisfies(&pt))
94 .unwrap_or(false);
95
96 if applies || matches!(vt, VariableType::Any) {
97 completions.push(Self::method(mk));
98 }
99 }
100
101 completions
102 }
103
104 pub fn build_scope(data: &VariableType) -> Vec<Completion> {
105 let mut completions = Vec::new();
106
107 let resolved_data = match data {
108 VariableType::Nullable(inner) => inner.as_ref(),
109 other => other,
110 };
111
112 if let VariableType::Object(obj) = resolved_data {
113 let obj = obj.borrow();
114 for (key, val) in obj.iter() {
115 completions.push(Completion {
116 label: key.to_string(),
117 kind: CompletionKind::Variable,
118 detail: val.to_string(),
119 info: String::new(),
120 boost: Some(20),
121 method_for: None,
122 });
123 }
124 }
125
126 completions.push(Completion {
127 label: "$root".to_string(),
128 kind: CompletionKind::Variable,
129 detail: "Root variable".to_string(),
130 info: String::new(),
131 boost: Some(-10),
132 method_for: None,
133 });
134
135 completions.extend(
136 InternalFunction::iter()
137 .map(FunctionKind::Internal)
138 .chain(ClosureFunction::iter().map(FunctionKind::Closure))
139 .map(|fk| Self::function(fk, None)),
140 );
141
142 completions
143 }
144
145 fn function(fk: FunctionKind, boost_override: Option<i32>) -> Completion {
146 let label = fk.to_string();
147 let info = function_info(&fk);
148 let detail = function_signature(&fk);
149 let boost = boost_override.or(match &fk {
150 FunctionKind::Internal(_) => Some(10),
151 FunctionKind::Closure(_) => None,
152 FunctionKind::Deprecated(_) => Some(-20),
153 });
154
155 Completion {
156 label,
157 kind: CompletionKind::Function,
158 detail,
159 info,
160 boost,
161 method_for: None,
162 }
163 }
164
165 fn method(mk: MethodKind) -> Completion {
166 let label = mk.to_string();
167 let info = method_info(&mk);
168 let (detail, method_for) = method_signature(&mk);
169
170 Completion {
171 label,
172 kind: CompletionKind::Method,
173 detail,
174 info,
175 boost: None,
176 method_for,
177 }
178 }
179
180 fn extract_prefix(before_cursor: &str) -> &str {
181 let boundary = before_cursor
182 .rfind(|c: char| !c.is_alphanumeric() && c != '_' && c != '$' && c != '#')
183 .map(|i| i + 1)
184 .unwrap_or(0);
185
186 &before_cursor[boundary..]
187 }
188
189 fn filter(completions: Vec<Completion>, prefix: &str) -> Vec<Completion> {
190 if prefix.is_empty() {
191 return completions;
192 }
193
194 let prefix_lower = prefix.to_lowercase();
195 completions
196 .into_iter()
197 .filter(|c| c.label.to_lowercase().starts_with(&prefix_lower))
198 .collect()
199 }
200
201 fn find_property_dot(before_cursor: &str) -> Option<usize> {
202 let trimmed = before_cursor.trim_end();
203 if trimmed.ends_with('.') {
204 return Some(trimmed.len() - 1);
205 }
206
207 let word_start = trimmed.rfind(|c: char| !c.is_alphanumeric() && c != '_' && c != '#');
208 match word_start {
209 Some(i) if trimmed.as_bytes().get(i) == Some(&b'.') => Some(i),
210 _ => None,
211 }
212 }
213}
214
215fn function_info(fk: &FunctionKind) -> String {
216 let s = match fk {
217 FunctionKind::Internal(i) => match i {
218 InternalFunction::Len => "Returns the length of variable",
219 InternalFunction::Contains => "Checks if variable contains a needle",
220 InternalFunction::Flatten => "Flattens an array",
221 InternalFunction::Upper => "Converts all characters in a string to uppercase",
222 InternalFunction::Lower => "Converts all characters in a string to lowercase",
223 InternalFunction::Trim => {
224 "Returns the string with leading and trailing whitespace removed"
225 }
226 InternalFunction::StartsWith => {
227 "Returns true if the string starts with the specified prefix"
228 }
229 InternalFunction::EndsWith => {
230 "Returns true if the string ends with the specified suffix"
231 }
232 InternalFunction::Matches => "Returns true if the string matches the specified pattern",
233 InternalFunction::Extract => "Extracts matching substrings according to a pattern",
234 InternalFunction::FuzzyMatch => "Performs a fuzzy search of the needle in the haystack",
235 InternalFunction::Split => {
236 "Splits a string into an array of substrings using the specified delimiter"
237 }
238 InternalFunction::Abs => "Returns the absolute value of a number",
239 InternalFunction::Sum => "Returns the sum of all elements in the input array",
240 InternalFunction::Avg => "Calculates the average of all elements in the input array",
241 InternalFunction::Min => "Returns the smallest of the elements in the input array",
242 InternalFunction::Max => "Returns the largest of the elements in the input array",
243 InternalFunction::Rand => {
244 "Generates a random number between 0 (inclusive) and max (inclusive)"
245 }
246 InternalFunction::Median => {
247 "Calculates the median value of all elements in the input array"
248 }
249 InternalFunction::Mode => "Finds the mode(s) of the input array",
250 InternalFunction::Floor => "Rounds a number down to the nearest integer",
251 InternalFunction::Ceil => "Rounds a number up to the nearest integer",
252 InternalFunction::Round => "Rounds a number to a specified number of decimal places",
253 InternalFunction::Trunc => "Truncates a number to a specified number of decimal places",
254 InternalFunction::IsNumeric => "Checks if the given value is of a numeric type",
255 InternalFunction::String => "Converts the given value to a string",
256 InternalFunction::Number => "Converts the given value to a number",
257 InternalFunction::Bool => "Converts the given value to a boolean",
258 InternalFunction::Type => "Returns a string representing the data type of the value",
259 InternalFunction::Keys => {
260 "Returns an array of a given object's own enumerable property names"
261 }
262 InternalFunction::Values => {
263 "Returns an array of a given object's own enumerable property values"
264 }
265 InternalFunction::Date => "Returns a new date time instance",
266 InternalFunction::Merge => "Merges multiple objects into one",
267 InternalFunction::MergeDeep => "Deeply merges multiple objects into one",
268 },
269 FunctionKind::Deprecated(d) => match d {
270 DeprecatedFunction::Date => "Converts a numeric timestamp to a unix timestamp",
271 DeprecatedFunction::Time => "Extracts the time from a numeric timestamp",
272 DeprecatedFunction::Duration => "Parses a duration string (e.g. 1h30min)",
273 DeprecatedFunction::Year => "Extracts the year from a given timestamp",
274 DeprecatedFunction::DayOfWeek => "Gets the day of the week from a given timestamp",
275 DeprecatedFunction::DayOfMonth => {
276 "Extracts the day of the month from a given timestamp"
277 }
278 DeprecatedFunction::DayOfYear => "Gets the day of the year from a given timestamp",
279 DeprecatedFunction::WeekOfYear => {
280 "Calculates the week of the year from a given timestamp"
281 }
282 DeprecatedFunction::MonthOfYear => "Extracts the month from a given timestamp",
283 DeprecatedFunction::MonthString => {
284 "Converts the month from a given timestamp into its string representation"
285 }
286 DeprecatedFunction::DateString => {
287 "Converts a timestamp to a human-readable date string"
288 }
289 DeprecatedFunction::WeekdayString => {
290 "Converts the day of the week into its string representation"
291 }
292 DeprecatedFunction::StartOf => {
293 "Returns the timestamp representing the start of a specified unit"
294 }
295 DeprecatedFunction::EndOf => {
296 "Returns the timestamp representing the end of a specified unit"
297 }
298 },
299 FunctionKind::Closure(c) => match c {
300 ClosureFunction::All => "Checks if all elements in the array satisfy the condition",
301 ClosureFunction::None => "Checks if no elements in the array satisfy the condition",
302 ClosureFunction::Some => "Checks if at least one element satisfies the condition",
303 ClosureFunction::One => "Checks if exactly one element satisfies the condition",
304 ClosureFunction::Filter => {
305 "Creates a new array with elements that satisfy the condition"
306 }
307 ClosureFunction::Map => "Creates a new array by transforming each element",
308 ClosureFunction::FlatMap => "Maps each element then flattens the result",
309 ClosureFunction::Count => "Counts elements that satisfy the condition",
310 },
311 };
312 s.to_string()
313}
314
315fn function_param_names(fk: &FunctionKind) -> Vec<&'static str> {
316 match fk {
317 FunctionKind::Internal(i) => match i {
318 InternalFunction::Len => vec!["var"],
319 InternalFunction::Contains => vec!["haystack", "needle"],
320 InternalFunction::Flatten => vec!["arr"],
321 InternalFunction::Upper | InternalFunction::Lower | InternalFunction::Trim => {
322 vec!["str"]
323 }
324 InternalFunction::StartsWith => vec!["str", "prefix"],
325 InternalFunction::EndsWith => vec!["str", "suffix"],
326 InternalFunction::Matches | InternalFunction::Extract => vec!["str", "pattern"],
327 InternalFunction::FuzzyMatch => vec!["haystack", "needle"],
328 InternalFunction::Split => vec!["str", "delimiter"],
329 InternalFunction::Abs | InternalFunction::Floor | InternalFunction::Ceil => {
330 vec!["num"]
331 }
332 InternalFunction::Sum
333 | InternalFunction::Avg
334 | InternalFunction::Min
335 | InternalFunction::Max
336 | InternalFunction::Median
337 | InternalFunction::Mode => vec!["arr"],
338 InternalFunction::Rand => vec!["max"],
339 InternalFunction::Round | InternalFunction::Trunc => vec!["num", "digits"],
340 InternalFunction::IsNumeric
341 | InternalFunction::String
342 | InternalFunction::Number
343 | InternalFunction::Bool
344 | InternalFunction::Type => vec!["value"],
345 InternalFunction::Keys | InternalFunction::Values => vec!["obj"],
346 InternalFunction::Date => vec!["dateOrTimezone", "timezone"],
347 InternalFunction::Merge | InternalFunction::MergeDeep => vec!["objects"],
348 },
349 FunctionKind::Deprecated(d) => match d {
350 DeprecatedFunction::Date
351 | DeprecatedFunction::Time
352 | DeprecatedFunction::Year
353 | DeprecatedFunction::DayOfWeek
354 | DeprecatedFunction::DayOfMonth
355 | DeprecatedFunction::DayOfYear
356 | DeprecatedFunction::WeekOfYear
357 | DeprecatedFunction::MonthOfYear
358 | DeprecatedFunction::MonthString
359 | DeprecatedFunction::DateString
360 | DeprecatedFunction::WeekdayString => vec!["timestamp"],
361 DeprecatedFunction::Duration => vec!["duration"],
362 DeprecatedFunction::StartOf | DeprecatedFunction::EndOf => vec!["timestamp", "unit"],
363 },
364 FunctionKind::Closure(_) => vec![],
365 }
366}
367
368fn function_signature(fk: &FunctionKind) -> String {
369 match fk {
370 FunctionKind::Internal(_) | FunctionKind::Deprecated(_) => {
371 let param_names = function_param_names(fk);
372 let Some(definition) = FunctionRegistry::get_definition(fk) else {
373 return String::new();
374 };
375
376 let required = definition.required_parameters();
377 let total = required + definition.optional_parameters();
378 let params: Vec<String> = (0..total)
379 .map(|i| {
380 let name = param_names.get(i).copied().unwrap_or("var");
381 let optional = if i >= required { "?" } else { "" };
382 let typ = definition.param_type_str(i);
383 format!("{name}{optional}: {typ}")
384 })
385 .collect();
386
387 format!(
388 "({}) -> {}",
389 params.join(", "),
390 definition.return_type_str()
391 )
392 }
393 FunctionKind::Closure(c) => match c {
394 ClosureFunction::All
395 | ClosureFunction::None
396 | ClosureFunction::Some
397 | ClosureFunction::One => {
398 "<T>(array: T[], callback: Callback<T, boolean>) -> boolean".to_string()
399 }
400 ClosureFunction::Filter => {
401 "<T>(array: T[], callback: Callback<T, boolean>) -> T[]".to_string()
402 }
403 ClosureFunction::Map => {
404 "<T, U>(array: T[], callback: Callback<T, U>) -> U[]".to_string()
405 }
406 ClosureFunction::FlatMap => {
407 "<T, U>(array: T[], callback: Callback<T, U[]>) -> U[]".to_string()
408 }
409 ClosureFunction::Count => {
410 "<T>(array: T[], callback: Callback<T, boolean>) -> number".to_string()
411 }
412 },
413 }
414}
415
416fn method_info(mk: &MethodKind) -> String {
417 let s = match mk {
418 MethodKind::DateMethod(dm) => match dm {
419 DateMethod::Add => "Adds time to a date",
420 DateMethod::Sub => "Subtracts time from a date",
421 DateMethod::Set => "Sets a specific unit of time on a date",
422 DateMethod::Format => "Formats a date into a string representation",
423 DateMethod::StartOf => "Returns the start of a specified time unit",
424 DateMethod::EndOf => "Returns the end of a specified time unit",
425 DateMethod::Diff => "Calculates the difference between two dates",
426 DateMethod::Tz => "Converts a date to a different timezone",
427 DateMethod::IsSame => "Checks if two dates are the same",
428 DateMethod::IsBefore => "Checks if a date is before another date",
429 DateMethod::IsAfter => "Checks if a date is after another date",
430 DateMethod::IsSameOrBefore => "Checks if a date is the same as or before another",
431 DateMethod::IsSameOrAfter => "Checks if a date is the same as or after another",
432 DateMethod::Second => "Gets the seconds of a date",
433 DateMethod::Minute => "Gets the minutes of a date",
434 DateMethod::Hour => "Gets the hours of a date",
435 DateMethod::Day => "Gets the day of the month",
436 DateMethod::DayOfYear => "Gets the day of the year",
437 DateMethod::Week => "Gets the week of the year",
438 DateMethod::Weekday => "Gets the day of the week",
439 DateMethod::Month => "Gets the month",
440 DateMethod::Quarter => "Gets the quarter",
441 DateMethod::Year => "Gets the year",
442 DateMethod::Timestamp => "Gets the Unix timestamp",
443 DateMethod::OffsetName => "Gets the timezone offset name",
444 DateMethod::IsValid => "Checks if a date is valid",
445 DateMethod::IsYesterday => "Checks if a date is yesterday",
446 DateMethod::IsToday => "Checks if a date is today",
447 DateMethod::IsTomorrow => "Checks if a date is tomorrow",
448 DateMethod::IsLeapYear => "Checks if the year is a leap year",
449 },
450 };
451 s.to_string()
452}
453
454fn method_param_names(mk: &MethodKind) -> Vec<&'static str> {
455 match mk {
456 MethodKind::DateMethod(dm) => match dm {
457 DateMethod::Add | DateMethod::Sub => vec!["amount", "unit"],
458 DateMethod::Set => vec!["value", "unit"],
459 DateMethod::Format => vec!["format"],
460 DateMethod::StartOf | DateMethod::EndOf => vec!["unit"],
461 DateMethod::Diff => vec!["otherDate", "unit"],
462 DateMethod::Tz => vec!["timezone"],
463 DateMethod::IsSame
464 | DateMethod::IsBefore
465 | DateMethod::IsAfter
466 | DateMethod::IsSameOrBefore
467 | DateMethod::IsSameOrAfter => vec!["otherDate", "unit"],
468 _ => vec![],
469 },
470 }
471}
472
473fn method_signature(mk: &MethodKind) -> (String, Option<VariableType>) {
474 let Some(definition) = MethodRegistry::get_definition(mk) else {
475 return (String::new(), None);
476 };
477
478 let param_names = method_param_names(mk);
479 let method_for = definition.param_type(0);
480 let required = definition.required_parameters();
481 let total = required + definition.optional_parameters();
482
483 let params: Vec<String> = (1..total)
484 .map(|i| {
485 let name = param_names.get(i - 1).copied().unwrap_or("var");
486 let optional = if i >= required { "?" } else { "" };
487 let typ = definition.param_type_str(i);
488 format!("{name}{optional}: {typ}")
489 })
490 .collect();
491
492 (
493 format!(
494 "({}) -> {}",
495 params.join(", "),
496 definition.return_type_str()
497 ),
498 method_for,
499 )
500}