+1
-1
src/cmd/source.rs
+1
-1
src/cmd/source.rs
···
18
18
19
19
fn signature(&self) -> Signature {
20
20
Signature::build(self.name())
21
-
.required("filename", SyntaxShape::String, "the file to source")
21
+
.required("filename", SyntaxShape::Filepath, "the file to source")
22
22
.input_output_type(Type::Nothing, Type::Nothing)
23
23
.category(Category::Core)
24
24
}
+44
-61
src/completion/context.rs
+44
-61
src/completion/context.rs
···
734
734
engine_guard: &EngineState,
735
735
byte_pos: usize,
736
736
global_offset: usize,
737
-
) -> Option<CompletionContext> {
737
+
) -> Vec<CompletionContext> {
738
738
use nu_parser::{TokenContents, lex};
739
739
740
740
console_log!("[completion] Context is None, entering fallback logic");
···
779
779
cmd_first_word.clone()
780
780
};
781
781
782
+
let mut context = Vec::with_capacity(2);
782
783
if let Some(signature) = get_command_signature(engine_guard, &cmd_name) {
783
784
// Check if command has any positional arguments
784
785
let has_positional_args = !signature.required_positional.is_empty()
···
809
810
"[completion] Right after command {cmd_name:?}, setting CommandArgument context with arg_index: {arg_count}"
810
811
);
811
812
812
-
return Some(CompletionContext::CommandArgument {
813
+
context.push(CompletionContext::CommandArgument {
813
814
prefix: String::new(),
814
815
span: Span::new(byte_pos, byte_pos),
815
-
command_name: cmd_name,
816
+
command_name: cmd_name.clone(),
816
817
arg_index: arg_count,
817
818
});
818
-
} else {
819
-
// No positional arguments
820
-
// If this is a subcommand (contains spaces), don't show subcommands
821
-
// Only show subcommands if we're using just the base command (single word)
822
-
if is_subcommand && full_cmd_exists {
823
-
console_log!(
824
-
"[completion] Command {cmd_name:?} is a subcommand with no positional args, not showing completions"
825
-
);
826
-
return None;
827
-
} else {
828
-
// Show subcommands of the base command
829
-
console_log!(
830
-
"[completion] Command {cmd_name:?} has no positional args, showing subcommands"
831
-
);
832
-
return Some(CompletionContext::Command {
833
-
prefix: String::new(),
834
-
span: Span::new(byte_pos, byte_pos),
835
-
parent_command: Some(cmd_first_word),
836
-
});
837
-
}
838
819
}
820
+
}
821
+
// No positional arguments
822
+
// If this is a subcommand (contains spaces), don't show subcommands
823
+
// Only show subcommands if we're using just the base command (single word)
824
+
if is_subcommand && full_cmd_exists {
825
+
console_log!(
826
+
"[completion] Command {cmd_name:?} is a subcommand with no positional args, not showing completions"
827
+
);
839
828
} else {
840
-
// Couldn't find signature
841
-
// If this is a subcommand, don't show completions
842
-
// Otherwise, show subcommands of the first word
843
-
if is_subcommand && full_cmd_exists {
844
-
console_log!(
845
-
"[completion] Could not find signature for subcommand {cmd_name:?}, not showing completions"
846
-
);
847
-
return None;
848
-
} else {
849
-
console_log!(
850
-
"[completion] Could not find signature for {cmd_name:?}, showing subcommands"
851
-
);
852
-
return Some(CompletionContext::Command {
853
-
prefix: String::new(),
854
-
span: Span::new(byte_pos, byte_pos),
855
-
parent_command: Some(cmd_first_word),
856
-
});
857
-
}
829
+
// Show subcommands of the base command
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
+
});
858
838
}
839
+
// reverse to put subcommands in the beginning
840
+
context.reverse();
841
+
return context;
859
842
} else {
860
843
// Not right after command, complete the command itself
861
844
console_log!("[completion] Set Command context with prefix: {cmd:?}");
862
-
return Some(CompletionContext::Command {
845
+
return vec![CompletionContext::Command {
863
846
prefix: cmd.to_string(),
864
847
span: local_span,
865
848
parent_command: None,
866
-
});
849
+
}];
867
850
}
868
851
}
869
852
}
···
919
902
console_log!("[completion] last_word_start={last_word_start}, last_word={last_word:?}");
920
903
921
904
if is_cmd_context {
922
-
Some(CompletionContext::Command {
905
+
vec![CompletionContext::Command {
923
906
prefix: last_word.to_string(),
924
907
span: Span::new(last_word_start, byte_pos),
925
908
parent_command: None,
926
-
})
909
+
}]
927
910
} else {
928
911
// Check if this is a variable or cell path (starts with $)
929
912
let trimmed_word = last_word.trim();
···
935
918
if let Some(var_id) = var_id {
936
919
let prefix_byte_len = cell_prefix.len();
937
920
let cell_span_start = byte_pos.saturating_sub(prefix_byte_len);
938
-
Some(CompletionContext::CellPath {
921
+
vec![CompletionContext::CellPath {
939
922
prefix: cell_prefix.to_string(),
940
923
span: Span::new(cell_span_start, byte_pos),
941
924
var_id,
942
925
path_so_far: path_so_far.iter().map(|s| s.to_string()).collect(),
943
-
})
926
+
}]
944
927
} else {
945
928
let var_prefix = trimmed_word[1..].to_string();
946
-
Some(CompletionContext::Variable {
929
+
vec![CompletionContext::Variable {
947
930
prefix: var_prefix,
948
931
span: Span::new(last_word_start, byte_pos),
949
-
})
932
+
}]
950
933
}
951
934
} else {
952
935
// Simple variable completion
953
936
let var_prefix = trimmed_word[1..].to_string();
954
-
Some(CompletionContext::Variable {
937
+
vec![CompletionContext::Variable {
955
938
prefix: var_prefix,
956
939
span: Span::new(last_word_start, byte_pos),
957
-
})
940
+
}]
958
941
}
959
942
} else if trimmed_word.starts_with('-') {
960
943
// Try to find command by looking backwards through shapes
···
969
952
}
970
953
}
971
954
if let Some(cmd_name) = found_cmd {
972
-
Some(CompletionContext::Flag {
955
+
vec![CompletionContext::Flag {
973
956
prefix: trimmed_word.to_string(),
974
957
span: Span::new(last_word_start, byte_pos),
975
958
command_name: cmd_name,
976
-
})
959
+
}]
977
960
} else {
978
-
Some(CompletionContext::Argument {
961
+
vec![CompletionContext::Argument {
979
962
prefix: last_word.to_string(),
980
963
span: Span::new(last_word_start, byte_pos),
981
-
})
964
+
}]
982
965
}
983
966
} else {
984
967
// Try to find command and argument index
···
1002
985
}
1003
986
}
1004
987
if let Some(cmd_name) = found_cmd {
1005
-
Some(CompletionContext::CommandArgument {
988
+
vec![CompletionContext::CommandArgument {
1006
989
prefix: trimmed_word.to_string(),
1007
990
span: Span::new(last_word_start, byte_pos),
1008
991
command_name: cmd_name,
1009
992
arg_index: arg_count,
1010
-
})
993
+
}]
1011
994
} else {
1012
-
Some(CompletionContext::Argument {
995
+
vec![CompletionContext::Argument {
1013
996
prefix: last_word.to_string(),
1014
997
span: Span::new(last_word_start, byte_pos),
1015
-
})
998
+
}]
1016
999
}
1017
1000
}
1018
1001
}
···
1025
1008
engine_guard: &EngineState,
1026
1009
byte_pos: usize,
1027
1010
global_offset: usize,
1028
-
) -> Option<CompletionContext> {
1011
+
) -> Vec<CompletionContext> {
1029
1012
// First try to determine context from shapes
1030
1013
if let Some(ctx) =
1031
1014
determine_context_from_shape(input, shapes, working_set, byte_pos, global_offset)
1032
1015
{
1033
-
return Some(ctx);
1016
+
return vec![ctx];
1034
1017
}
1035
1018
1036
1019
// Fallback to token-based context determination
-51
src/completion/files.rs
-51
src/completion/files.rs
···
1
-
use crate::completion::helpers::to_char_span;
2
-
use crate::completion::types::Suggestion;
3
-
use nu_protocol::Span;
4
-
5
-
pub fn generate_file_suggestions(
6
-
prefix: &str,
7
-
span: Span,
8
-
root: &std::sync::Arc<vfs::VfsPath>,
9
-
description: Option<String>,
10
-
input: &str,
11
-
) -> Vec<Suggestion> {
12
-
let (dir, file_prefix) = prefix
13
-
.rfind('/')
14
-
.map(|idx| (&prefix[..idx + 1], &prefix[idx + 1..]))
15
-
.unwrap_or(("", prefix));
16
-
17
-
let dir_to_join = (dir.len() > 1 && dir.ends_with('/'))
18
-
.then(|| &dir[..dir.len() - 1])
19
-
.unwrap_or(dir);
20
-
21
-
let target_dir = if !dir.is_empty() {
22
-
match root.join(dir_to_join) {
23
-
Ok(d) if d.is_dir().unwrap_or(false) => Some(d),
24
-
_ => None,
25
-
}
26
-
} else {
27
-
Some(root.join("").unwrap())
28
-
};
29
-
30
-
let mut file_suggestions = Vec::new();
31
-
if let Some(d) = target_dir {
32
-
if let Ok(iterator) = d.read_dir() {
33
-
let char_span = to_char_span(input, span);
34
-
for entry in iterator {
35
-
let name = entry.filename();
36
-
if name.starts_with(file_prefix) {
37
-
let full_completion = format!("{}{}", dir, name);
38
-
file_suggestions.push(Suggestion {
39
-
name: full_completion.clone(),
40
-
description: description.clone(),
41
-
is_command: false,
42
-
rendered: full_completion,
43
-
span_start: char_span.start,
44
-
span_end: char_span.end,
45
-
});
46
-
}
47
-
}
48
-
}
49
-
}
50
-
file_suggestions
51
-
}
+1
-3
src/completion/mod.rs
+1
-3
src/completion/mod.rs
···
9
9
use super::*;
10
10
11
11
pub mod context;
12
-
pub mod files;
13
12
pub mod helpers;
14
13
pub mod suggestions;
15
14
pub mod types;
···
75
74
);
76
75
77
76
// Generate suggestions based on context
78
-
let mut suggestions = generate_suggestions(
77
+
let suggestions = generate_suggestions(
79
78
&input,
80
79
context,
81
80
&working_set,
···
88
87
drop(working_set);
89
88
drop(engine_guard);
90
89
91
-
suggestions.sort();
92
90
let suggestions = serde_json::to_string(&suggestions).unwrap_or_else(|_| "[]".to_string());
93
91
console_log!("{suggestions}");
94
92
suggestions
+114
-82
src/completion/suggestions.rs
+114
-82
src/completion/suggestions.rs
···
1
1
use crate::completion::context::get_command_signature;
2
-
use crate::completion::files::generate_file_suggestions;
3
2
use crate::completion::helpers::to_char_span;
4
3
use crate::completion::types::{CompletionContext, Suggestion};
5
4
use crate::completion::variables::*;
···
65
64
},
66
65
name: display_name,
67
66
description: desc.map(|d| d.to_string()),
68
-
is_command: true,
69
67
span_start: span.start,
70
68
span_end: span.end,
71
69
});
72
70
cmd_count += 1;
73
71
}
74
72
console_log!("[completion] Found {cmd_count} command suggestions");
73
+
suggestions.sort();
75
74
suggestions
76
75
}
77
76
···
83
82
) -> Vec<Suggestion> {
84
83
console_log!("[completion] Generating Argument suggestions with prefix: {prefix:?}");
85
84
// File completion
86
-
let file_suggestions = generate_file_suggestions(&prefix, span, root, None, input);
87
-
let file_count = file_suggestions.len();
88
-
console_log!("[completion] Found {file_count} file suggestions");
85
+
let mut file_suggestions = generate_file_suggestions(&prefix, span, root, None, input);
86
+
console_log!(
87
+
"[completion] Found {file_count} file suggestions",
88
+
file_count = file_suggestions.len()
89
+
);
90
+
file_suggestions.sort();
89
91
file_suggestions
90
92
}
91
93
···
127
129
Suggestion {
128
130
name: flag_name.clone(),
129
131
description: Some(flag.desc.clone()),
130
-
is_command: false,
131
132
rendered: {
132
133
let flag_colored = ansi_term::Color::Cyan.bold().paint(&flag_name);
133
134
format!("{flag_colored} {}", flag.desc)
···
185
186
} else {
186
187
console_log!("[completion] Could not find signature for command: {command_name:?}");
187
188
}
189
+
suggestions.sort();
188
190
suggestions
189
191
}
190
192
···
309
311
310
312
if let Some(arg) = arg {
311
313
// Check the SyntaxShape to determine completion type
312
-
// Only suggest files/dirs for Filepath type (or "any" when type is unknown)
314
+
// Only suggest files/dirs for Filepath type
313
315
match &arg.shape {
314
316
nu_protocol::SyntaxShape::Filepath | nu_protocol::SyntaxShape::Any => {
315
317
// File/directory completion
···
325
327
console_log!(
326
328
"[completion] Found {file_count} file suggestions for argument {arg_index}"
327
329
);
328
-
329
-
// If the argument is optional and of type Any or Filepath, also show subcommands
330
-
if is_optional {
331
-
console_log!(
332
-
"[completion] Argument {arg_index} is optional and of type {:?}, also showing subcommands",
333
-
arg.shape
334
-
);
335
-
let subcommand_suggestions = generate_command_suggestions(
336
-
input,
337
-
working_set,
338
-
prefix.clone(),
339
-
span,
340
-
Some(command_name.clone()),
341
-
);
342
-
let subcommand_count = subcommand_suggestions.len();
343
-
suggestions.extend(subcommand_suggestions);
344
-
console_log!(
345
-
"[completion] Found {subcommand_count} subcommand suggestions"
346
-
);
347
-
}
348
330
}
349
331
_ => {
350
332
// For other types, don't suggest files
···
369
351
let file_suggestions = generate_file_suggestions(&prefix, span, root, None, input);
370
352
suggestions.extend(file_suggestions);
371
353
}
354
+
suggestions.sort();
372
355
suggestions
373
356
}
374
357
···
396
379
suggestions.push(Suggestion {
397
380
name: var_name.clone(),
398
381
description: Some(var_type.clone()),
399
-
is_command: false,
400
382
rendered: {
401
383
let var_colored = ansi_term::Color::Blue.bold().paint(&var_name);
402
384
format!("{var_colored} {var_type}")
···
409
391
}
410
392
411
393
console_log!("[completion] Found {var_count} variable suggestions");
394
+
suggestions.sort();
412
395
suggestions
413
396
}
414
397
···
451
434
suggestions.push(Suggestion {
452
435
name: col_name.clone(),
453
436
description: Some(type_str.to_string()),
454
-
is_command: false,
455
437
rendered: {
456
438
let col_colored = ansi_term::Color::Yellow.paint(&col_name);
457
439
format!("{col_colored} {type_str}")
···
478
460
console_log!("[completion] Variable type: {ty:?}", ty = var_info.ty);
479
461
}
480
462
}
463
+
suggestions.sort();
481
464
suggestions
482
465
}
483
466
467
+
pub fn generate_file_suggestions(
468
+
prefix: &str,
469
+
span: Span,
470
+
root: &std::sync::Arc<vfs::VfsPath>,
471
+
description: Option<String>,
472
+
input: &str,
473
+
) -> Vec<Suggestion> {
474
+
let (dir, file_prefix) = prefix
475
+
.rfind('/')
476
+
.map(|idx| (&prefix[..idx + 1], &prefix[idx + 1..]))
477
+
.unwrap_or(("", prefix));
478
+
479
+
let dir_to_join = (dir.len() > 1 && dir.ends_with('/'))
480
+
.then(|| &dir[..dir.len() - 1])
481
+
.unwrap_or(dir);
482
+
483
+
let target_dir = if !dir.is_empty() {
484
+
match root.join(dir_to_join) {
485
+
Ok(d) if d.is_dir().unwrap_or(false) => Some(d),
486
+
_ => None,
487
+
}
488
+
} else {
489
+
Some(root.join("").unwrap())
490
+
};
491
+
492
+
let mut file_suggestions = Vec::new();
493
+
if let Some(d) = target_dir {
494
+
if let Ok(iterator) = d.read_dir() {
495
+
let char_span = to_char_span(input, span);
496
+
for entry in iterator {
497
+
let name = entry.filename();
498
+
if name.starts_with(file_prefix) {
499
+
let full_completion = format!("{}{}", dir, name);
500
+
file_suggestions.push(Suggestion {
501
+
name: full_completion.clone(),
502
+
description: description.clone(),
503
+
rendered: full_completion,
504
+
span_start: char_span.start,
505
+
span_end: char_span.end,
506
+
});
507
+
}
508
+
}
509
+
}
510
+
}
511
+
file_suggestions
512
+
}
513
+
484
514
pub fn generate_suggestions(
485
515
input: &str,
486
-
context: Option<CompletionContext>,
516
+
contexts: Vec<CompletionContext>,
487
517
working_set: &StateWorkingSet,
488
518
engine_guard: &EngineState,
489
519
stack_guard: &Stack,
490
520
root: &std::sync::Arc<vfs::VfsPath>,
491
521
byte_pos: usize,
492
522
) -> Vec<Suggestion> {
493
-
console_log!("context: {context:?}");
523
+
console_log!("contexts: {contexts:?}");
494
524
495
-
match context {
496
-
Some(CompletionContext::Command {
497
-
prefix,
498
-
span,
499
-
parent_command,
500
-
}) => generate_command_suggestions(input, working_set, prefix, span, parent_command),
501
-
Some(CompletionContext::Argument { prefix, span }) => {
502
-
generate_argument_suggestions(input, prefix, span, root)
503
-
}
504
-
Some(CompletionContext::Flag {
505
-
prefix,
506
-
span,
507
-
command_name,
508
-
}) => generate_flag_suggestions(input, engine_guard, prefix, span, command_name),
509
-
Some(CompletionContext::CommandArgument {
510
-
prefix,
511
-
span,
512
-
command_name,
513
-
arg_index,
514
-
}) => generate_command_argument_suggestions(
515
-
input,
516
-
engine_guard,
517
-
working_set,
518
-
prefix,
519
-
span,
520
-
command_name,
521
-
arg_index,
522
-
root,
523
-
),
524
-
Some(CompletionContext::Variable { prefix, span }) => {
525
-
generate_variable_suggestions(input, working_set, prefix, span, byte_pos)
526
-
}
527
-
Some(CompletionContext::CellPath {
528
-
prefix,
529
-
span,
530
-
var_id,
531
-
path_so_far,
532
-
}) => generate_cell_path_suggestions(
533
-
input,
534
-
working_set,
535
-
engine_guard,
536
-
stack_guard,
537
-
prefix,
538
-
span,
539
-
var_id,
540
-
path_so_far,
541
-
),
542
-
_ => {
543
-
console_log!("[completion] Context is None, no suggestions generated");
544
-
Vec::new()
545
-
}
525
+
let mut suggestions = Vec::new();
526
+
for context in contexts {
527
+
let mut sug = match context {
528
+
CompletionContext::Command {
529
+
prefix,
530
+
span,
531
+
parent_command,
532
+
} => generate_command_suggestions(input, working_set, prefix, span, parent_command),
533
+
CompletionContext::Argument { prefix, span } => {
534
+
generate_argument_suggestions(input, prefix, span, root)
535
+
}
536
+
CompletionContext::Flag {
537
+
prefix,
538
+
span,
539
+
command_name,
540
+
} => generate_flag_suggestions(input, engine_guard, prefix, span, command_name),
541
+
CompletionContext::CommandArgument {
542
+
prefix,
543
+
span,
544
+
command_name,
545
+
arg_index,
546
+
} => generate_command_argument_suggestions(
547
+
input,
548
+
engine_guard,
549
+
working_set,
550
+
prefix,
551
+
span,
552
+
command_name,
553
+
arg_index,
554
+
root,
555
+
),
556
+
CompletionContext::Variable { prefix, span } => {
557
+
generate_variable_suggestions(input, working_set, prefix, span, byte_pos)
558
+
}
559
+
CompletionContext::CellPath {
560
+
prefix,
561
+
span,
562
+
var_id,
563
+
path_so_far,
564
+
} => generate_cell_path_suggestions(
565
+
input,
566
+
working_set,
567
+
engine_guard,
568
+
stack_guard,
569
+
prefix,
570
+
span,
571
+
var_id,
572
+
path_so_far,
573
+
),
574
+
};
575
+
suggestions.append(&mut sug);
546
576
}
577
+
578
+
suggestions
547
579
}
+1
-2
src/completion/types.rs
+1
-2
src/completion/types.rs
···
5
5
pub struct Suggestion {
6
6
pub name: String,
7
7
pub description: Option<String>,
8
-
pub is_command: bool,
9
8
pub rendered: String,
10
9
pub span_start: usize, // char index (not byte)
11
10
pub span_end: usize, // char index (not byte)
···
28
27
}
29
28
}
30
29
31
-
#[derive(Debug)]
30
+
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
32
31
pub enum CompletionContext {
33
32
Command {
34
33
prefix: String,
-2
www/src/main.ts
-2
www/src/main.ts
···
14
14
type Candidate = {
15
15
name: string;
16
16
description: string;
17
-
is_command: boolean;
18
17
rendered: string;
19
18
span_start: number;
20
19
span_end: number;
···
300
299
completionCandidates = matches.map((cmd) => ({
301
300
name: cmd,
302
301
description: "",
303
-
is_command: true,
304
302
rendered: cmd,
305
303
span_start: 0,
306
304
span_end: currentLine.length,