tangled
alpha
login
or
join now
ptr.pet
/
faunu
2
fork
atom
nushell on your web browser
nushell
wasm
terminal
2
fork
atom
overview
issues
pulls
pipelines
fix subcommands not showing up if command also has argument
ptr.pet
2 months ago
6f317b2d
373a827a
verified
This commit was signed with the committer's
known signature
.
ptr.pet
SSH Key Fingerprint:
SHA256:Abmvag+juovVufZTxyWY8KcVgrznxvBjQpJesv071Aw=
+189
-155
2 changed files
expand all
collapse all
unified
split
src
completion
context.rs
suggestions.rs
+188
-116
src/completion/context.rs
···
107
107
.map(|id| engine_guard.get_decl(id).signature())
108
108
}
109
109
110
110
+
/// Creates CommandArgument context(s), and optionally adds a Command context for subcommands
111
111
+
/// if we're at argument index 0 and the command has subcommands.
112
112
+
pub fn create_command_argument_contexts(
113
113
+
command_name: String,
114
114
+
arg_index: usize,
115
115
+
prefix: String,
116
116
+
span: Span,
117
117
+
working_set: &StateWorkingSet,
118
118
+
_engine_guard: &EngineState,
119
119
+
) -> Vec<CompletionContext> {
120
120
+
let mut contexts = Vec::new();
121
121
+
122
122
+
// Always add the CommandArgument context
123
123
+
contexts.push(CompletionContext {
124
124
+
kind: CompletionKind::CommandArgument {
125
125
+
command_name: command_name.clone(),
126
126
+
arg_index,
127
127
+
},
128
128
+
prefix: prefix.clone(),
129
129
+
span,
130
130
+
});
131
131
+
132
132
+
// If we're at argument index 0, check if the command has subcommands
133
133
+
if arg_index == 0 {
134
134
+
// Check if command has subcommands
135
135
+
// Subcommands are commands that start with "command_name " (with space)
136
136
+
let parent_prefix = format!("{} ", command_name);
137
137
+
let subcommands = working_set
138
138
+
.find_commands_by_predicate(|value| value.starts_with(parent_prefix.as_bytes()), true);
139
139
+
140
140
+
if !subcommands.is_empty() {
141
141
+
// Command has subcommands - add a Command context for subcommands
142
142
+
console_log!(
143
143
+
"[completion] Command {command_name:?} has subcommands, adding Command context for subcommands"
144
144
+
);
145
145
+
contexts.push(CompletionContext {
146
146
+
kind: CompletionKind::Command {
147
147
+
parent_command: Some(command_name),
148
148
+
},
149
149
+
prefix,
150
150
+
span,
151
151
+
});
152
152
+
}
153
153
+
}
154
154
+
155
155
+
contexts
156
156
+
}
157
157
+
110
158
pub fn determine_flag_or_argument_context(
111
159
input: &str,
112
160
shapes: &[(Span, FlatShape)],
···
115
163
local_span: Span,
116
164
span: Span,
117
165
global_offset: usize,
118
118
-
) -> CompletionContext {
166
166
+
working_set: &StateWorkingSet,
167
167
+
_engine_guard: &EngineState,
168
168
+
) -> Vec<CompletionContext> {
119
169
let trimmed_prefix = prefix.trim();
120
170
if trimmed_prefix.starts_with('-') {
121
171
// This looks like a flag - find the command
122
172
if let Some((cmd_name, _)) =
123
173
find_command_and_arg_index(input, shapes, idx, local_span, global_offset)
124
174
{
125
125
-
CompletionContext {
175
175
+
vec![CompletionContext {
126
176
kind: CompletionKind::Flag {
127
177
command_name: cmd_name,
128
178
},
129
179
prefix: trimmed_prefix.to_string(),
130
180
span,
131
131
-
}
181
181
+
}]
132
182
} else {
133
133
-
CompletionContext {
183
183
+
vec![CompletionContext {
134
184
kind: CompletionKind::Argument,
135
185
prefix: prefix.to_string(),
136
186
span,
137
137
-
}
187
187
+
}]
138
188
}
139
189
} else {
140
190
// This is a positional argument - find the command and argument index
141
191
if let Some((cmd_name, arg_index)) =
142
192
find_command_and_arg_index(input, shapes, idx, local_span, global_offset)
143
193
{
144
144
-
CompletionContext {
145
145
-
kind: CompletionKind::CommandArgument {
146
146
-
command_name: cmd_name,
147
147
-
arg_index,
148
148
-
},
149
149
-
prefix: trimmed_prefix.to_string(),
194
194
+
create_command_argument_contexts(
195
195
+
cmd_name,
196
196
+
arg_index,
197
197
+
trimmed_prefix.to_string(),
150
198
span,
151
151
-
}
199
199
+
working_set,
200
200
+
_engine_guard,
201
201
+
)
152
202
} else {
153
153
-
CompletionContext {
203
203
+
vec![CompletionContext {
154
204
kind: CompletionKind::Argument,
155
205
prefix: prefix.to_string(),
156
206
span,
157
157
-
}
207
207
+
}]
158
208
}
159
209
}
160
210
}
···
163
213
input: &str,
164
214
shapes: &[(Span, FlatShape)],
165
215
working_set: &StateWorkingSet,
216
216
+
engine_guard: &EngineState,
166
217
prefix: &str,
167
218
span: Span,
168
219
shape_name: &str,
169
220
current_idx: usize,
170
221
local_span: Span,
171
222
global_offset: usize,
172
172
-
) -> Option<CompletionContext> {
223
223
+
) -> Vec<CompletionContext> {
173
224
console_log!("[completion] Processing {shape_name} shape with prefix: {prefix:?}");
174
225
175
226
// Check if the content ends with a pipe or semicolon
···
214
265
console_log!(
215
266
"[completion] {shape_name} is empty but found full command {cmd_full:?} before it, not showing completions"
216
267
);
217
217
-
return None;
268
268
+
return Vec::new();
218
269
}
219
270
220
271
// Use the first word to show subcommands
···
233
284
console_log!(
234
285
"[completion] {shape_name} is empty, showing subcommands of {cmd_name:?}"
235
286
);
236
236
-
Some(CompletionContext {
287
287
+
vec![CompletionContext {
237
288
kind: CompletionKind::Command {
238
289
parent_command: Some(cmd_name),
239
290
},
240
291
prefix: String::new(),
241
292
span: adjusted_span,
242
242
-
})
293
293
+
}]
243
294
} else {
244
295
// Truly empty - show all commands
245
296
console_log!("[completion] {shape_name} is empty, setting Command context");
246
246
-
Some(CompletionContext {
297
297
+
vec![CompletionContext {
247
298
kind: CompletionKind::Command {
248
299
parent_command: None,
249
300
},
250
301
prefix: String::new(),
251
302
span: adjusted_span,
252
252
-
})
303
303
+
}]
253
304
}
254
305
} else if let Some(last_sep_pos) = last_sep_pos_in_prefix {
255
306
// After a separator - command context
···
257
308
console_log!(
258
309
"[completion] {shape_name} has separator at {last_sep_pos}, after_sep={after_sep:?}, setting Command context"
259
310
);
260
260
-
Some(CompletionContext {
311
311
+
vec![CompletionContext {
261
312
kind: CompletionKind::Command {
262
313
parent_command: None,
263
314
},
264
315
prefix: after_sep.to_string(),
265
316
span: Span::new(span.start + last_sep_pos, span.end),
266
266
-
})
317
317
+
}]
267
318
} else {
268
319
console_log!(
269
320
"[completion] {shape_name} has no separator, checking for variable/flag/argument context"
···
282
333
console_log!(
283
334
"[completion] {shape_name}: Setting CellPath context with var {var_name:?}, prefix {cell_prefix:?}"
284
335
);
285
285
-
Some(CompletionContext {
336
336
+
vec![CompletionContext {
286
337
kind: CompletionKind::CellPath {
287
338
var_id,
288
339
path_so_far: path_so_far.iter().map(|s| s.to_string()).collect(),
289
340
},
290
341
prefix: cell_prefix.to_string(),
291
342
span: Span::new(cell_span_start, adjusted_span.end),
292
292
-
})
343
343
+
}]
293
344
} else {
294
345
// Unknown variable, fall back to variable completion
295
346
let var_prefix = trimmed[1..].to_string();
296
347
console_log!(
297
348
"[completion] {shape_name}: Unknown var, setting Variable context with prefix {var_prefix:?}"
298
349
);
299
299
-
Some(CompletionContext {
350
350
+
vec![CompletionContext {
300
351
kind: CompletionKind::Variable,
301
352
prefix: var_prefix,
302
353
span: adjusted_span,
303
303
-
})
354
354
+
}]
304
355
}
305
356
} else {
306
357
// Simple variable completion (no dot)
···
312
363
console_log!(
313
364
"[completion] {shape_name}: Setting Variable context with prefix {var_prefix:?}"
314
365
);
315
315
-
Some(CompletionContext {
366
366
+
vec![CompletionContext {
316
367
kind: CompletionKind::Variable,
317
368
prefix: var_prefix,
318
369
span: adjusted_span,
319
319
-
})
370
370
+
}]
320
371
}
321
372
} else if trimmed.starts_with('-') {
322
373
// Flag completion
···
330
381
console_log!(
331
382
"[completion] {shape_name}: Found command {cmd_name:?} for flag completion"
332
383
);
333
333
-
Some(CompletionContext {
384
384
+
vec![CompletionContext {
334
385
kind: CompletionKind::Flag {
335
386
command_name: cmd_name,
336
387
},
337
388
prefix: trimmed.to_string(),
338
389
span: adjusted_span,
339
339
-
})
390
390
+
}]
340
391
} else {
341
341
-
Some(CompletionContext {
392
392
+
vec![CompletionContext {
342
393
kind: CompletionKind::Argument,
343
394
prefix: trimmed_prefix.to_string(),
344
395
span: adjusted_span,
345
345
-
})
396
396
+
}]
346
397
}
347
398
} else {
348
399
// Try to find the command and argument index
···
356
407
console_log!(
357
408
"[completion] {shape_name}: Found command {cmd_name:?} with arg_index {arg_index} for argument completion"
358
409
);
359
359
-
Some(CompletionContext {
360
360
-
kind: CompletionKind::CommandArgument {
361
361
-
command_name: cmd_name,
362
362
-
arg_index,
363
363
-
},
364
364
-
prefix: trimmed.to_string(),
365
365
-
span: adjusted_span,
366
366
-
})
410
410
+
create_command_argument_contexts(
411
411
+
cmd_name,
412
412
+
arg_index,
413
413
+
trimmed.to_string(),
414
414
+
adjusted_span,
415
415
+
working_set,
416
416
+
engine_guard,
417
417
+
)
367
418
} else {
368
419
// No command found, treat as regular argument
369
420
console_log!(
370
421
"[completion] {shape_name}: No command found, using Argument context"
371
422
);
372
372
-
Some(CompletionContext {
423
423
+
vec![CompletionContext {
373
424
kind: CompletionKind::Argument,
374
425
prefix: trimmed_prefix.to_string(),
375
426
span: adjusted_span,
376
376
-
})
427
427
+
}]
377
428
}
378
429
}
379
430
}
380
431
} else {
381
381
-
None
432
432
+
Vec::new()
382
433
}
383
434
}
384
435
385
436
pub fn handle_variable_string_shape(
386
437
input: &str,
387
438
shapes: &[(Span, FlatShape)],
388
388
-
_working_set: &StateWorkingSet,
439
439
+
working_set: &StateWorkingSet,
440
440
+
engine_guard: &EngineState,
389
441
idx: usize,
390
442
prefix: &str,
391
443
span: Span,
392
444
local_span: Span,
393
445
global_offset: usize,
394
394
-
) -> Option<CompletionContext> {
446
446
+
) -> Vec<CompletionContext> {
395
447
if idx == 0 {
396
396
-
return None;
448
448
+
return Vec::new();
397
449
}
398
450
399
451
let prev_shape = &shapes[idx - 1];
···
414
466
console_log!(
415
467
"[completion] Detected cell path from Variable+String shapes, var_id={var_id:?}, prefix={cell_prefix:?}, path={path_so_far:?}"
416
468
);
417
417
-
Some(CompletionContext {
469
469
+
vec![CompletionContext {
418
470
kind: CompletionKind::CellPath {
419
471
var_id,
420
472
path_so_far: path_so_far.iter().map(|s| s.to_string()).collect(),
421
473
},
422
474
prefix: cell_prefix.to_string(),
423
475
span: Span::new(cell_span_start, span.end),
424
424
-
})
476
476
+
}]
425
477
} else {
426
478
// Gap between shapes, use helper to determine context
427
427
-
Some(determine_flag_or_argument_context(
479
479
+
determine_flag_or_argument_context(
428
480
input,
429
481
shapes,
430
482
&prefix.trim(),
···
432
484
local_span,
433
485
span,
434
486
global_offset,
435
435
-
))
487
487
+
working_set,
488
488
+
engine_guard,
489
489
+
)
436
490
}
437
491
} else {
438
492
// Previous shape is not a Variable, use helper to determine context
439
439
-
Some(determine_flag_or_argument_context(
493
493
+
determine_flag_or_argument_context(
440
494
input,
441
495
shapes,
442
496
&prefix.trim(),
···
444
498
local_span,
445
499
span,
446
500
global_offset,
447
447
-
))
501
501
+
working_set,
502
502
+
engine_guard,
503
503
+
)
448
504
}
449
505
}
450
506
···
456
512
span: Span,
457
513
local_span: Span,
458
514
global_offset: usize,
459
459
-
) -> Option<CompletionContext> {
515
515
+
) -> Vec<CompletionContext> {
460
516
if idx == 0 {
461
461
-
return Some(CompletionContext {
517
517
+
return vec![CompletionContext {
462
518
kind: CompletionKind::Argument,
463
519
prefix: prefix.to_string(),
464
520
span,
465
465
-
});
521
521
+
}];
466
522
}
467
523
468
524
let prev_shape = &shapes[idx - 1];
···
485
541
console_log!(
486
542
"[completion] Detected cell path from adjacent Variable shape, var_id={var_id:?}, prefix={cell_prefix:?}"
487
543
);
488
488
-
Some(CompletionContext {
544
544
+
vec![CompletionContext {
489
545
kind: CompletionKind::CellPath {
490
546
var_id,
491
547
path_so_far: path_so_far.iter().map(|s| s.to_string()).collect(),
492
548
},
493
549
prefix: cell_prefix.to_string(),
494
550
span: Span::new(cell_span_start, span.end),
495
495
-
})
551
551
+
}]
496
552
} else {
497
553
// Gap between shapes, fall through to default handling
498
498
-
Some(CompletionContext {
554
554
+
vec![CompletionContext {
499
555
kind: CompletionKind::Argument,
500
556
prefix: prefix.to_string(),
501
557
span,
502
502
-
})
558
558
+
}]
503
559
}
504
560
} else {
505
561
// Previous shape is not a Variable, this is likely a file path starting with .
506
506
-
Some(CompletionContext {
562
562
+
vec![CompletionContext {
507
563
kind: CompletionKind::Argument,
508
564
prefix: prefix.to_string(),
509
565
span,
510
510
-
})
566
566
+
}]
511
567
}
512
568
}
513
569
···
515
571
input: &str,
516
572
shapes: &[(Span, FlatShape)],
517
573
working_set: &StateWorkingSet,
574
574
+
engine_guard: &EngineState,
518
575
byte_pos: usize,
519
576
global_offset: usize,
520
520
-
) -> Option<CompletionContext> {
577
577
+
) -> Vec<CompletionContext> {
521
578
// First, check if cursor is within a shape
522
579
for (idx, (span, shape)) in shapes.iter().enumerate() {
523
580
let local_span = to_local_span(*span, global_offset);
···
547
604
if trimmed_prefix == "{" {
548
605
// We're right after '{' - command context
549
606
if let Some((_, adjusted_span, _)) = handle_block_prefix(&prefix, span) {
550
550
-
return Some(CompletionContext {
607
607
+
return vec![CompletionContext {
551
608
kind: CompletionKind::Command {
552
609
parent_command: None,
553
610
},
554
611
prefix: String::new(),
555
612
span: adjusted_span,
556
556
-
});
613
613
+
}];
557
614
}
558
615
} else {
559
616
match shape {
560
617
// Special case: Check if we're completing a cell path where the Variable and field are in separate shapes
561
618
_ if { idx > 0 && matches!(shape, FlatShape::String) } => {
562
562
-
if let Some(ctx) = handle_variable_string_shape(
619
619
+
let contexts = handle_variable_string_shape(
563
620
input,
564
621
shapes,
565
622
working_set,
623
623
+
engine_guard,
566
624
idx,
567
625
&prefix,
568
626
span,
569
627
local_span,
570
628
global_offset,
571
571
-
) {
572
572
-
return Some(ctx);
629
629
+
);
630
630
+
if !contexts.is_empty() {
631
631
+
return contexts;
573
632
}
574
633
}
575
634
// Special case: Check if we're completing a cell path where the Variable and dot are in separate shapes
···
578
637
trimmed_prefix.starts_with('.') && idx > 0
579
638
} =>
580
639
{
581
581
-
if let Some(ctx) = handle_dot_shape(
640
640
+
let contexts = handle_dot_shape(
582
641
input,
583
642
shapes,
584
643
idx,
···
586
645
span,
587
646
local_span,
588
647
global_offset,
589
589
-
) {
590
590
-
return Some(ctx);
648
648
+
);
649
649
+
if !contexts.is_empty() {
650
650
+
return contexts;
591
651
}
592
652
}
593
653
_ if {
···
608
668
// Calculate span for the cell path member being completed
609
669
let prefix_byte_len = cell_prefix.len();
610
670
let cell_span_start = span.end.saturating_sub(prefix_byte_len);
611
611
-
return Some(CompletionContext {
671
671
+
return vec![CompletionContext {
612
672
kind: CompletionKind::CellPath {
613
673
var_id,
614
674
path_so_far: path_so_far
···
618
678
},
619
679
prefix: cell_prefix.to_string(),
620
680
span: Span::new(cell_span_start, span.end),
621
621
-
});
681
681
+
}];
622
682
} else {
623
683
// Unknown variable, fall back to variable completion
624
684
let var_prefix = trimmed_prefix[1..].to_string();
625
625
-
return Some(CompletionContext {
685
685
+
return vec![CompletionContext {
626
686
kind: CompletionKind::Variable,
627
687
prefix: var_prefix,
628
688
span,
629
629
-
});
689
689
+
}];
630
690
}
631
691
} else {
632
692
// Variable completion context (no dot)
···
635
695
} else {
636
696
String::new()
637
697
};
638
638
-
return Some(CompletionContext {
698
698
+
return vec![CompletionContext {
639
699
kind: CompletionKind::Variable,
640
700
prefix: var_prefix,
641
701
span,
642
642
-
});
702
702
+
}];
643
703
}
644
704
}
645
705
_ if is_command_shape(input, shape, local_span) => {
646
706
let (full_prefix, full_span) =
647
707
build_command_prefix(input, shapes, idx, span, &prefix, global_offset);
648
648
-
return Some(CompletionContext {
708
708
+
return vec![CompletionContext {
649
709
kind: CompletionKind::Command {
650
710
parent_command: None,
651
711
},
652
712
prefix: full_prefix,
653
713
span: full_span,
654
654
-
});
714
714
+
}];
655
715
}
656
716
FlatShape::Block | FlatShape::Closure => {
657
657
-
if let Some(ctx) = handle_block_or_closure(
717
717
+
let contexts = handle_block_or_closure(
658
718
input,
659
719
shapes,
660
720
working_set,
721
721
+
engine_guard,
661
722
&prefix,
662
723
span,
663
724
shape.as_str().trim_start_matches("shape_"),
664
725
idx,
665
726
local_span,
666
727
global_offset,
667
667
-
) {
668
668
-
return Some(ctx);
728
728
+
);
729
729
+
if !contexts.is_empty() {
730
730
+
return contexts;
669
731
}
670
732
}
671
733
FlatShape::Variable(var_id) => {
···
678
740
{
679
741
let prefix_byte_len = cell_prefix.len();
680
742
let cell_span_start = span.end.saturating_sub(prefix_byte_len);
681
681
-
return Some(CompletionContext {
743
743
+
return vec![CompletionContext {
682
744
kind: CompletionKind::CellPath {
683
745
var_id: *var_id,
684
746
path_so_far: path_so_far
···
688
750
},
689
751
prefix: cell_prefix.to_string(),
690
752
span: Span::new(cell_span_start, span.end),
691
691
-
});
753
753
+
}];
692
754
} else {
693
755
// Simple variable completion
694
756
let var_prefix = trimmed_prefix[1..].to_string();
695
695
-
return Some(CompletionContext {
757
757
+
return vec![CompletionContext {
696
758
kind: CompletionKind::Variable,
697
759
prefix: var_prefix,
698
760
span,
699
699
-
});
761
761
+
}];
700
762
}
701
763
} else {
702
764
// Fallback to argument context if no $ found
703
703
-
return Some(CompletionContext {
765
765
+
return vec![CompletionContext {
704
766
kind: CompletionKind::Argument,
705
767
prefix: prefix.to_string(),
706
768
span,
707
707
-
});
769
769
+
}];
708
770
}
709
771
}
710
772
_ => {
···
719
781
if let Some(var_id) = var_id {
720
782
let prefix_byte_len = cell_prefix.len();
721
783
let cell_span_start = span.end.saturating_sub(prefix_byte_len);
722
722
-
return Some(CompletionContext {
784
784
+
return vec![CompletionContext {
723
785
kind: CompletionKind::CellPath {
724
786
var_id,
725
787
path_so_far: path_so_far
···
729
791
},
730
792
prefix: cell_prefix.to_string(),
731
793
span: Span::new(cell_span_start, span.end),
732
732
-
});
794
794
+
}];
733
795
} else {
734
796
let var_prefix = trimmed_prefix[1..].to_string();
735
735
-
return Some(CompletionContext {
797
797
+
return vec![CompletionContext {
736
798
kind: CompletionKind::Variable,
737
799
prefix: var_prefix,
738
800
span,
739
739
-
});
801
801
+
}];
740
802
}
741
803
} else {
742
804
// Simple variable completion
···
745
807
} else {
746
808
String::new()
747
809
};
748
748
-
return Some(CompletionContext {
810
810
+
return vec![CompletionContext {
749
811
kind: CompletionKind::Variable,
750
812
prefix: var_prefix,
751
813
span,
752
752
-
});
814
814
+
}];
753
815
}
754
816
} else {
755
817
// Use helper to determine flag or argument context
756
756
-
return Some(determine_flag_or_argument_context(
818
818
+
return determine_flag_or_argument_context(
757
819
input,
758
820
shapes,
759
821
&trimmed_prefix,
···
761
823
local_span,
762
824
span,
763
825
global_offset,
764
764
-
));
826
826
+
working_set,
827
827
+
engine_guard,
828
828
+
);
765
829
}
766
830
}
767
831
}
···
769
833
break;
770
834
}
771
835
}
772
772
-
None
836
836
+
Vec::new()
773
837
}
774
838
775
839
pub fn determine_context_fallback(
···
855
919
"[completion] Right after command {cmd_name:?}, setting CommandArgument context with arg_index: {arg_count}"
856
920
);
857
921
858
858
-
context.push(CompletionContext {
859
859
-
kind: CompletionKind::CommandArgument {
860
860
-
command_name: cmd_name.clone(),
861
861
-
arg_index: arg_count,
862
862
-
},
863
863
-
prefix: String::new(),
864
864
-
span: Span::new(byte_pos, byte_pos),
865
865
-
});
922
922
+
// Use helper to create CommandArgument context(s) - may include subcommand context
923
923
+
let arg_contexts = create_command_argument_contexts(
924
924
+
cmd_name.clone(),
925
925
+
arg_count,
926
926
+
String::new(),
927
927
+
Span::new(byte_pos, byte_pos),
928
928
+
working_set,
929
929
+
engine_guard,
930
930
+
);
931
931
+
context.extend(arg_contexts);
866
932
}
867
933
}
868
934
// No positional arguments
···
1045
1111
}
1046
1112
}
1047
1113
if let Some(cmd_name) = found_cmd {
1048
1048
-
vec![CompletionContext {
1049
1049
-
kind: CompletionKind::CommandArgument {
1050
1050
-
command_name: cmd_name,
1051
1051
-
arg_index: arg_count,
1052
1052
-
},
1053
1053
-
prefix: trimmed_word.to_string(),
1054
1054
-
span: Span::new(last_word_start, byte_pos),
1055
1055
-
}]
1114
1114
+
create_command_argument_contexts(
1115
1115
+
cmd_name,
1116
1116
+
arg_count,
1117
1117
+
trimmed_word.to_string(),
1118
1118
+
Span::new(last_word_start, byte_pos),
1119
1119
+
working_set,
1120
1120
+
engine_guard,
1121
1121
+
)
1056
1122
} else {
1057
1123
vec![CompletionContext {
1058
1124
kind: CompletionKind::Argument,
···
1073
1139
global_offset: usize,
1074
1140
) -> Vec<CompletionContext> {
1075
1141
// First try to determine context from shapes
1076
1076
-
if let Some(ctx) =
1077
1077
-
determine_context_from_shape(input, shapes, working_set, byte_pos, global_offset)
1078
1078
-
{
1079
1079
-
return vec![ctx];
1142
1142
+
let contexts = determine_context_from_shape(
1143
1143
+
input,
1144
1144
+
shapes,
1145
1145
+
working_set,
1146
1146
+
engine_guard,
1147
1147
+
byte_pos,
1148
1148
+
global_offset,
1149
1149
+
);
1150
1150
+
if !contexts.is_empty() {
1151
1151
+
return contexts;
1080
1152
}
1081
1153
1082
1154
// Fallback to token-based context determination
+1
-39
src/completion/suggestions.rs
···
194
194
pub fn generate_command_argument_suggestions(
195
195
input: &str,
196
196
engine_guard: &EngineState,
197
197
-
working_set: &StateWorkingSet,
197
197
+
_working_set: &StateWorkingSet,
198
198
prefix: String,
199
199
span: Span,
200
200
command_name: String,
···
206
206
);
207
207
208
208
let mut suggestions = Vec::new();
209
209
-
210
210
-
// If we're at argument index 0, check if the command has subcommands and add them
211
211
-
if arg_index == 0 {
212
212
-
let parent_prefix = format!("{} ", command_name);
213
213
-
let search_prefix = if prefix.is_empty() {
214
214
-
parent_prefix.clone()
215
215
-
} else {
216
216
-
format!("{}{}", parent_prefix, prefix)
217
217
-
};
218
218
-
219
219
-
let subcommands = working_set
220
220
-
.find_commands_by_predicate(|value| value.starts_with(search_prefix.as_bytes()), true);
221
221
-
222
222
-
if !subcommands.is_empty() {
223
223
-
// Command has subcommands - add them to suggestions
224
224
-
console_log!(
225
225
-
"[completion] Command {command_name:?} has subcommands, adding subcommand suggestions for prefix: {prefix:?}"
226
226
-
);
227
227
-
let span = to_char_span(input, span);
228
228
-
for (_, name, desc, _) in subcommands {
229
229
-
let name_str = String::from_utf8_lossy(&name).to_string();
230
230
-
if let Some(subcommand_name) = name_str.strip_prefix(&parent_prefix) {
231
231
-
suggestions.push(Suggestion {
232
232
-
rendered: {
233
233
-
let name_colored =
234
234
-
ansi_term::Color::Green.bold().paint(subcommand_name);
235
235
-
let desc_str = desc.as_deref().unwrap_or("<no description>");
236
236
-
format!("{name_colored} {desc_str}")
237
237
-
},
238
238
-
name: subcommand_name.to_string(),
239
239
-
description: desc.map(|d| d.to_string()),
240
240
-
span_start: span.start,
241
241
-
span_end: span.end,
242
242
-
});
243
243
-
}
244
244
-
}
245
245
-
}
246
246
-
}
247
209
248
210
if let Some(signature) = get_command_signature(engine_guard, &command_name) {
249
211
// First, check if we're completing an argument for a flag