endpoint 2.0 dysnomia.ptr.pet
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix short flag completion, dont show completion if command has no arguments

dawn 6771fa21 597269f7

+328 -127
+328 -127
src/completion.rs
··· 1 1 use futures::FutureExt; 2 2 use js_sys::Promise; 3 - use wasm_bindgen_futures::future_to_promise; 4 - use std::collections::HashMap; 5 3 use nu_protocol::{ENV_VARIABLE_ID, IN_VARIABLE_ID, NU_VARIABLE_ID, Value}; 4 + use std::collections::HashMap; 5 + use wasm_bindgen_futures::future_to_promise; 6 6 7 7 use super::*; 8 8 ··· 261 261 ))); 262 262 // Check if this is a variable or cell path first 263 263 let trimmed = trimmed_prefix.trim(); 264 - 264 + 265 265 if trimmed.starts_with('$') { 266 266 // Variable or cell path completion 267 267 if let Some(dot_pos) = trimmed[1..].find('.') { ··· 272 272 let (path_so_far, cell_prefix) = if parts.is_empty() { 273 273 (vec![], String::new()) 274 274 } else if after_var.ends_with('.') { 275 - (parts.iter().filter(|s| !s.is_empty()).map(|s| s.to_string()).collect(), String::new()) 275 + ( 276 + parts 277 + .iter() 278 + .filter(|s| !s.is_empty()) 279 + .map(|s| s.to_string()) 280 + .collect(), 281 + String::new(), 282 + ) 276 283 } else { 277 - let path: Vec<String> = parts[..parts.len().saturating_sub(1)].iter().map(|s| s.to_string()).collect(); 284 + let path: Vec<String> = parts[..parts.len().saturating_sub(1)] 285 + .iter() 286 + .map(|s| s.to_string()) 287 + .collect(); 278 288 let prefix = parts.last().map(|s| s.to_string()).unwrap_or_default(); 279 289 (path, prefix) 280 290 }; 281 - 291 + 282 292 let var_id = match var_name { 283 293 "env" => Some(ENV_VARIABLE_ID), 284 294 "nu" => Some(NU_VARIABLE_ID), 285 295 "in" => Some(IN_VARIABLE_ID), 286 - _ => working_set.find_variable(var_name.as_bytes()) 296 + _ => working_set.find_variable(var_name.as_bytes()), 287 297 }; 288 - 298 + 289 299 if let Some(var_id) = var_id { 290 300 let prefix_byte_len = cell_prefix.len(); 291 301 let cell_span_start = adjusted_span.end.saturating_sub(prefix_byte_len); ··· 329 339 } 330 340 } else if trimmed.starts_with('-') { 331 341 // Flag completion 332 - if let Some((cmd_name, _)) = find_command_and_arg_index(current_idx, local_span) { 342 + if let Some((cmd_name, _)) = find_command_and_arg_index(current_idx, local_span) 343 + { 333 344 web_sys::console::log_1(&JsValue::from_str(&format!( 334 345 "[completion] {}: Found command {:?} for flag completion", 335 346 shape_name, cmd_name ··· 380 391 381 392 // Helper function to evaluate a variable for completion 382 393 // Returns the Value of a variable if it can be evaluated 383 - let eval_variable_for_completion = |var_id: nu_protocol::VarId, working_set: &StateWorkingSet| -> Option<Value> { 394 + let eval_variable_for_completion = |var_id: nu_protocol::VarId, 395 + working_set: &StateWorkingSet| 396 + -> Option<Value> { 384 397 match var_id { 385 398 id if id == NU_VARIABLE_ID => { 386 399 // $nu - get from engine state constant ··· 433 446 } 434 447 } 435 448 }; 436 - 449 + 437 450 // Helper function to extract column/field names from a Value 438 451 let get_columns_from_value = |value: &Value| -> Vec<(String, Option<String>)> { 439 452 match value { 440 - Value::Record { val, .. } => { 441 - val.iter() 442 - .map(|(name, v)| (name.to_string(), Some(v.get_type().to_string()))) 443 - .collect() 444 - } 453 + Value::Record { val, .. } => val 454 + .iter() 455 + .map(|(name, v)| (name.to_string(), Some(v.get_type().to_string()))) 456 + .collect(), 445 457 Value::List { vals, .. } => { 446 458 // Get common columns from list of records 447 459 if let Some(first) = vals.first() { 448 460 if let Value::Record { val, .. } = first { 449 - return val.iter() 461 + return val 462 + .iter() 450 463 .map(|(name, v)| (name.to_string(), Some(v.get_type().to_string()))) 451 464 .collect(); 452 465 } ··· 456 469 _ => vec![], 457 470 } 458 471 }; 459 - 472 + 460 473 // Helper function to follow a cell path and get the value at that path 461 474 let follow_cell_path = |value: &Value, path: &[String]| -> Option<Value> { 462 475 let mut current = value.clone(); ··· 489 502 // We parse the input directly to find closures containing the cursor and extract their parameters 490 503 let extract_closure_params = |input: &str, cursor_pos: usize| -> Vec<String> { 491 504 let mut params = Vec::new(); 492 - 505 + 493 506 // Find all closures in the input by looking for {|...| patterns 494 507 // We need to find closures that contain the cursor position 495 508 let mut brace_stack: Vec<usize> = Vec::new(); // Stack of opening brace positions 496 509 let mut closures: Vec<(usize, usize, Vec<String>)> = Vec::new(); // (start, end, params) 497 - 510 + 498 511 let mut i = 0; 499 512 let chars: Vec<char> = input.chars().collect(); 500 - 513 + 501 514 while i < chars.len() { 502 515 if chars[i] == '{' { 503 516 brace_stack.push(i); ··· 508 521 // Find the parameter list 509 522 let param_start = start + 2; 510 523 let mut param_end = param_start; 511 - 524 + 512 525 // Find the closing | of the parameter list 513 526 while param_end < chars.len() && chars[param_end] != '|' { 514 527 param_end += 1; 515 528 } 516 - 529 + 517 530 if param_end < chars.len() { 518 531 // Extract parameter names 519 - let params_text: String = chars[param_start..param_end].iter().collect(); 532 + let params_text: String = 533 + chars[param_start..param_end].iter().collect(); 520 534 let param_names: Vec<String> = params_text 521 535 .split(',') 522 536 .map(|s| s.trim().to_string()) 523 537 .filter(|s| !s.is_empty()) 524 538 .collect(); 525 - 539 + 526 540 closures.push((start, i + 1, param_names)); 527 541 } 528 542 } ··· 530 544 } 531 545 i += 1; 532 546 } 533 - 547 + 534 548 // Find closures that contain the cursor position 535 549 // A closure contains the cursor if: start <= cursor_pos < end 536 550 for (start, end, param_names) in closures { ··· 542 556 params.extend(param_names); 543 557 } 544 558 } 545 - 559 + 546 560 params 547 561 }; 548 562 549 563 // Helper function to collect variables from working set 550 - let collect_variables = |working_set: &StateWorkingSet, input: &str, cursor_pos: usize| -> HashMap<String, nu_protocol::VarId> { 564 + let collect_variables = |working_set: &StateWorkingSet, 565 + input: &str, 566 + cursor_pos: usize| 567 + -> HashMap<String, nu_protocol::VarId> { 551 568 let mut variables = HashMap::new(); 552 - 569 + 553 570 // Add built-in variables 554 571 variables.insert("$nu".to_string(), NU_VARIABLE_ID); 555 572 variables.insert("$in".to_string(), IN_VARIABLE_ID); 556 573 variables.insert("$env".to_string(), ENV_VARIABLE_ID); 557 - 574 + 558 575 // Collect closure parameters at cursor position 559 576 // We don't need real var_ids for closure parameters since they're not evaluated yet 560 577 // We'll use a placeholder var_id (using IN_VARIABLE_ID as a safe placeholder) ··· 570 587 var_name 571 588 ))); 572 589 } 573 - 590 + 574 591 // Collect from working set delta scope 575 592 let mut removed_overlays = vec![]; 576 593 for scope_frame in working_set.delta.scope.iter().rev() { ··· 581 598 } 582 599 } 583 600 } 584 - 601 + 585 602 // Collect from permanent state scope 586 603 for overlay_frame in working_set 587 604 .permanent_state ··· 593 610 variables.insert(name, *var_id); 594 611 } 595 612 } 596 - 613 + 597 614 variables 598 615 }; 599 616 ··· 624 641 span: Span, 625 642 }, 626 643 CellPath { 627 - prefix: String, // the partial field name being typed (after the last dot) 628 - span: Span, // replacement span 644 + prefix: String, // the partial field name being typed (after the last dot) 645 + span: Span, // replacement span 629 646 var_id: nu_protocol::VarId, // variable ID for evaluation 630 - path_so_far: Vec<String>, // path members accessed before current one 647 + path_so_far: Vec<String>, // path members accessed before current one 631 648 }, 632 649 } 633 650 ··· 677 694 (full_prefix, Span::new(span_start, span_end)) 678 695 }; 679 696 697 + // Helper function to get command signature (needed for context determination) 698 + let get_command_signature = |cmd_name: &str| -> Option<nu_protocol::Signature> { 699 + engine_guard 700 + .find_decl(cmd_name.as_bytes(), &[]) 701 + .map(|id| engine_guard.get_decl(id).signature()) 702 + }; 703 + 680 704 // First, check if cursor is within a shape 681 705 for (idx, (span, shape)) in shapes.iter().enumerate() { 682 706 let local_span = to_local_span(*span); ··· 721 745 match shape { 722 746 // Special case: Check if we're completing a cell path where the Variable and field are in separate shapes 723 747 // e.g., `$a.na` where $a is a Variable shape and `.na` is a String shape 724 - _ if { 725 - idx > 0 && matches!(shape, FlatShape::String) 726 - } => { 748 + _ if { idx > 0 && matches!(shape, FlatShape::String) } => { 727 749 // Look at the previous shape to see if it's a Variable 728 750 let prev_shape = &shapes[idx - 1]; 729 751 let prev_local_span = to_local_span(prev_shape.0); 730 - 752 + 731 753 if let FlatShape::Variable(var_id) = prev_shape.1 { 732 754 // Check if the variable shape ends right where this shape starts (or very close) 733 755 // Allow for a small gap (like a dot) between shapes ··· 740 762 let (path_so_far, cell_prefix) = if parts.is_empty() { 741 763 (vec![], String::new()) 742 764 } else if trimmed_prefix.ends_with('.') { 743 - (parts.iter().filter(|s| !s.is_empty()).map(|s| s.to_string()).collect(), String::new()) 765 + ( 766 + parts 767 + .iter() 768 + .filter(|s| !s.is_empty()) 769 + .map(|s| s.to_string()) 770 + .collect(), 771 + String::new(), 772 + ) 744 773 } else { 745 - let path: Vec<String> = parts[..parts.len().saturating_sub(1)].iter().map(|s| s.to_string()).collect(); 746 - let prefix = parts.last().map(|s| s.to_string()).unwrap_or_default(); 774 + let path: Vec<String> = parts[..parts.len().saturating_sub(1)] 775 + .iter() 776 + .map(|s| s.to_string()) 777 + .collect(); 778 + let prefix = 779 + parts.last().map(|s| s.to_string()).unwrap_or_default(); 747 780 (path, prefix) 748 781 }; 749 - 782 + 750 783 let prefix_byte_len = cell_prefix.len(); 751 784 let cell_span_start = span.end.saturating_sub(prefix_byte_len); 752 785 web_sys::console::log_1(&JsValue::from_str(&format!( ··· 760 793 path_so_far, 761 794 }); 762 795 } else { 763 - // Gap between shapes, fall through to default handling 796 + // Gap between shapes, check if this is a flag 797 + let trimmed_prefix = prefix.trim(); 798 + if trimmed_prefix.starts_with('-') { 799 + // This looks like a flag - find the command 800 + if let Some((cmd_name, _)) = 801 + find_command_and_arg_index(idx, local_span) 802 + { 803 + context = Some(CompletionContext::Flag { 804 + prefix: trimmed_prefix.to_string(), 805 + span, 806 + command_name: cmd_name, 807 + }); 808 + } else { 809 + context = 810 + Some(CompletionContext::Argument { prefix, span }); 811 + } 812 + } else { 813 + context = Some(CompletionContext::Argument { prefix, span }); 814 + } 815 + } 816 + } else { 817 + // Previous shape is not a Variable, check if this is a flag 818 + let trimmed_prefix = prefix.trim(); 819 + if trimmed_prefix.starts_with('-') { 820 + // This looks like a flag - find the command 821 + if let Some((cmd_name, _)) = 822 + find_command_and_arg_index(idx, local_span) 823 + { 824 + context = Some(CompletionContext::Flag { 825 + prefix: trimmed_prefix.to_string(), 826 + span, 827 + command_name: cmd_name, 828 + }); 829 + } else { 830 + context = Some(CompletionContext::Argument { prefix, span }); 831 + } 832 + } else { 833 + // This is likely a regular string argument 764 834 context = Some(CompletionContext::Argument { prefix, span }); 765 835 } 766 - } else { 767 - // Previous shape is not a Variable, this is likely a regular string 768 - context = Some(CompletionContext::Argument { prefix, span }); 769 836 } 770 837 } 771 838 // Special case: Check if we're completing a cell path where the Variable and dot are in separate shapes ··· 773 840 _ if { 774 841 let trimmed_prefix = prefix.trim(); 775 842 trimmed_prefix.starts_with('.') && idx > 0 776 - } => { 843 + } => 844 + { 777 845 // Look at the previous shape to see if it's a Variable 778 846 let prev_shape = &shapes[idx - 1]; 779 847 let prev_local_span = to_local_span(prev_shape.0); 780 - 848 + 781 849 if let FlatShape::Variable(var_id) = prev_shape.1 { 782 850 // Check if the variable shape ends right where this shape starts 783 851 if prev_local_span.end == local_span.start { ··· 785 853 // Parse path members from the prefix (which is like ".field" or ".field.subfield") 786 854 let after_dot = &trimmed_prefix[1..]; // Remove leading dot 787 855 let parts: Vec<&str> = after_dot.split('.').collect(); 788 - let (path_so_far, cell_prefix) = if parts.is_empty() || (parts.len() == 1 && parts[0].is_empty()) { 856 + let (path_so_far, cell_prefix) = if parts.is_empty() 857 + || (parts.len() == 1 && parts[0].is_empty()) 858 + { 789 859 (vec![], String::new()) 790 860 } else if after_dot.ends_with('.') { 791 - (parts.iter().filter(|s| !s.is_empty()).map(|s| s.to_string()).collect(), String::new()) 861 + ( 862 + parts 863 + .iter() 864 + .filter(|s| !s.is_empty()) 865 + .map(|s| s.to_string()) 866 + .collect(), 867 + String::new(), 868 + ) 792 869 } else { 793 - let path: Vec<String> = parts[..parts.len().saturating_sub(1)].iter().map(|s| s.to_string()).collect(); 794 - let prefix = parts.last().map(|s| s.to_string()).unwrap_or_default(); 870 + let path: Vec<String> = parts[..parts.len().saturating_sub(1)] 871 + .iter() 872 + .map(|s| s.to_string()) 873 + .collect(); 874 + let prefix = 875 + parts.last().map(|s| s.to_string()).unwrap_or_default(); 795 876 (path, prefix) 796 877 }; 797 - 878 + 798 879 let prefix_byte_len = cell_prefix.len(); 799 880 let cell_span_start = span.end.saturating_sub(prefix_byte_len); 800 881 web_sys::console::log_1(&JsValue::from_str(&format!( ··· 820 901 // Check if this is a variable or cell path (starts with $) before treating as command 821 902 let trimmed_prefix = prefix.trim(); 822 903 trimmed_prefix.starts_with('$') 823 - } => { 904 + } => 905 + { 824 906 let trimmed_prefix = prefix.trim(); 825 907 // Check if this is a cell path (contains a dot after $) 826 908 if let Some(dot_pos) = trimmed_prefix[1..].find('.') { 827 909 // Cell path completion: $env.PWD, $nu.home-path, etc. 828 910 let var_name = &trimmed_prefix[1..dot_pos + 1]; // e.g., "env" 829 911 let after_var = &trimmed_prefix[dot_pos + 2..]; // e.g., "PWD" or "config.color" 830 - 912 + 831 913 // Parse path members and current prefix 832 914 let parts: Vec<&str> = after_var.split('.').collect(); 833 915 let (path_so_far, cell_prefix) = if parts.is_empty() { 834 916 (vec![], String::new()) 835 917 } else if after_var.ends_with('.') { 836 918 // Cursor is right after a dot, complete all fields 837 - (parts.iter().filter(|s| !s.is_empty()).map(|s| s.to_string()).collect(), String::new()) 919 + ( 920 + parts 921 + .iter() 922 + .filter(|s| !s.is_empty()) 923 + .map(|s| s.to_string()) 924 + .collect(), 925 + String::new(), 926 + ) 838 927 } else { 839 928 // Cursor is in the middle of typing a field name 840 - let path: Vec<String> = parts[..parts.len().saturating_sub(1)].iter().map(|s| s.to_string()).collect(); 841 - let prefix = parts.last().map(|s| s.to_string()).unwrap_or_default(); 929 + let path: Vec<String> = parts[..parts.len().saturating_sub(1)] 930 + .iter() 931 + .map(|s| s.to_string()) 932 + .collect(); 933 + let prefix = 934 + parts.last().map(|s| s.to_string()).unwrap_or_default(); 842 935 (path, prefix) 843 936 }; 844 - 937 + 845 938 // Find the variable ID 846 939 let var_id = match var_name { 847 940 "env" => Some(ENV_VARIABLE_ID), ··· 852 945 working_set.find_variable(var_name.as_bytes()) 853 946 } 854 947 }; 855 - 948 + 856 949 if let Some(var_id) = var_id { 857 950 // Calculate span for the cell path member being completed 858 951 let prefix_byte_len = cell_prefix.len(); ··· 914 1007 let (path_so_far, cell_prefix) = if parts.is_empty() { 915 1008 (vec![], String::new()) 916 1009 } else if after_var.ends_with('.') { 917 - (parts.iter().filter(|s| !s.is_empty()).map(|s| s.to_string()).collect(), String::new()) 1010 + ( 1011 + parts 1012 + .iter() 1013 + .filter(|s| !s.is_empty()) 1014 + .map(|s| s.to_string()) 1015 + .collect(), 1016 + String::new(), 1017 + ) 918 1018 } else { 919 - let path: Vec<String> = parts[..parts.len().saturating_sub(1)].iter().map(|s| s.to_string()).collect(); 920 - let prefix = parts.last().map(|s| s.to_string()).unwrap_or_default(); 1019 + let path: Vec<String> = parts[..parts.len().saturating_sub(1)] 1020 + .iter() 1021 + .map(|s| s.to_string()) 1022 + .collect(); 1023 + let prefix = 1024 + parts.last().map(|s| s.to_string()).unwrap_or_default(); 921 1025 (path, prefix) 922 1026 }; 923 - 1027 + 924 1028 let prefix_byte_len = cell_prefix.len(); 925 1029 let cell_span_start = span.end.saturating_sub(prefix_byte_len); 926 1030 context = Some(CompletionContext::CellPath { ··· 955 1059 let (path_so_far, cell_prefix) = if parts.is_empty() { 956 1060 (vec![], String::new()) 957 1061 } else if after_var.ends_with('.') { 958 - (parts.iter().filter(|s| !s.is_empty()).map(|s| s.to_string()).collect(), String::new()) 1062 + ( 1063 + parts 1064 + .iter() 1065 + .filter(|s| !s.is_empty()) 1066 + .map(|s| s.to_string()) 1067 + .collect(), 1068 + String::new(), 1069 + ) 959 1070 } else { 960 - let path: Vec<String> = parts[..parts.len().saturating_sub(1)].iter().map(|s| s.to_string()).collect(); 961 - let prefix = parts.last().map(|s| s.to_string()).unwrap_or_default(); 1071 + let path: Vec<String> = parts[..parts.len().saturating_sub(1)] 1072 + .iter() 1073 + .map(|s| s.to_string()) 1074 + .collect(); 1075 + let prefix = 1076 + parts.last().map(|s| s.to_string()).unwrap_or_default(); 962 1077 (path, prefix) 963 1078 }; 964 - 1079 + 965 1080 let var_id = match var_name { 966 1081 "env" => Some(ENV_VARIABLE_ID), 967 1082 "nu" => Some(NU_VARIABLE_ID), 968 1083 "in" => Some(IN_VARIABLE_ID), 969 - _ => working_set.find_variable(var_name.as_bytes()) 1084 + _ => working_set.find_variable(var_name.as_bytes()), 970 1085 }; 971 - 1086 + 972 1087 if let Some(var_id) = var_id { 973 1088 let prefix_byte_len = cell_prefix.len(); 974 1089 let cell_span_start = span.end.saturating_sub(prefix_byte_len); ··· 1054 1169 1055 1170 // Extract the command text 1056 1171 let cmd = safe_slice(local_span); 1057 - web_sys::console::log_1(&JsValue::from_str(&format!( 1058 - "[completion] Set Command context with prefix: {:?}", 1059 - cmd 1060 - ))); 1172 + let cmd_name = cmd.split_whitespace().next().unwrap_or(&cmd).trim(); 1061 1173 1062 - // We're after a command, complete with that command as prefix 1063 - context = Some(CompletionContext::Command { 1064 - prefix: cmd, 1065 - span: local_span, 1066 - }); 1174 + // Check if we're right after the command (only whitespace between command and cursor) 1175 + let text_after_command = if local_span.end < input.len() { 1176 + &input[local_span.end..byte_pos] 1177 + } else { 1178 + "" 1179 + }; 1180 + let is_right_after_command = text_after_command.trim().is_empty(); 1181 + 1182 + // If we're right after a command, check if it has positional arguments 1183 + if is_right_after_command { 1184 + if let Some(signature) = get_command_signature(cmd_name) { 1185 + // Check if command has any positional arguments 1186 + let has_positional_args = !signature.required_positional.is_empty() 1187 + || !signature.optional_positional.is_empty(); 1188 + 1189 + if has_positional_args { 1190 + // Count existing arguments before cursor 1191 + let mut arg_count = 0; 1192 + for (prev_span, prev_shape) in shapes.iter().rev() { 1193 + let prev_local_span = to_local_span(*prev_span); 1194 + if prev_local_span.end <= byte_pos 1195 + && prev_local_span.end > local_span.end 1196 + { 1197 + if !is_command_shape(prev_shape, prev_local_span) { 1198 + let arg_text = safe_slice(prev_local_span); 1199 + let trimmed_arg = arg_text.trim(); 1200 + // Don't count flags (starting with -) or empty arguments 1201 + if !trimmed_arg.is_empty() 1202 + && !trimmed_arg.starts_with('-') 1203 + { 1204 + arg_count += 1; 1205 + } 1206 + } 1207 + } 1208 + } 1209 + 1210 + web_sys::console::log_1(&JsValue::from_str(&format!( 1211 + "[completion] Right after command {:?}, setting CommandArgument context with arg_index: {}", 1212 + cmd_name, arg_count 1213 + ))); 1214 + 1215 + context = Some(CompletionContext::CommandArgument { 1216 + prefix: String::new(), 1217 + span: Span::new(byte_pos, byte_pos), 1218 + command_name: cmd_name.to_string(), 1219 + arg_index: arg_count, 1220 + }); 1221 + } else { 1222 + // No positional arguments, don't show any completions 1223 + web_sys::console::log_1(&JsValue::from_str(&format!( 1224 + "[completion] Command {:?} has no positional args, not showing completions", 1225 + cmd_name 1226 + ))); 1227 + // Leave context as None to show no completions 1228 + } 1229 + } else { 1230 + // Couldn't find signature, don't show completions 1231 + web_sys::console::log_1(&JsValue::from_str(&format!( 1232 + "[completion] Could not find signature for {:?}, not showing completions", 1233 + cmd_name 1234 + ))); 1235 + // Leave context as None to show no completions 1236 + } 1237 + } else { 1238 + // Not right after command, complete the command itself 1239 + web_sys::console::log_1(&JsValue::from_str(&format!( 1240 + "[completion] Set Command context with prefix: {:?}", 1241 + cmd 1242 + ))); 1243 + context = Some(CompletionContext::Command { 1244 + prefix: cmd, 1245 + span: local_span, 1246 + }); 1247 + } 1067 1248 } 1068 1249 } 1069 1250 break; ··· 1145 1326 let (path_so_far, cell_prefix) = if parts.is_empty() { 1146 1327 (vec![], String::new()) 1147 1328 } else if after_var.ends_with('.') { 1148 - (parts.iter().filter(|s| !s.is_empty()).map(|s| s.to_string()).collect(), String::new()) 1329 + ( 1330 + parts 1331 + .iter() 1332 + .filter(|s| !s.is_empty()) 1333 + .map(|s| s.to_string()) 1334 + .collect(), 1335 + String::new(), 1336 + ) 1149 1337 } else { 1150 - let path: Vec<String> = parts[..parts.len().saturating_sub(1)].iter().map(|s| s.to_string()).collect(); 1338 + let path: Vec<String> = parts[..parts.len().saturating_sub(1)] 1339 + .iter() 1340 + .map(|s| s.to_string()) 1341 + .collect(); 1151 1342 let prefix = parts.last().map(|s| s.to_string()).unwrap_or_default(); 1152 1343 (path, prefix) 1153 1344 }; 1154 - 1345 + 1155 1346 let var_id = match var_name { 1156 1347 "env" => Some(ENV_VARIABLE_ID), 1157 1348 "nu" => Some(NU_VARIABLE_ID), 1158 1349 "in" => Some(IN_VARIABLE_ID), 1159 - _ => working_set.find_variable(var_name.as_bytes()) 1350 + _ => working_set.find_variable(var_name.as_bytes()), 1160 1351 }; 1161 - 1352 + 1162 1353 if let Some(var_id) = var_id { 1163 1354 let prefix_byte_len = cell_prefix.len(); 1164 1355 let cell_span_start = byte_pos.saturating_sub(prefix_byte_len); ··· 1298 1489 Span::new(char_start, char_end) 1299 1490 }; 1300 1491 1301 - let get_command_signature = |cmd_name: &str| -> Option<nu_protocol::Signature> { 1302 - engine_guard 1303 - .find_decl(cmd_name.as_bytes(), &[]) 1304 - .map(|id| engine_guard.get_decl(id).signature()) 1305 - }; 1306 - 1307 1492 match context { 1308 1493 Some(CompletionContext::Command { prefix, span }) => { 1309 1494 web_sys::console::log_1(&JsValue::from_str(&format!( ··· 1414 1599 let long_name = format!("--{}", flag.long); 1415 1600 let short_name = flag.short.map(|c| format!("-{}", c)); 1416 1601 1417 - // Check if prefix matches long or short form 1418 - let matches_long = long_name.starts_with(&prefix) || prefix.is_empty(); 1419 - let matches_short = short_name 1420 - .as_ref() 1421 - .map(|s| s.starts_with(&prefix) || prefix.is_empty()) 1422 - .unwrap_or(false); 1602 + // Determine which flags to show based on prefix: 1603 + // - If prefix is empty or exactly "-", show all flags (both short and long) 1604 + // - If prefix starts with "--", only show long flags that match the prefix 1605 + // - If prefix starts with "-" (but not "--"), only show short flags that match the prefix 1606 + let show_all = prefix.is_empty() || prefix == "-"; 1423 1607 1424 - if matches_long { 1425 - suggestions.push(Suggestion { 1426 - name: long_name.clone(), 1608 + // Helper to create a flag suggestion 1609 + let create_flag_suggestion = |flag_name: String| -> Suggestion { 1610 + Suggestion { 1611 + name: flag_name.clone(), 1427 1612 description: Some(flag.desc.clone()), 1428 1613 is_command: false, 1429 1614 rendered: { 1430 1615 let flag_colored = 1431 - ansi_term::Color::Cyan.bold().paint(&long_name); 1616 + ansi_term::Color::Cyan.bold().paint(&flag_name); 1432 1617 format!("{flag_colored} {}", flag.desc) 1433 1618 }, 1434 1619 span_start: span.start, 1435 1620 span_end: span.end, 1436 - }); 1621 + } 1622 + }; 1623 + 1624 + // Add long flag if it matches 1625 + let should_show_long = if show_all { 1626 + true // Show all flags when prefix is "-" or empty 1627 + } else if prefix.starts_with("--") { 1628 + long_name.starts_with(&prefix) // Only show long flags matching prefix 1629 + } else { 1630 + false // Don't show long flags if prefix is short flag format 1631 + }; 1632 + 1633 + if should_show_long { 1634 + suggestions.push(create_flag_suggestion(long_name)); 1437 1635 flag_count += 1; 1438 1636 } 1439 1637 1440 - if matches_short { 1441 - if let Some(short) = short_name { 1442 - suggestions.push(Suggestion { 1443 - name: short.clone(), 1444 - description: Some(flag.desc.clone()), 1445 - is_command: false, 1446 - rendered: { 1447 - let flag_colored = 1448 - ansi_term::Color::Cyan.bold().paint(&short); 1449 - format!("{flag_colored} {}", flag.desc) 1450 - }, 1451 - span_start: span.start, 1452 - span_end: span.end, 1453 - }); 1638 + // Add short flag if it matches 1639 + if let Some(short) = &short_name { 1640 + let should_show_short = if show_all { 1641 + true // Show all flags when prefix is "-" or empty 1642 + } else if prefix.starts_with("-") && !prefix.starts_with("--") { 1643 + short.starts_with(&prefix) // Only show short flags matching prefix 1644 + } else { 1645 + false // Don't show short flags if prefix is long flag format 1646 + }; 1647 + 1648 + if should_show_short { 1649 + suggestions.push(create_flag_suggestion(short.clone())); 1454 1650 flag_count += 1; 1455 1651 } 1456 1652 } ··· 1672 1868 "[completion] Generating Variable suggestions with prefix: {:?}", 1673 1869 prefix 1674 1870 ))); 1675 - 1871 + 1676 1872 // Collect all available variables 1677 1873 let variables = collect_variables(&working_set, &input, byte_pos); 1678 1874 let span = to_char_span(span); 1679 1875 let mut var_count = 0; 1680 - 1876 + 1681 1877 for (var_name, var_id) in variables { 1682 1878 // Filter by prefix (variable name includes $, so we need to check after $) 1683 1879 if var_name.len() > 1 && var_name[1..].starts_with(&prefix) { 1684 1880 // Get variable type 1685 1881 let var_type = working_set.get_variable(var_id).ty.to_string(); 1686 - 1882 + 1687 1883 suggestions.push(Suggestion { 1688 1884 name: var_name.clone(), 1689 1885 description: Some(var_type.clone()), ··· 1698 1894 var_count += 1; 1699 1895 } 1700 1896 } 1701 - 1897 + 1702 1898 web_sys::console::log_1(&JsValue::from_str(&format!( 1703 1899 "[completion] Found {} variable suggestions", 1704 1900 var_count 1705 1901 ))); 1706 1902 } 1707 - Some(CompletionContext::CellPath { prefix, span, var_id, path_so_far }) => { 1903 + Some(CompletionContext::CellPath { 1904 + prefix, 1905 + span, 1906 + var_id, 1907 + path_so_far, 1908 + }) => { 1708 1909 web_sys::console::log_1(&JsValue::from_str(&format!( 1709 1910 "[completion] Generating CellPath suggestions with prefix: {:?}, path: {:?}", 1710 1911 prefix, path_so_far 1711 1912 ))); 1712 - 1913 + 1713 1914 // Evaluate the variable to get its value 1714 1915 if let Some(var_value) = eval_variable_for_completion(var_id, &working_set) { 1715 1916 // Follow the path to get the value at the current level ··· 1718 1919 } else { 1719 1920 follow_cell_path(&var_value, &path_so_far).unwrap_or(var_value) 1720 1921 }; 1721 - 1922 + 1722 1923 // Get columns/fields from the current value 1723 1924 let columns = get_columns_from_value(&current_value); 1724 1925 let span = to_char_span(span); 1725 1926 let mut field_count = 0; 1726 - 1927 + 1727 1928 for (col_name, col_type) in columns { 1728 1929 // Filter by prefix 1729 1930 if col_name.starts_with(&prefix) { ··· 1742 1943 field_count += 1; 1743 1944 } 1744 1945 } 1745 - 1946 + 1746 1947 web_sys::console::log_1(&JsValue::from_str(&format!( 1747 1948 "[completion] Found {} cell path suggestions", 1748 1949 field_count ··· 1754 1955 "[completion] Could not evaluate variable {:?} for cell path completion (runtime variable)", 1755 1956 var_id 1756 1957 ))); 1757 - 1958 + 1758 1959 // Try to get type information to provide better feedback 1759 1960 if let Ok(var_info) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { 1760 1961 working_set.get_variable(var_id)