Repository: nushell/nushell (Rust)

Issue:
Alias completions with external completers does not work anymore (after #8123)
### Describe the bug

I use fish as a completer with the exact same setup as [in the docs](https://www.nushell.sh/book/custom_completions.html#external-completions).

Before, in 0.76 I could `alias k = kubectl` and get automcompletion for the resource names from the fish completion file (`kubectl completion fish`)

In 0.77, this completion does not work anymore and I have to use the full command to get completion.



### How to reproduce

1. `alias k = kubectl`
2. `> k get pods <TAB>`

(0. Have kubectl installed and external completion setup)

### Expected behavior

Autocomplete as if I typed `kubectl get pods`.

### Screenshots

![image](https://user-images.githubusercontent.com/51253506/225660583-5ff53664-1733-4bdc-b312-8cd98c3dba9d.png)

(Not screenshotting expected behavior because I'm at work)

### Configuration

| key                | value                                                            |
| ------------------ | ---------------------------------------------------------------- |
| version            | 0.77.0                                                           |
| branch             | makepkg                                                          |
| commit_hash        | fd09609b44b53d41ac7929e90708cdeba7bfb571                         |
| build_os           | linux-x86_64                                                     |
| build_target       | x86_64-unknown-linux-gnu                                         |
| rust_version       | rustc 1.68.0 (2c8cc3432 2023-03-06) (Arch Linux rust 1:1.68.0-1) |
| cargo_version      | cargo 1.68.0                                                     |
| build_time         | 2023-03-15 09:27:56 +00:00                                       |
| build_rust_channel | release                                                          |
| features           | default, zip                                                     |
| installed_plugins  |                                                                  |


### Additional context

_No response_


Discussion:
I think this is a subissue of the regression observed in #8471. So the changes to the aliases do not play nice with the completions yet.
> I think this is a subissue of the regression observed in #8471. So the changes to the aliases do not play nice with the completions yet.

The issue you mentioned is within 0.76.1, so the new alias changes were not in place.
I also have this issue and I noticed that aliases get expanded wrongly when passed to the external completer.
For example, I have an alias `alias gp = git push`.
I noticed parameters that are passed to the completer in this case are `gp push` instead of `git push`. 
So it seems the alias is expanded, but the first part is not replaced with the first part of the expanded alias.

I have the following (ugly) workaround in my config and this seems to work (for carapace):

```
let carapace_completer = {|spans|
    let has_alias = ($nu.scope.aliases | where name == $spans.0)
    let spans = (if not ($has_alias | is-empty) {
      # put the first word of the expanded alias first in the span
      $spans | skip 1 | prepend ($has_alias | get expansion | split words | get 0)
    } else { $spans })
    carapace $spans.0 nushell $spans | from json

```

Heho,

any progress on this issue?

Kind regards
Alexander
> I also have this issue and I noticed that aliases get expanded wrongly when passed to the external completer. For example, I have an alias `alias gp = git push`. I noticed parameters that are passed to the completer in this case are `gp push` instead of `git push`. So it seems the alias is expanded, but the first part is not replaced with the first part of the expanded alias.
> 
> I have the following (ugly) workaround in my config and this seems to work (for carapace):
> 
> ```
> let carapace_completer = {|spans|
>     let has_alias = ($nu.scope.aliases | where name == $spans.0)
>     let spans = (if not ($has_alias | is-empty) {
>       # put the first word of the expanded alias first in the span
>       $spans | skip 1 | prepend ($has_alias | get expansion | split words | get 0)
>     } else { $spans })
>     carapace $spans.0 nushell $spans | from json
> ```

Heho,

fyi. This snippet is not working anymore. In the cookbook is a running [workaround](https://www.nushell.sh/cookbook/external_completers.html#alias-completions).

Regards
Alexander
Hello, the workaround doesn't work if alias use variable

Basically, if I have a alias like this:
```nushell
alias aws_test = aws --profile "admin-test"
```

Then carapace (external completer) would provide completions for aws as expected

However, if I define alias like this:
```nushell
let aws_admin_test_profile = "admin-test"
alias aws_test = aws --profile $aws_admin_test_profile
```

Then completions wouldn't work.

That's, I guess, is expected, because the workaround replaces alias with it's "expansion". So in the second case caparace tries to expand "aws --profile $aws_admin_test_profile" and fails to do so. 

Would be very nice to have a workaround for this case too, however I couldn't come up with one myself
I suppose it connects with `completions.algorithm = fuzzy`, because only in such case completion on one letter alias isn't working.
Lifehack:) Try to do two spaces after command, like this: `g  ` and then press tab
If there isn't a definitive 'fix' or planned refactoring about this, can the current workaround of expanding the aliases just be upstreamed / embedded to the Rust codebase itself? External completers could just receive expanded form in the first place. Or is there something making this hard to implement in the Rust src and can only be solved in `config.nu`?
I found the external completer now does not work with custom commands (`def`s) either.
> If there isn't a definitive 'fix' or planned refactoring about this, can the current workaround of expanding the aliases just be upstreamed / embedded to the Rust codebase itself? External completers could just receive expanded form in the first place. Or is there something making this hard to implement in the Rust src and can only be solved in `config.nu`?

I'm gonna bump this while expanding on what @WieeRd said. In Fish Shell aliases are technically just a wrapper around fish's `function`:

> alias is a simple wrapper for the function builtin, which creates a function wrapping a command. It has similar syntax to POSIX shell alias. For other uses, it is recommended to define a [function](https://fishshell.com/docs/current/cmds/function.html).
> [ ... ]
> fish marks functions that have been created by alias by including the command used to create them in the function description.

And I assume that's one component of how fish's aliases can have the completions of the commands they wrap, or maybe it is *the* way.

I could be wrong, but maybe Nushell doesn't do this, and that an `alias` in Nushell is something other than a command defined with `def`? If it does, then I guess the bug is somewhere related to how `alias` wraps what's passed to it.

**EDIT:**

...or, as, you know, implied by what @aserowy posted: aliases aren't being expanded properly under the hood.
I've been trying out ways to get the alias expanded before being sent to the external completers. Based on some very limited testing I found that the expansion seems to work after applying the changes that can be found here: https://github.com/simonborje/nushell/commit/4f71467508272601a9a7800e94407f178f7987a9.
I've only tested with a simple "external completer" that simply prints the arguments passed to it but it seems to work ok.

```
alias glo = git log --oneline

// Without patch
glo test<TAB>   =>   external completer called with ["glo" "log" "--oneline" "test"}

// With patch
glo test<TAB>   =>   external completer called with ["git" "log" "--oneline" "test"}
```
> I've been trying out ways to get the alias expanded before being sent to the external completers. Based on some very limited testing I found that the expansion seems to work after applying the changes that can be found here: [simonborje@4f71467](https://github.com/simonborje/nushell/commit/4f71467508272601a9a7800e94407f178f7987a9).`

Looks like a simple way to fix it! Maybe you could open a PR and we could see where it goes from there?

Personally, I've re-worked my workflow to dance around the issue and am mostly using custom completions for all commands I often use. I would still be interested in bringing back alias completion to my shell :)



Patch:
diff --git a/crates/nu-cli/src/completions/completer.rs b/crates/nu-cli/src/completions/completer.rs
index c237a31af6b1c..0e208ca5ebceb 100644
--- a/crates/nu-cli/src/completions/completer.rs
+++ b/crates/nu-cli/src/completions/completer.rs
@@ -6,7 +6,7 @@ use crate::completions::{
 };
 use nu_color_config::{color_record_to_nustyle, lookup_ansi_color_style};
 use nu_engine::eval_block;
-use nu_parser::{flatten_expression, parse, parse_module_file_or_dir};
+use nu_parser::{FlatShape, flatten_expression, parse, parse_module_file_or_dir};
 use nu_protocol::{
     Completion, PipelineData, Span, Type, Value,
     ast::{Argument, Block, Expr, Expression, FindMapResult, ListItem, Traverse},
@@ -512,8 +512,13 @@ impl NuCompleter {
                             let mut text_spans: Vec<String> =
                                 flatten_expression(working_set, element_expression)
                                     .iter()
-                                    .map(|(span, _)| {
-                                        let bytes = working_set.get_span_contents(*span);
+                                    .map(|(span, shape)| {
+                                        let bytes = if let FlatShape::External(span) = shape {
+                                            // Use expanded alias span
+                                            working_set.get_span_contents(**span)
+                                        } else {
+                                            working_set.get_span_contents(*span)
+                                        };
                                         String::from_utf8_lossy(bytes).to_string()
                                     })
                                     .collect();


Test patch:
diff --git a/crates/nu-cli/tests/completions/mod.rs b/crates/nu-cli/tests/completions/mod.rs
index 21980c66aa11d..397b89e727261 100644
--- a/crates/nu-cli/tests/completions/mod.rs
+++ b/crates/nu-cli/tests/completions/mod.rs
@@ -2723,6 +2723,46 @@ fn cellpath_assignment_operator_completions() {
     match_suggestions(&expected, &suggestions);
 }
 
+#[test]
+fn alias_expansion_for_external_completions() {
+    let (_, _, mut engine, mut stack) = new_engine();
+    let command = r#"alias example_alias = example_cmd arg1 arg2"#;
+    assert!(support::merge_input(command.as_bytes(), &mut engine, &mut stack).is_ok());
+
+    // Define an external completer that returns the arguments passed to it
+    let command = r#"$env.config.completions.external.completer = {|s| $s }"#;
+    assert!(support::merge_input(command.as_bytes(), &mut engine, &mut stack).is_ok());
+
+    let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
+    let completion_str = "example_alias extra_arg";
+    let suggestions = completer.complete(completion_str, completion_str.len());
+    let expected: Vec<_> = vec!["example_cmd", "arg1", "arg2", "extra_arg"];
+    match_suggestions(&expected, &suggestions);
+}
+
+#[test]
+fn nested_alias_expansion_for_external_completions() {
+    let (_, _, mut engine, mut stack) = new_engine();
+    let command = r#"alias example_alias = example_cmd arg1 arg2; alias nested_alias = example_alias nested_alias_arg"#;
+    assert!(support::merge_input(command.as_bytes(), &mut engine, &mut stack).is_ok());
+
+    // Define an external completer that returns the arguments passed to it
+    let command = r#"$env.config.completions.external.completer = {|s| $s }"#;
+    assert!(support::merge_input(command.as_bytes(), &mut engine, &mut stack).is_ok());
+
+    let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
+    let completion_str = "nested_alias extra_arg";
+    let suggestions = completer.complete(completion_str, completion_str.len());
+    let expected: Vec<_> = vec![
+        "example_cmd",
+        "arg1",
+        "arg2",
+        "nested_alias_arg",
+        "extra_arg",
+    ];
+    match_suggestions(&expected, &suggestions);
+}
+
 // TODO: type inference
 #[ignore]
 #[rstest]