nushell on your web browser
nushell wasm terminal
at main 8.4 kB view raw
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 &current { 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}