+3
-2
src/cmd/ls.rs
+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
+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
+5
-1
src/completion/mod.rs
···
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
+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
+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
+
}