Skip to main content

zen_expression/intellisense/
completion.rs

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}