1use crate::console_log;
2use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
3use nu_protocol::{ENV_VARIABLE_ID, IN_VARIABLE_ID, NU_VARIABLE_ID, Span, Value};
4use std::collections::HashMap;
5
6pub fn eval_variable_for_completion(
7 var_id: nu_protocol::VarId,
8 working_set: &StateWorkingSet,
9 engine_guard: &EngineState,
10 stack_guard: &Stack,
11) -> Option<Value> {
12 match var_id {
13 id if id == NU_VARIABLE_ID => {
14 // $nu - get from engine state constant
15 engine_guard.get_constant(id).cloned()
16 }
17 id if id == ENV_VARIABLE_ID => {
18 // $env - build from environment variables in engine state
19 // EnvVars is HashMap<String, HashMap<String, Value>> (overlay -> vars)
20 let mut pairs: Vec<(String, Value)> = Vec::new();
21 for overlay_env in engine_guard.env_vars.values() {
22 for (name, value) in overlay_env.iter() {
23 pairs.push((name.clone(), value.clone()));
24 }
25 }
26 pairs.sort_by(|a, b| a.0.cmp(&b.0));
27 // Deduplicate by name (later overlays override earlier ones)
28 pairs.dedup_by(|a, b| a.0 == b.0);
29 Some(Value::record(pairs.into_iter().collect(), Span::unknown()))
30 }
31 id if id == IN_VARIABLE_ID => {
32 // $in - typically not available at completion time
33 None
34 }
35 _ => {
36 // User-defined variable - try to get const value first
37 let var_info = working_set.get_variable(var_id);
38 if let Some(const_val) = &var_info.const_val {
39 Some(const_val.clone())
40 } else {
41 // Variable doesn't have a const value (runtime value)
42 // Try to get the value from the stack (runtime storage)
43 match stack_guard.get_var(var_id, Span::unknown()) {
44 Ok(value) => {
45 console_log!("[completion] Found variable {var_id:?} value in stack");
46 Some(value)
47 }
48 Err(_) => {
49 // Variable not in stack either
50 console_log!(
51 "[completion] Variable {var_id:?} has no const value and not in stack, type: {ty:?}",
52 ty = var_info.ty
53 );
54 None
55 }
56 }
57 }
58 }
59 }
60}
61
62pub fn get_columns_from_value(value: &Value) -> Vec<(String, Option<String>)> {
63 match value {
64 Value::Record { val, .. } => val
65 .iter()
66 .map(|(name, v)| (name.to_string(), Some(v.get_type().to_string())))
67 .collect(),
68 Value::List { vals, .. } => {
69 // Get common columns from list of records
70 if let Some(first) = vals.first() {
71 if let Value::Record { val, .. } = first {
72 return val
73 .iter()
74 .map(|(name, v)| (name.to_string(), Some(v.get_type().to_string())))
75 .collect();
76 }
77 }
78 vec![]
79 }
80 _ => vec![],
81 }
82}
83
84pub fn follow_cell_path(value: &Value, path: &[&str]) -> Option<Value> {
85 let mut current = value.clone();
86 for member in path {
87 match ¤t {
88 Value::Record { val, .. } => {
89 current = val.get(member)?.clone();
90 }
91 Value::List { vals, .. } => {
92 // Try to parse as index or get from first record
93 if let Ok(idx) = member.parse::<usize>() {
94 current = vals.get(idx)?.clone();
95 } else if let Some(first) = vals.first() {
96 if let Value::Record { val, .. } = first {
97 current = val.get(member)?.clone();
98 } else {
99 return None;
100 }
101 } else {
102 return None;
103 }
104 }
105 _ => return None,
106 }
107 }
108 Some(current)
109}
110
111pub fn extract_closure_params(input: &str, cursor_pos: usize) -> Vec<String> {
112 let mut params = Vec::new();
113
114 // Find all closures in the input by looking for {|...| patterns
115 // We need to find closures that contain the cursor position
116 let mut brace_stack: Vec<usize> = Vec::new(); // Stack of opening brace positions
117 let mut closures: Vec<(usize, usize, Vec<String>)> = Vec::new(); // (start, end, params)
118
119 let mut i = 0;
120 let chars: Vec<char> = input.chars().collect();
121
122 while i < chars.len() {
123 if chars[i] == '{' {
124 brace_stack.push(i);
125 } else if chars[i] == '}' {
126 if let Some(start) = brace_stack.pop() {
127 // Check if this is a closure with parameters: {|param| ...}
128 if start + 1 < chars.len() && chars[start + 1] == '|' {
129 // Find the parameter list
130 let param_start = start + 2;
131 let mut param_end = param_start;
132
133 // Find the closing | of the parameter list
134 while param_end < chars.len() && chars[param_end] != '|' {
135 param_end += 1;
136 }
137
138 if param_end < chars.len() {
139 // Extract parameter names
140 let params_text: String = chars[param_start..param_end].iter().collect();
141 let param_names: Vec<String> = params_text
142 .split(',')
143 .map(|s| s.trim().to_string())
144 .filter(|s| !s.is_empty())
145 .collect();
146
147 closures.push((start, i + 1, param_names));
148 }
149 }
150 }
151 }
152 i += 1;
153 }
154
155 // Find closures that contain the cursor position
156 // A closure contains the cursor if: start <= cursor_pos < end
157 for (start, end, param_names) in closures {
158 if start <= cursor_pos && cursor_pos < end {
159 console_log!(
160 "[completion] Found closure at [{start}, {end}) containing cursor {cursor_pos}, params: {param_names:?}"
161 );
162 params.extend(param_names);
163 }
164 }
165
166 params
167}
168
169pub fn collect_variables(
170 working_set: &StateWorkingSet,
171 input: &str,
172 cursor_pos: usize,
173) -> HashMap<String, nu_protocol::VarId> {
174 let mut variables = HashMap::new();
175
176 // Add built-in variables
177 variables.insert("$nu".to_string(), NU_VARIABLE_ID);
178 variables.insert("$in".to_string(), IN_VARIABLE_ID);
179 variables.insert("$env".to_string(), ENV_VARIABLE_ID);
180
181 // Collect closure parameters at cursor position
182 // We don't need real var_ids for closure parameters since they're not evaluated yet
183 // We'll use a placeholder var_id (using IN_VARIABLE_ID as a safe placeholder)
184 // The actual var_id lookup will happen when the variable is used
185 let closure_params = extract_closure_params(input, cursor_pos);
186 for param_name in closure_params {
187 let var_name = format!("${}", param_name);
188 // Use IN_VARIABLE_ID as placeholder - it's safe since we're just using it for the name
189 // The completion logic only needs the name, not the actual var_id
190 variables.insert(var_name.clone(), IN_VARIABLE_ID);
191 console_log!("[completion] Added closure parameter: {var_name:?}");
192 }
193
194 // Collect from working set delta scope
195 let mut removed_overlays = vec![];
196 for scope_frame in working_set.delta.scope.iter().rev() {
197 for overlay_frame in scope_frame.active_overlays(&mut removed_overlays).rev() {
198 for (name, var_id) in &overlay_frame.vars {
199 let name = String::from_utf8_lossy(name).to_string();
200 variables.insert(name, *var_id);
201 }
202 }
203 }
204
205 // Collect from permanent state scope
206 for overlay_frame in working_set
207 .permanent_state
208 .active_overlays(&removed_overlays)
209 .rev()
210 {
211 for (name, var_id) in &overlay_frame.vars {
212 let name = String::from_utf8_lossy(name).to_string();
213 variables.insert(name, *var_id);
214 }
215 }
216
217 variables
218}