Skip to main content

shape_lsp/completion/
functions.rs

1//! Function and keyword completions
2
3use crate::context::ArgumentContext;
4use crate::symbols::{SymbolInfo, symbols_to_completions};
5use crate::type_inference::unified_metadata;
6use shape_runtime::metadata::{FunctionInfo, LanguageMetadata};
7use tower_lsp_server::ls_types::{
8    CompletionItem, CompletionItemKind, Documentation, InsertTextFormat, MarkupContent, MarkupKind,
9};
10
11use super::annotations::{enum_value_completions, symbols_with_annotation};
12use super::providers::provider_completions;
13
14/// Intelligent function argument completions based on parameter constraints
15pub fn function_argument_completions(
16    user_symbols: &[SymbolInfo],
17    function: &str,
18    arg_context: &ArgumentContext,
19) -> Vec<CompletionItem> {
20    match arg_context {
21        ArgumentContext::FunctionArgument { arg_index, .. } => {
22            // Module method calls: duckdb.query(, http.get(, etc.
23            // The function name includes the module prefix (e.g., "duckdb.query")
24            if let Some(dot_pos) = function.rfind('.') {
25                let module = &function[..dot_pos];
26                let method = &function[dot_pos + 1..];
27                if super::imports::is_extension_module(module) {
28                    return super::imports::module_function_param_completions(module, method);
29                }
30            }
31
32            // Content style method argument completions (.fg(, .bg(, .border(, etc.)
33            if let Some(completions) = content_method_arg_completions(function, *arg_index) {
34                return completions;
35            }
36
37            // Get function metadata
38            let meta = unified_metadata();
39            if let Some(func_info) = meta.get_function(function) {
40                if let Some(param) = func_info.parameters.get(*arg_index) {
41                    // Check for parameter constraints
42                    if let Some(constraints) = &param.constraints {
43                        // Provider name constraint
44                        if constraints.is_provider_name {
45                            return provider_completions();
46                        }
47
48                        // Enum values constraint
49                        if let Some(values) = &constraints.allowed_values {
50                            return enum_value_completions(values);
51                        }
52
53                        // Annotation requirement constraint
54                        if let Some(annotation) = &constraints.requires_annotation {
55                            return symbols_with_annotation(annotation, user_symbols);
56                        }
57                    }
58                }
59            }
60
61            // Default: show all symbols (no keywords in function arguments)
62            symbols_to_completions(user_symbols)
63        }
64        ArgumentContext::ObjectLiteralValue {
65            containing_function,
66            property_name,
67        } => {
68            if let Some(func) = containing_function {
69                return object_property_value_completions(func, property_name, user_symbols);
70            }
71            symbols_to_completions(user_symbols)
72        }
73        ArgumentContext::ObjectLiteralPropertyName {
74            containing_function,
75        } => {
76            if let Some(func) = containing_function {
77                return object_property_name_completions(func);
78            }
79            vec![]
80        }
81        ArgumentContext::General => symbols_to_completions(user_symbols),
82    }
83}
84
85/// Completions for object literal property values
86pub fn object_property_value_completions(
87    function: &str,
88    property_name: &str,
89    user_symbols: &[SymbolInfo],
90) -> Vec<CompletionItem> {
91    let meta = unified_metadata();
92    if let Some(func_info) = meta.get_function(function) {
93        // Find parameter with object_properties constraint
94        for param in &func_info.parameters {
95            if let Some(constraints) = &param.constraints {
96                if let Some(properties) = &constraints.object_properties {
97                    // Find the specific property constraint
98                    for prop_constraint in properties {
99                        if prop_constraint.name == property_name {
100                            if let Some(constraint) = &prop_constraint.constraint {
101                                // Check for annotation requirement
102                                if let Some(annotation) = &constraint.requires_annotation {
103                                    return symbols_with_annotation(annotation, user_symbols);
104                                }
105
106                                // Check for enum values
107                                if let Some(values) = &constraint.allowed_values {
108                                    return enum_value_completions(values);
109                                }
110                            }
111                        }
112                    }
113                }
114            }
115        }
116    }
117
118    // Default: show all symbols
119    symbols_to_completions(user_symbols)
120}
121
122/// Completions for object literal property names
123pub fn object_property_name_completions(function: &str) -> Vec<CompletionItem> {
124    let meta = unified_metadata();
125    if let Some(func_info) = meta.get_function(function) {
126        // Find parameter with object_properties constraint
127        for param in &func_info.parameters {
128            if let Some(constraints) = &param.constraints {
129                if let Some(properties) = &constraints.object_properties {
130                    return properties
131                        .iter()
132                        .map(|prop| {
133                            let required_marker = if prop.required { " (required)" } else { "" };
134
135                            CompletionItem {
136                                label: prop.name.clone(),
137                                kind: Some(CompletionItemKind::PROPERTY),
138                                detail: Some(format!("{}{}", prop.value_type, required_marker)),
139                                insert_text: Some(format!("{}: ${{1}}", prop.name)),
140                                insert_text_format: Some(InsertTextFormat::SNIPPET),
141                                ..Default::default()
142                            }
143                        })
144                        .collect();
145                }
146            }
147        }
148    }
149
150    vec![]
151}
152
153/// Keyword completions from metadata API
154pub fn keyword_completions() -> Vec<CompletionItem> {
155    LanguageMetadata::keywords()
156        .into_iter()
157        .filter(|kw| is_globally_suggested_keyword(&kw.keyword))
158        .map(|kw| CompletionItem {
159            label: kw.keyword,
160            kind: Some(CompletionItemKind::KEYWORD),
161            detail: Some(kw.description.clone()),
162            documentation: Some(Documentation::String(kw.description)),
163            insert_text_format: Some(InsertTextFormat::PLAIN_TEXT),
164            ..CompletionItem::default()
165        })
166        .collect()
167}
168
169fn is_globally_suggested_keyword(keyword: &str) -> bool {
170    !matches!(
171        keyword,
172        // Deprecated / legacy surface
173        "meta" | "pattern" | "function" | "import" | "export" | "stream"
174            // Context-only control keywords
175            | "break" | "continue" | "join" | "race" | "settle" | "any"
176            // Context-only syntax keywords
177            | "method" | "as" | "default"
178            // Removed/placeholder query keywords
179            | "find" | "scan" | "analyze" | "simulate" | "all" | "extend"
180            // Non-canonical textual operators
181            | "and" | "or" | "not" | "on"
182            // Reserved/legacy type-system surface
183            | "module" | "interface" | "this" | "when"
184    )
185}
186
187fn is_removed_toplevel_function(name: &str) -> bool {
188    matches!(
189        name,
190        "length"
191            | "keys"
192            | "values"
193            | "entries"
194            | "configure_data_source"
195            | "load"
196            | "rolling_mean"
197            | "rolling_sum"
198            | "rolling_std"
199            | "rolling_min"
200            | "rolling_max"
201    )
202}
203
204/// Built-in function completions from unified metadata API
205/// Includes: Rust builtins (proc-macro) + Shape stdlib + legacy builtins
206pub fn builtin_function_completions() -> Vec<CompletionItem> {
207    unified_metadata()
208        .all_functions()
209        .into_iter()
210        .filter(|f| !f.comptime_only && f.implemented && !is_removed_toplevel_function(&f.name))
211        .map(function_completion_item)
212        .collect()
213}
214
215/// Comptime-only builtin completions from unified metadata API.
216pub fn comptime_builtin_function_completions() -> Vec<CompletionItem> {
217    unified_metadata()
218        .all_functions()
219        .into_iter()
220        .filter(|f| f.comptime_only && f.implemented)
221        .map(function_completion_item)
222        .collect()
223}
224
225pub fn function_completion_item(func: &FunctionInfo) -> CompletionItem {
226    // Build snippet with parameter placeholders
227    let params_snippet: Vec<String> = func
228        .parameters
229        .iter()
230        .enumerate()
231        .map(|(i, p)| format!("${{{}:{}}}", i + 1, p.name))
232        .collect();
233    let snippet = format!("{}({})", func.name, params_snippet.join(", "));
234
235    // Build documentation
236    let mut doc = format!("**{}**\n\n{}\n\n", func.signature, func.description);
237    if !func.parameters.is_empty() {
238        doc.push_str("**Parameters:**\n");
239        for param in &func.parameters {
240            doc.push_str(&format!(
241                "- `{}`: {} - {}\n",
242                param.name, param.param_type, param.description
243            ));
244        }
245    }
246    if let Some(example) = &func.example {
247        doc.push_str(&format!("\n**Example:**\n```shape\n{}\n```", example));
248    }
249    if !func.implemented {
250        doc.push_str("\n\n**Status:** Not yet implemented.");
251    }
252
253    let detail = if func.implemented {
254        func.signature.clone()
255    } else {
256        format!("{} (unimplemented)", func.signature)
257    };
258
259    CompletionItem {
260        label: func.name.clone(),
261        kind: Some(CompletionItemKind::FUNCTION),
262        detail: Some(detail),
263        documentation: Some(Documentation::MarkupContent(MarkupContent {
264            kind: MarkupKind::Markdown,
265            value: doc,
266        })),
267        insert_text: Some(snippet),
268        insert_text_format: Some(InsertTextFormat::SNIPPET),
269        ..CompletionItem::default()
270    }
271}
272
273/// Provide completions for Content style method arguments.
274///
275/// When the user types `.fg(`, `.bg(`, or `.border(`, we suggest Color/Border enum values.
276fn content_method_arg_completions(function: &str, arg_index: usize) -> Option<Vec<CompletionItem>> {
277    // Extract the method name from "expr.method" format
278    let method = function.rsplit('.').next().unwrap_or(function);
279
280    match (method, arg_index) {
281        ("fg" | "bg", 0) => Some(color_completions()),
282        ("border", 0) => Some(border_completions()),
283        ("chart", 0) if function.contains("Content") => Some(chart_type_completions()),
284        _ => None,
285    }
286}
287
288fn color_completions() -> Vec<CompletionItem> {
289    let colors = [
290        ("Color.red", "Red terminal color"),
291        ("Color.green", "Green terminal color"),
292        ("Color.blue", "Blue terminal color"),
293        ("Color.yellow", "Yellow terminal color"),
294        ("Color.magenta", "Magenta terminal color"),
295        ("Color.cyan", "Cyan terminal color"),
296        ("Color.white", "White terminal color"),
297        ("Color.default", "Default terminal color"),
298    ];
299    let mut items: Vec<CompletionItem> = colors
300        .into_iter()
301        .map(|(label, doc)| CompletionItem {
302            label: label.to_string(),
303            kind: Some(CompletionItemKind::ENUM_MEMBER),
304            detail: Some("Color".to_string()),
305            documentation: Some(Documentation::String(doc.to_string())),
306            ..CompletionItem::default()
307        })
308        .collect();
309
310    items.push(CompletionItem {
311        label: "Color.rgb".to_string(),
312        kind: Some(CompletionItemKind::METHOD),
313        detail: Some("Color".to_string()),
314        documentation: Some(Documentation::String(
315            "Custom RGB color (0-255 per channel)".to_string(),
316        )),
317        insert_text: Some("Color.rgb(${1:r}, ${2:g}, ${3:b})".to_string()),
318        insert_text_format: Some(InsertTextFormat::SNIPPET),
319        ..CompletionItem::default()
320    });
321
322    items
323}
324
325fn border_completions() -> Vec<CompletionItem> {
326    [
327        ("Border.rounded", "Rounded corners (default)"),
328        ("Border.sharp", "Sharp 90-degree corners"),
329        ("Border.heavy", "Thick border lines"),
330        ("Border.double", "Double-line border"),
331        ("Border.minimal", "Minimal separator lines"),
332        ("Border.none", "No border"),
333    ]
334    .into_iter()
335    .map(|(label, doc)| CompletionItem {
336        label: label.to_string(),
337        kind: Some(CompletionItemKind::ENUM_MEMBER),
338        detail: Some("Border".to_string()),
339        documentation: Some(Documentation::String(doc.to_string())),
340        ..CompletionItem::default()
341    })
342    .collect()
343}
344
345fn chart_type_completions() -> Vec<CompletionItem> {
346    [
347        ("ChartType.line", "Line chart"),
348        ("ChartType.bar", "Bar chart"),
349        ("ChartType.scatter", "Scatter plot"),
350        ("ChartType.area", "Area chart"),
351        ("ChartType.candlestick", "Candlestick chart"),
352        ("ChartType.histogram", "Histogram"),
353    ]
354    .into_iter()
355    .map(|(label, doc)| CompletionItem {
356        label: label.to_string(),
357        kind: Some(CompletionItemKind::ENUM_MEMBER),
358        detail: Some("ChartType".to_string()),
359        documentation: Some(Documentation::String(doc.to_string())),
360        ..CompletionItem::default()
361    })
362    .collect()
363}