nushell on your web browser
nushell wasm terminal

fix completions being not unique and duplicated

ptr.pet 373a827a fb6d141e

verified
Changed files
+253 -143
src
+3 -2
src/cmd/ls.rs
··· 98 } else { 99 // Non-glob path: check if it's a directory and list its contents 100 let normalized_path = path_str.trim_start_matches('/').trim_end_matches('/'); 101 - let target_path = base_path.join(normalized_path) 102 .map_err(to_shell_err(call.arguments_span()))?; 103 - 104 let metadata = target_path.metadata().map_err(to_shell_err(span))?; 105 match metadata.file_type { 106 vfs::VfsFileType::Directory => {
··· 98 } else { 99 // Non-glob path: check if it's a directory and list its contents 100 let normalized_path = path_str.trim_start_matches('/').trim_end_matches('/'); 101 + let target_path = base_path 102 + .join(normalized_path) 103 .map_err(to_shell_err(call.arguments_span()))?; 104 + 105 let metadata = target_path.metadata().map_err(to_shell_err(span))?; 106 match metadata.file_type { 107 vfs::VfsFileType::Directory => {
+147 -84
src/completion/context.rs
··· 1 use crate::completion::helpers::*; 2 - use crate::completion::types::CompletionContext; 3 use crate::console_log; 4 use nu_parser::FlatShape; 5 use nu_protocol::engine::{EngineState, StateWorkingSet}; ··· 122 if let Some((cmd_name, _)) = 123 find_command_and_arg_index(input, shapes, idx, local_span, global_offset) 124 { 125 - CompletionContext::Flag { 126 prefix: trimmed_prefix.to_string(), 127 span, 128 - command_name: cmd_name, 129 } 130 } else { 131 - CompletionContext::Argument { 132 prefix: prefix.to_string(), 133 span, 134 } ··· 138 if let Some((cmd_name, arg_index)) = 139 find_command_and_arg_index(input, shapes, idx, local_span, global_offset) 140 { 141 - CompletionContext::CommandArgument { 142 prefix: trimmed_prefix.to_string(), 143 span, 144 - command_name: cmd_name, 145 - arg_index, 146 } 147 } else { 148 - CompletionContext::Argument { 149 prefix: prefix.to_string(), 150 span, 151 } ··· 227 console_log!( 228 "[completion] {shape_name} is empty, showing subcommands of {cmd_name:?}" 229 ); 230 - Some(CompletionContext::Command { 231 prefix: String::new(), 232 span: adjusted_span, 233 - parent_command: Some(cmd_name), 234 }) 235 } else { 236 // Truly empty - show all commands 237 console_log!("[completion] {shape_name} is empty, setting Command context"); 238 - Some(CompletionContext::Command { 239 prefix: String::new(), 240 span: adjusted_span, 241 - parent_command: None, 242 }) 243 } 244 } else if let Some(last_sep_pos) = last_sep_pos_in_prefix { ··· 247 console_log!( 248 "[completion] {shape_name} has separator at {last_sep_pos}, after_sep={after_sep:?}, setting Command context" 249 ); 250 - Some(CompletionContext::Command { 251 prefix: after_sep.to_string(), 252 span: Span::new(span.start + last_sep_pos, span.end), 253 - parent_command: None, 254 }) 255 } else { 256 console_log!( ··· 270 console_log!( 271 "[completion] {shape_name}: Setting CellPath context with var {var_name:?}, prefix {cell_prefix:?}" 272 ); 273 - Some(CompletionContext::CellPath { 274 prefix: cell_prefix.to_string(), 275 span: Span::new(cell_span_start, adjusted_span.end), 276 - var_id, 277 - path_so_far: path_so_far.iter().map(|s| s.to_string()).collect(), 278 }) 279 } else { 280 // Unknown variable, fall back to variable completion ··· 282 console_log!( 283 "[completion] {shape_name}: Unknown var, setting Variable context with prefix {var_prefix:?}" 284 ); 285 - Some(CompletionContext::Variable { 286 prefix: var_prefix, 287 span: adjusted_span, 288 }) ··· 297 console_log!( 298 "[completion] {shape_name}: Setting Variable context with prefix {var_prefix:?}" 299 ); 300 - Some(CompletionContext::Variable { 301 prefix: var_prefix, 302 span: adjusted_span, 303 }) ··· 314 console_log!( 315 "[completion] {shape_name}: Found command {cmd_name:?} for flag completion" 316 ); 317 - Some(CompletionContext::Flag { 318 prefix: trimmed.to_string(), 319 span: adjusted_span, 320 - command_name: cmd_name, 321 }) 322 } else { 323 - Some(CompletionContext::Argument { 324 prefix: trimmed_prefix.to_string(), 325 span: adjusted_span, 326 }) ··· 337 console_log!( 338 "[completion] {shape_name}: Found command {cmd_name:?} with arg_index {arg_index} for argument completion" 339 ); 340 - Some(CompletionContext::CommandArgument { 341 prefix: trimmed.to_string(), 342 span: adjusted_span, 343 - command_name: cmd_name, 344 - arg_index, 345 }) 346 } else { 347 // No command found, treat as regular argument 348 console_log!( 349 "[completion] {shape_name}: No command found, using Argument context" 350 ); 351 - Some(CompletionContext::Argument { 352 prefix: trimmed_prefix.to_string(), 353 span: adjusted_span, 354 }) ··· 392 console_log!( 393 "[completion] Detected cell path from Variable+String shapes, var_id={var_id:?}, prefix={cell_prefix:?}, path={path_so_far:?}" 394 ); 395 - Some(CompletionContext::CellPath { 396 prefix: cell_prefix.to_string(), 397 span: Span::new(cell_span_start, span.end), 398 - var_id, 399 - path_so_far: path_so_far.iter().map(|s| s.to_string()).collect(), 400 }) 401 } else { 402 // Gap between shapes, use helper to determine context ··· 434 global_offset: usize, 435 ) -> Option<CompletionContext> { 436 if idx == 0 { 437 - return Some(CompletionContext::Argument { 438 prefix: prefix.to_string(), 439 span, 440 }); ··· 460 console_log!( 461 "[completion] Detected cell path from adjacent Variable shape, var_id={var_id:?}, prefix={cell_prefix:?}" 462 ); 463 - Some(CompletionContext::CellPath { 464 prefix: cell_prefix.to_string(), 465 span: Span::new(cell_span_start, span.end), 466 - var_id, 467 - path_so_far: path_so_far.iter().map(|s| s.to_string()).collect(), 468 }) 469 } else { 470 // Gap between shapes, fall through to default handling 471 - Some(CompletionContext::Argument { 472 prefix: prefix.to_string(), 473 span, 474 }) 475 } 476 } else { 477 // Previous shape is not a Variable, this is likely a file path starting with . 478 - Some(CompletionContext::Argument { 479 prefix: prefix.to_string(), 480 span, 481 }) ··· 518 if trimmed_prefix == "{" { 519 // We're right after '{' - command context 520 if let Some((_, adjusted_span, _)) = handle_block_prefix(&prefix, span) { 521 - return Some(CompletionContext::Command { 522 prefix: String::new(), 523 span: adjusted_span, 524 - parent_command: None, 525 }); 526 } 527 } else { ··· 577 // Calculate span for the cell path member being completed 578 let prefix_byte_len = cell_prefix.len(); 579 let cell_span_start = span.end.saturating_sub(prefix_byte_len); 580 - return Some(CompletionContext::CellPath { 581 prefix: cell_prefix.to_string(), 582 span: Span::new(cell_span_start, span.end), 583 - var_id, 584 - path_so_far: path_so_far 585 - .iter() 586 - .map(|s| s.to_string()) 587 - .collect(), 588 }); 589 } else { 590 // Unknown variable, fall back to variable completion 591 let var_prefix = trimmed_prefix[1..].to_string(); 592 - return Some(CompletionContext::Variable { 593 prefix: var_prefix, 594 span, 595 }); ··· 601 } else { 602 String::new() 603 }; 604 - return Some(CompletionContext::Variable { 605 prefix: var_prefix, 606 span, 607 }); ··· 610 _ if is_command_shape(input, shape, local_span) => { 611 let (full_prefix, full_span) = 612 build_command_prefix(input, shapes, idx, span, &prefix, global_offset); 613 - return Some(CompletionContext::Command { 614 prefix: full_prefix, 615 span: full_span, 616 - parent_command: None, 617 }); 618 } 619 FlatShape::Block | FlatShape::Closure => { ··· 641 { 642 let prefix_byte_len = cell_prefix.len(); 643 let cell_span_start = span.end.saturating_sub(prefix_byte_len); 644 - return Some(CompletionContext::CellPath { 645 prefix: cell_prefix.to_string(), 646 span: Span::new(cell_span_start, span.end), 647 - var_id: *var_id, 648 - path_so_far: path_so_far 649 - .iter() 650 - .map(|s| s.to_string()) 651 - .collect(), 652 }); 653 } else { 654 // Simple variable completion 655 let var_prefix = trimmed_prefix[1..].to_string(); 656 - return Some(CompletionContext::Variable { 657 prefix: var_prefix, 658 span, 659 }); 660 } 661 } else { 662 // Fallback to argument context if no $ found 663 - return Some(CompletionContext::Argument { 664 prefix: prefix.to_string(), 665 span, 666 }); ··· 678 if let Some(var_id) = var_id { 679 let prefix_byte_len = cell_prefix.len(); 680 let cell_span_start = span.end.saturating_sub(prefix_byte_len); 681 - return Some(CompletionContext::CellPath { 682 prefix: cell_prefix.to_string(), 683 span: Span::new(cell_span_start, span.end), 684 - var_id, 685 - path_so_far: path_so_far 686 - .iter() 687 - .map(|s| s.to_string()) 688 - .collect(), 689 }); 690 } else { 691 let var_prefix = trimmed_prefix[1..].to_string(); 692 - return Some(CompletionContext::Variable { 693 prefix: var_prefix, 694 span, 695 }); ··· 701 } else { 702 String::new() 703 }; 704 - return Some(CompletionContext::Variable { 705 prefix: var_prefix, 706 span, 707 }); ··· 810 "[completion] Right after command {cmd_name:?}, setting CommandArgument context with arg_index: {arg_count}" 811 ); 812 813 - context.push(CompletionContext::CommandArgument { 814 prefix: String::new(), 815 span: Span::new(byte_pos, byte_pos), 816 - command_name: cmd_name.clone(), 817 - arg_index: arg_count, 818 }); 819 } 820 } ··· 830 console_log!( 831 "[completion] Command {cmd_name:?} has no positional args, showing subcommands" 832 ); 833 - context.push(CompletionContext::Command { 834 prefix: String::new(), 835 span: Span::new(byte_pos, byte_pos), 836 - parent_command: Some(cmd_first_word), 837 }); 838 } 839 // reverse to put subcommands in the beginning ··· 842 } else { 843 // Not right after command, complete the command itself 844 console_log!("[completion] Set Command context with prefix: {cmd:?}"); 845 - return vec![CompletionContext::Command { 846 prefix: cmd.to_string(), 847 span: local_span, 848 - parent_command: None, 849 }]; 850 } 851 } ··· 902 console_log!("[completion] last_word_start={last_word_start}, last_word={last_word:?}"); 903 904 if is_cmd_context { 905 - vec![CompletionContext::Command { 906 prefix: last_word.to_string(), 907 span: Span::new(last_word_start, byte_pos), 908 - parent_command: None, 909 }] 910 } else { 911 // Check if this is a variable or cell path (starts with $) ··· 918 if let Some(var_id) = var_id { 919 let prefix_byte_len = cell_prefix.len(); 920 let cell_span_start = byte_pos.saturating_sub(prefix_byte_len); 921 - vec![CompletionContext::CellPath { 922 prefix: cell_prefix.to_string(), 923 span: Span::new(cell_span_start, byte_pos), 924 - var_id, 925 - path_so_far: path_so_far.iter().map(|s| s.to_string()).collect(), 926 }] 927 } else { 928 let var_prefix = trimmed_word[1..].to_string(); 929 - vec![CompletionContext::Variable { 930 prefix: var_prefix, 931 span: Span::new(last_word_start, byte_pos), 932 }] ··· 934 } else { 935 // Simple variable completion 936 let var_prefix = trimmed_word[1..].to_string(); 937 - vec![CompletionContext::Variable { 938 prefix: var_prefix, 939 span: Span::new(last_word_start, byte_pos), 940 }] ··· 952 } 953 } 954 if let Some(cmd_name) = found_cmd { 955 - vec![CompletionContext::Flag { 956 prefix: trimmed_word.to_string(), 957 span: Span::new(last_word_start, byte_pos), 958 - command_name: cmd_name, 959 }] 960 } else { 961 - vec![CompletionContext::Argument { 962 prefix: last_word.to_string(), 963 span: Span::new(last_word_start, byte_pos), 964 }] ··· 985 } 986 } 987 if let Some(cmd_name) = found_cmd { 988 - vec![CompletionContext::CommandArgument { 989 prefix: trimmed_word.to_string(), 990 span: Span::new(last_word_start, byte_pos), 991 - command_name: cmd_name, 992 - arg_index: arg_count, 993 }] 994 } else { 995 - vec![CompletionContext::Argument { 996 prefix: last_word.to_string(), 997 span: Span::new(last_word_start, byte_pos), 998 }]
··· 1 use crate::completion::helpers::*; 2 + use crate::completion::types::{CompletionContext, CompletionKind}; 3 use crate::console_log; 4 use nu_parser::FlatShape; 5 use nu_protocol::engine::{EngineState, StateWorkingSet}; ··· 122 if let Some((cmd_name, _)) = 123 find_command_and_arg_index(input, shapes, idx, local_span, global_offset) 124 { 125 + CompletionContext { 126 + kind: CompletionKind::Flag { 127 + command_name: cmd_name, 128 + }, 129 prefix: trimmed_prefix.to_string(), 130 span, 131 } 132 } else { 133 + CompletionContext { 134 + kind: CompletionKind::Argument, 135 prefix: prefix.to_string(), 136 span, 137 } ··· 141 if let Some((cmd_name, arg_index)) = 142 find_command_and_arg_index(input, shapes, idx, local_span, global_offset) 143 { 144 + CompletionContext { 145 + kind: CompletionKind::CommandArgument { 146 + command_name: cmd_name, 147 + arg_index, 148 + }, 149 prefix: trimmed_prefix.to_string(), 150 span, 151 } 152 } else { 153 + CompletionContext { 154 + kind: CompletionKind::Argument, 155 prefix: prefix.to_string(), 156 span, 157 } ··· 233 console_log!( 234 "[completion] {shape_name} is empty, showing subcommands of {cmd_name:?}" 235 ); 236 + Some(CompletionContext { 237 + kind: CompletionKind::Command { 238 + parent_command: Some(cmd_name), 239 + }, 240 prefix: String::new(), 241 span: adjusted_span, 242 }) 243 } else { 244 // Truly empty - show all commands 245 console_log!("[completion] {shape_name} is empty, setting Command context"); 246 + Some(CompletionContext { 247 + kind: CompletionKind::Command { 248 + parent_command: None, 249 + }, 250 prefix: String::new(), 251 span: adjusted_span, 252 }) 253 } 254 } else if let Some(last_sep_pos) = last_sep_pos_in_prefix { ··· 257 console_log!( 258 "[completion] {shape_name} has separator at {last_sep_pos}, after_sep={after_sep:?}, setting Command context" 259 ); 260 + Some(CompletionContext { 261 + kind: CompletionKind::Command { 262 + parent_command: None, 263 + }, 264 prefix: after_sep.to_string(), 265 span: Span::new(span.start + last_sep_pos, span.end), 266 }) 267 } else { 268 console_log!( ··· 282 console_log!( 283 "[completion] {shape_name}: Setting CellPath context with var {var_name:?}, prefix {cell_prefix:?}" 284 ); 285 + Some(CompletionContext { 286 + kind: CompletionKind::CellPath { 287 + var_id, 288 + path_so_far: path_so_far.iter().map(|s| s.to_string()).collect(), 289 + }, 290 prefix: cell_prefix.to_string(), 291 span: Span::new(cell_span_start, adjusted_span.end), 292 }) 293 } else { 294 // Unknown variable, fall back to variable completion ··· 296 console_log!( 297 "[completion] {shape_name}: Unknown var, setting Variable context with prefix {var_prefix:?}" 298 ); 299 + Some(CompletionContext { 300 + kind: CompletionKind::Variable, 301 prefix: var_prefix, 302 span: adjusted_span, 303 }) ··· 312 console_log!( 313 "[completion] {shape_name}: Setting Variable context with prefix {var_prefix:?}" 314 ); 315 + Some(CompletionContext { 316 + kind: CompletionKind::Variable, 317 prefix: var_prefix, 318 span: adjusted_span, 319 }) ··· 330 console_log!( 331 "[completion] {shape_name}: Found command {cmd_name:?} for flag completion" 332 ); 333 + Some(CompletionContext { 334 + kind: CompletionKind::Flag { 335 + command_name: cmd_name, 336 + }, 337 prefix: trimmed.to_string(), 338 span: adjusted_span, 339 }) 340 } else { 341 + Some(CompletionContext { 342 + kind: CompletionKind::Argument, 343 prefix: trimmed_prefix.to_string(), 344 span: adjusted_span, 345 }) ··· 356 console_log!( 357 "[completion] {shape_name}: Found command {cmd_name:?} with arg_index {arg_index} for argument completion" 358 ); 359 + Some(CompletionContext { 360 + kind: CompletionKind::CommandArgument { 361 + command_name: cmd_name, 362 + arg_index, 363 + }, 364 prefix: trimmed.to_string(), 365 span: adjusted_span, 366 }) 367 } else { 368 // No command found, treat as regular argument 369 console_log!( 370 "[completion] {shape_name}: No command found, using Argument context" 371 ); 372 + Some(CompletionContext { 373 + kind: CompletionKind::Argument, 374 prefix: trimmed_prefix.to_string(), 375 span: adjusted_span, 376 }) ··· 414 console_log!( 415 "[completion] Detected cell path from Variable+String shapes, var_id={var_id:?}, prefix={cell_prefix:?}, path={path_so_far:?}" 416 ); 417 + Some(CompletionContext { 418 + kind: CompletionKind::CellPath { 419 + var_id, 420 + path_so_far: path_so_far.iter().map(|s| s.to_string()).collect(), 421 + }, 422 prefix: cell_prefix.to_string(), 423 span: Span::new(cell_span_start, span.end), 424 }) 425 } else { 426 // Gap between shapes, use helper to determine context ··· 458 global_offset: usize, 459 ) -> Option<CompletionContext> { 460 if idx == 0 { 461 + return Some(CompletionContext { 462 + kind: CompletionKind::Argument, 463 prefix: prefix.to_string(), 464 span, 465 }); ··· 485 console_log!( 486 "[completion] Detected cell path from adjacent Variable shape, var_id={var_id:?}, prefix={cell_prefix:?}" 487 ); 488 + Some(CompletionContext { 489 + kind: CompletionKind::CellPath { 490 + var_id, 491 + path_so_far: path_so_far.iter().map(|s| s.to_string()).collect(), 492 + }, 493 prefix: cell_prefix.to_string(), 494 span: Span::new(cell_span_start, span.end), 495 }) 496 } else { 497 // Gap between shapes, fall through to default handling 498 + Some(CompletionContext { 499 + kind: CompletionKind::Argument, 500 prefix: prefix.to_string(), 501 span, 502 }) 503 } 504 } else { 505 // Previous shape is not a Variable, this is likely a file path starting with . 506 + Some(CompletionContext { 507 + kind: CompletionKind::Argument, 508 prefix: prefix.to_string(), 509 span, 510 }) ··· 547 if trimmed_prefix == "{" { 548 // We're right after '{' - command context 549 if let Some((_, adjusted_span, _)) = handle_block_prefix(&prefix, span) { 550 + return Some(CompletionContext { 551 + kind: CompletionKind::Command { 552 + parent_command: None, 553 + }, 554 prefix: String::new(), 555 span: adjusted_span, 556 }); 557 } 558 } else { ··· 608 // Calculate span for the cell path member being completed 609 let prefix_byte_len = cell_prefix.len(); 610 let cell_span_start = span.end.saturating_sub(prefix_byte_len); 611 + return Some(CompletionContext { 612 + kind: CompletionKind::CellPath { 613 + var_id, 614 + path_so_far: path_so_far 615 + .iter() 616 + .map(|s| s.to_string()) 617 + .collect(), 618 + }, 619 prefix: cell_prefix.to_string(), 620 span: Span::new(cell_span_start, span.end), 621 }); 622 } else { 623 // Unknown variable, fall back to variable completion 624 let var_prefix = trimmed_prefix[1..].to_string(); 625 + return Some(CompletionContext { 626 + kind: CompletionKind::Variable, 627 prefix: var_prefix, 628 span, 629 }); ··· 635 } else { 636 String::new() 637 }; 638 + return Some(CompletionContext { 639 + kind: CompletionKind::Variable, 640 prefix: var_prefix, 641 span, 642 }); ··· 645 _ if is_command_shape(input, shape, local_span) => { 646 let (full_prefix, full_span) = 647 build_command_prefix(input, shapes, idx, span, &prefix, global_offset); 648 + return Some(CompletionContext { 649 + kind: CompletionKind::Command { 650 + parent_command: None, 651 + }, 652 prefix: full_prefix, 653 span: full_span, 654 }); 655 } 656 FlatShape::Block | FlatShape::Closure => { ··· 678 { 679 let prefix_byte_len = cell_prefix.len(); 680 let cell_span_start = span.end.saturating_sub(prefix_byte_len); 681 + return Some(CompletionContext { 682 + kind: CompletionKind::CellPath { 683 + var_id: *var_id, 684 + path_so_far: path_so_far 685 + .iter() 686 + .map(|s| s.to_string()) 687 + .collect(), 688 + }, 689 prefix: cell_prefix.to_string(), 690 span: Span::new(cell_span_start, span.end), 691 }); 692 } else { 693 // Simple variable completion 694 let var_prefix = trimmed_prefix[1..].to_string(); 695 + return Some(CompletionContext { 696 + kind: CompletionKind::Variable, 697 prefix: var_prefix, 698 span, 699 }); 700 } 701 } else { 702 // Fallback to argument context if no $ found 703 + return Some(CompletionContext { 704 + kind: CompletionKind::Argument, 705 prefix: prefix.to_string(), 706 span, 707 }); ··· 719 if let Some(var_id) = var_id { 720 let prefix_byte_len = cell_prefix.len(); 721 let cell_span_start = span.end.saturating_sub(prefix_byte_len); 722 + return Some(CompletionContext { 723 + kind: CompletionKind::CellPath { 724 + var_id, 725 + path_so_far: path_so_far 726 + .iter() 727 + .map(|s| s.to_string()) 728 + .collect(), 729 + }, 730 prefix: cell_prefix.to_string(), 731 span: Span::new(cell_span_start, span.end), 732 }); 733 } else { 734 let var_prefix = trimmed_prefix[1..].to_string(); 735 + return Some(CompletionContext { 736 + kind: CompletionKind::Variable, 737 prefix: var_prefix, 738 span, 739 }); ··· 745 } else { 746 String::new() 747 }; 748 + return Some(CompletionContext { 749 + kind: CompletionKind::Variable, 750 prefix: var_prefix, 751 span, 752 }); ··· 855 "[completion] Right after command {cmd_name:?}, setting CommandArgument context with arg_index: {arg_count}" 856 ); 857 858 + context.push(CompletionContext { 859 + kind: CompletionKind::CommandArgument { 860 + command_name: cmd_name.clone(), 861 + arg_index: arg_count, 862 + }, 863 prefix: String::new(), 864 span: Span::new(byte_pos, byte_pos), 865 }); 866 } 867 } ··· 877 console_log!( 878 "[completion] Command {cmd_name:?} has no positional args, showing subcommands" 879 ); 880 + context.push(CompletionContext { 881 + kind: CompletionKind::Command { 882 + parent_command: Some(cmd_first_word), 883 + }, 884 prefix: String::new(), 885 span: Span::new(byte_pos, byte_pos), 886 }); 887 } 888 // reverse to put subcommands in the beginning ··· 891 } else { 892 // Not right after command, complete the command itself 893 console_log!("[completion] Set Command context with prefix: {cmd:?}"); 894 + return vec![CompletionContext { 895 + kind: CompletionKind::Command { 896 + parent_command: None, 897 + }, 898 prefix: cmd.to_string(), 899 span: local_span, 900 }]; 901 } 902 } ··· 953 console_log!("[completion] last_word_start={last_word_start}, last_word={last_word:?}"); 954 955 if is_cmd_context { 956 + vec![CompletionContext { 957 + kind: CompletionKind::Command { 958 + parent_command: None, 959 + }, 960 prefix: last_word.to_string(), 961 span: Span::new(last_word_start, byte_pos), 962 }] 963 } else { 964 // Check if this is a variable or cell path (starts with $) ··· 971 if let Some(var_id) = var_id { 972 let prefix_byte_len = cell_prefix.len(); 973 let cell_span_start = byte_pos.saturating_sub(prefix_byte_len); 974 + vec![CompletionContext { 975 + kind: CompletionKind::CellPath { 976 + var_id, 977 + path_so_far: path_so_far.iter().map(|s| s.to_string()).collect(), 978 + }, 979 prefix: cell_prefix.to_string(), 980 span: Span::new(cell_span_start, byte_pos), 981 }] 982 } else { 983 let var_prefix = trimmed_word[1..].to_string(); 984 + vec![CompletionContext { 985 + kind: CompletionKind::Variable, 986 prefix: var_prefix, 987 span: Span::new(last_word_start, byte_pos), 988 }] ··· 990 } else { 991 // Simple variable completion 992 let var_prefix = trimmed_word[1..].to_string(); 993 + vec![CompletionContext { 994 + kind: CompletionKind::Variable, 995 prefix: var_prefix, 996 span: Span::new(last_word_start, byte_pos), 997 }] ··· 1009 } 1010 } 1011 if let Some(cmd_name) = found_cmd { 1012 + vec![CompletionContext { 1013 + kind: CompletionKind::Flag { 1014 + command_name: cmd_name, 1015 + }, 1016 prefix: trimmed_word.to_string(), 1017 span: Span::new(last_word_start, byte_pos), 1018 }] 1019 } else { 1020 + vec![CompletionContext { 1021 + kind: CompletionKind::Argument, 1022 prefix: last_word.to_string(), 1023 span: Span::new(last_word_start, byte_pos), 1024 }] ··· 1045 } 1046 } 1047 if let Some(cmd_name) = found_cmd { 1048 + vec![CompletionContext { 1049 + kind: CompletionKind::CommandArgument { 1050 + command_name: cmd_name, 1051 + arg_index: arg_count, 1052 + }, 1053 prefix: trimmed_word.to_string(), 1054 span: Span::new(last_word_start, byte_pos), 1055 }] 1056 } else { 1057 + vec![CompletionContext { 1058 + kind: CompletionKind::Argument, 1059 prefix: last_word.to_string(), 1060 span: Span::new(last_word_start, byte_pos), 1061 }]
+5 -1
src/completion/mod.rs
··· 73 global_offset, 74 ); 75 76 // Generate suggestions based on context 77 let suggestions = generate_suggestions( 78 &input, 79 - context, 80 &working_set, 81 &engine_guard, 82 &stack_guard,
··· 73 global_offset, 74 ); 75 76 + // Convert Vec to HashSet 77 + use std::collections::HashSet; 78 + let context_set: HashSet<CompletionContext> = context.into_iter().collect(); 79 + 80 // Generate suggestions based on context 81 let suggestions = generate_suggestions( 82 &input, 83 + context_set, 84 &working_set, 85 &engine_guard, 86 &stack_guard,
+54 -38
src/completion/suggestions.rs
··· 1 use crate::completion::context::get_command_signature; 2 use crate::completion::helpers::to_char_span; 3 - use crate::completion::types::{CompletionContext, Suggestion}; 4 use crate::completion::variables::*; 5 use crate::console_log; 6 use nu_protocol::Span; 7 use nu_protocol::engine::{EngineState, Stack, StateWorkingSet}; 8 9 pub fn generate_command_suggestions( 10 input: &str, ··· 205 ); 206 207 let mut suggestions = Vec::new(); 208 - 209 // If we're at argument index 0, check if the command has subcommands and add them 210 if arg_index == 0 { 211 let parent_prefix = format!("{} ", command_name); ··· 214 } else { 215 format!("{}{}", parent_prefix, prefix) 216 }; 217 - 218 let subcommands = working_set 219 .find_commands_by_predicate(|value| value.starts_with(search_prefix.as_bytes()), true); 220 - 221 if !subcommands.is_empty() { 222 // Command has subcommands - add them to suggestions 223 console_log!( ··· 229 if let Some(subcommand_name) = name_str.strip_prefix(&parent_prefix) { 230 suggestions.push(Suggestion { 231 rendered: { 232 - let name_colored = ansi_term::Color::Green.bold().paint(subcommand_name); 233 let desc_str = desc.as_deref().unwrap_or("<no description>"); 234 format!("{name_colored} {desc_str}") 235 }, ··· 242 } 243 } 244 } 245 - 246 if let Some(signature) = get_command_signature(engine_guard, &command_name) { 247 // First, check if we're completing an argument for a flag 248 // Look backwards from the current position to find the previous flag ··· 558 559 pub fn generate_suggestions( 560 input: &str, 561 - contexts: Vec<CompletionContext>, 562 working_set: &StateWorkingSet, 563 engine_guard: &EngineState, 564 stack_guard: &Stack, ··· 567 ) -> Vec<Suggestion> { 568 console_log!("contexts: {contexts:?}"); 569 570 let mut suggestions = Vec::new(); 571 - for context in contexts { 572 - let mut sug = match context { 573 - CompletionContext::Command { 574 - prefix, 575 - span, 576 - parent_command, 577 - } => generate_command_suggestions(input, working_set, prefix, span, parent_command), 578 - CompletionContext::Argument { prefix, span } => { 579 - generate_argument_suggestions(input, prefix, span, root) 580 } 581 - CompletionContext::Flag { 582 - prefix, 583 - span, 584 - command_name, 585 - } => generate_flag_suggestions(input, engine_guard, prefix, span, command_name), 586 - CompletionContext::CommandArgument { 587 - prefix, 588 - span, 589 command_name, 590 arg_index, 591 } => generate_command_argument_suggestions( 592 input, 593 engine_guard, 594 working_set, 595 - prefix, 596 - span, 597 - command_name, 598 - arg_index, 599 root, 600 ), 601 - CompletionContext::Variable { prefix, span } => { 602 - generate_variable_suggestions(input, working_set, prefix, span, byte_pos) 603 - } 604 - CompletionContext::CellPath { 605 - prefix, 606 - span, 607 var_id, 608 path_so_far, 609 } => generate_cell_path_suggestions( ··· 611 working_set, 612 engine_guard, 613 stack_guard, 614 - prefix, 615 - span, 616 - var_id, 617 - path_so_far, 618 ), 619 }; 620 suggestions.append(&mut sug);
··· 1 use crate::completion::context::get_command_signature; 2 use crate::completion::helpers::to_char_span; 3 + use crate::completion::types::{CompletionContext, CompletionKind, Suggestion}; 4 use crate::completion::variables::*; 5 use crate::console_log; 6 use nu_protocol::Span; 7 use nu_protocol::engine::{EngineState, Stack, StateWorkingSet}; 8 + use std::collections::HashSet; 9 10 pub fn generate_command_suggestions( 11 input: &str, ··· 206 ); 207 208 let mut suggestions = Vec::new(); 209 + 210 // If we're at argument index 0, check if the command has subcommands and add them 211 if arg_index == 0 { 212 let parent_prefix = format!("{} ", command_name); ··· 215 } else { 216 format!("{}{}", parent_prefix, prefix) 217 }; 218 + 219 let subcommands = working_set 220 .find_commands_by_predicate(|value| value.starts_with(search_prefix.as_bytes()), true); 221 + 222 if !subcommands.is_empty() { 223 // Command has subcommands - add them to suggestions 224 console_log!( ··· 230 if let Some(subcommand_name) = name_str.strip_prefix(&parent_prefix) { 231 suggestions.push(Suggestion { 232 rendered: { 233 + let name_colored = 234 + ansi_term::Color::Green.bold().paint(subcommand_name); 235 let desc_str = desc.as_deref().unwrap_or("<no description>"); 236 format!("{name_colored} {desc_str}") 237 }, ··· 244 } 245 } 246 } 247 + 248 if let Some(signature) = get_command_signature(engine_guard, &command_name) { 249 // First, check if we're completing an argument for a flag 250 // Look backwards from the current position to find the previous flag ··· 560 561 pub fn generate_suggestions( 562 input: &str, 563 + contexts: HashSet<CompletionContext>, 564 working_set: &StateWorkingSet, 565 engine_guard: &EngineState, 566 stack_guard: &Stack, ··· 569 ) -> Vec<Suggestion> { 570 console_log!("contexts: {contexts:?}"); 571 572 + let mut context_vec: Vec<_> = contexts.into_iter().collect(); 573 + context_vec.sort_by_key(|ctx| match &ctx.kind { 574 + CompletionKind::Command { .. } => 0, 575 + CompletionKind::Flag { .. } => 1, 576 + CompletionKind::Variable => 2, 577 + CompletionKind::CellPath { .. } => 3, 578 + CompletionKind::CommandArgument { .. } => 4, 579 + CompletionKind::Argument => 5, 580 + }); 581 + 582 let mut suggestions = Vec::new(); 583 + for context in context_vec.iter() { 584 + let mut sug = match &context.kind { 585 + CompletionKind::Command { parent_command } => generate_command_suggestions( 586 + input, 587 + working_set, 588 + context.prefix.clone(), 589 + context.span, 590 + parent_command.clone(), 591 + ), 592 + CompletionKind::Argument => { 593 + generate_argument_suggestions(input, context.prefix.clone(), context.span, root) 594 } 595 + CompletionKind::Flag { command_name } => generate_flag_suggestions( 596 + input, 597 + engine_guard, 598 + context.prefix.clone(), 599 + context.span, 600 + command_name.clone(), 601 + ), 602 + CompletionKind::CommandArgument { 603 command_name, 604 arg_index, 605 } => generate_command_argument_suggestions( 606 input, 607 engine_guard, 608 working_set, 609 + context.prefix.clone(), 610 + context.span, 611 + command_name.clone(), 612 + *arg_index, 613 root, 614 ), 615 + CompletionKind::Variable => generate_variable_suggestions( 616 + input, 617 + working_set, 618 + context.prefix.clone(), 619 + context.span, 620 + byte_pos, 621 + ), 622 + CompletionKind::CellPath { 623 var_id, 624 path_so_far, 625 } => generate_cell_path_suggestions( ··· 627 working_set, 628 engine_guard, 629 stack_guard, 630 + context.prefix.clone(), 631 + context.span, 632 + *var_id, 633 + path_so_far.clone(), 634 ), 635 }; 636 suggestions.append(&mut sug);
+44 -18
src/completion/types.rs
··· 27 } 28 } 29 30 - #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] 31 - pub enum CompletionContext { 32 Command { 33 - prefix: String, 34 - span: Span, 35 parent_command: Option<String>, // If Some, only show subcommands of this command 36 }, 37 - Argument { 38 - prefix: String, 39 - span: Span, 40 - }, 41 Flag { 42 - prefix: String, 43 - span: Span, 44 command_name: String, 45 }, 46 CommandArgument { 47 - prefix: String, 48 - span: Span, 49 command_name: String, 50 arg_index: usize, 51 }, 52 - Variable { 53 - prefix: String, // without the $ prefix 54 - span: Span, 55 - }, 56 CellPath { 57 - prefix: String, // the partial field name being typed (after the last dot) 58 - span: Span, // replacement span 59 var_id: nu_protocol::VarId, // variable ID for evaluation 60 path_so_far: Vec<String>, // path members accessed before current one 61 }, 62 }
··· 27 } 28 } 29 30 + #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 31 + pub enum CompletionKind { 32 Command { 33 parent_command: Option<String>, // If Some, only show subcommands of this command 34 }, 35 + Argument, 36 Flag { 37 command_name: String, 38 }, 39 CommandArgument { 40 command_name: String, 41 arg_index: usize, 42 }, 43 + Variable, // prefix is without the $ prefix 44 CellPath { 45 var_id: nu_protocol::VarId, // variable ID for evaluation 46 path_so_far: Vec<String>, // path members accessed before current one 47 }, 48 } 49 + 50 + #[derive(Debug)] 51 + pub struct CompletionContext { 52 + pub kind: CompletionKind, 53 + pub prefix: String, // the partial text being completed 54 + pub span: Span, 55 + } 56 + 57 + impl PartialEq for CompletionContext { 58 + fn eq(&self, other: &Self) -> bool { 59 + self.kind == other.kind && self.prefix == other.prefix 60 + } 61 + } 62 + 63 + impl Eq for CompletionContext {} 64 + 65 + impl PartialOrd for CompletionContext { 66 + fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { 67 + match self.kind.partial_cmp(&other.kind) { 68 + Some(std::cmp::Ordering::Equal) => self.prefix.partial_cmp(&other.prefix), 69 + other => other, 70 + } 71 + } 72 + } 73 + 74 + impl Ord for CompletionContext { 75 + fn cmp(&self, other: &Self) -> std::cmp::Ordering { 76 + match self.kind.cmp(&other.kind) { 77 + std::cmp::Ordering::Equal => self.prefix.cmp(&other.prefix), 78 + other => other, 79 + } 80 + } 81 + } 82 + 83 + impl std::hash::Hash for CompletionContext { 84 + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { 85 + self.kind.hash(state); 86 + self.prefix.hash(state); 87 + } 88 + }