Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Zsh shell autocompletions #43970

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 10 commits into from
Closed

Zsh shell autocompletions #43970

wants to merge 10 commits into from

Conversation

adhocore
Copy link
Contributor

@adhocore adhocore commented Nov 9, 2021

Q A
Branch? 5.4
Bug fix? no
New feature? yes
Deprecations? no
Tickets Fix #43592
License MIT
Doc PR -

Usage

Setup/Install

In zsh terminal run:

bin/console completion zsh > "$fpath[1]/console"

which will add dumped zsh completion helper in first fpath (zsh's functions autoload path).
Then reload shell or just do source "$fpath[1]/console".


Here's a simple script sfzsh that I'm using while adding the zsh autocompletion support as a testbed:

Toggle code

#!/bin/env php
<?php

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Descriptor\ApplicationDescription;
use Symfony\Component\Console\Helper\DescriptorHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

putenv('SYMFONY_COMPLETION_DEBUG=1');

$app = new Application('sfzsh', '0.0.1');

// test1
$app->add(new class() extends Command {
    protected static $defaultName = 'test1';
    protected static $defaultDescription = 'Test one cmd';

    protected function configure(): void
    {
        $this
            ->addOption('t1o1', '1', InputOption::VALUE_REQUIRED, 't1o1 str val')
            ->addOption('t1o2', '2', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 't1o2 array val')
            ->addArgument('php', InputArgument::REQUIRED, 'PHP file in current dir')
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        return 0;
    }

    public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
    {
        if ($input->mustSuggestOptionValuesFor('t1o1')) {
            $suggestions->suggestValues(['t1o1.v1', 't1o1.v2']);

            return;
        }

        if ($input->mustSuggestOptionValuesFor('t1o2')) {
            $suggestions->suggestValues(['t1o2.v1', 't1o2.v2']);

            return;
        }

        if ($input->mustSuggestArgumentValuesFor('php')) {
            $suggestions->suggestValues([system('ls -1 *.php')]);

            return;
        }
    }
});

// test2
$app->add(new class() extends Command {
    protected static $defaultName = 'test2';
    protected static $defaultDescription = 'Test two cmd';

    protected function configure(): void
    {
        $this
            ->addOption('t2o1', '1', InputOption::VALUE_REQUIRED, 't2o1 str val')
            ->addOption('t2o2', '2', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 't2o2 array val')
            ->addArgument('dir', InputArgument::REQUIRED, 'subdir in current dir')
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        return 0;
    }

    public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
    {
        if ($input->mustSuggestOptionValuesFor('t2o1')) {
            $suggestions->suggestValues(['t2o1.v1', 't2o1.v2']);

            return;
        }

        if ($input->mustSuggestOptionValuesFor('t2o2')) {
            $suggestions->suggestValues(['t2o2.v1', 't2o2.v2']);

            return;
        }

        if ($input->mustSuggestArgumentValuesFor('dir')) {
            $suggestions->suggestValues([system('ls -1d */')]);

            return;
        }
    }
});

$app->run();

sfzsh completion zsh > "${fpath[1]}/sfzsh"
. "${fpath[1]}/sfzsh"

Autocomplete commands

sfzsh [TAB]

image

Shortlisting works too: sfzsh te[TAB] autocompletes for test1 and test2

Fuzzy match works too: sfzsh t1[TAB] autocompletes test1

Autocomplete options

 sfzsh test1 --[TAB]

image

Autocomplete option values

sfzsh test1 --t1o1 [TAB]

image

= separator works too: sfzsh test1 --t1o1=[TAB]

Autocomplete args

sfzsh test1 [TAB]

|_ (autocompletes *.php files in current dir)

sfzsh test2 [TAB]

|_ (autocompletes subdirs in current dir)

@adhocore
Copy link
Contributor Author

adhocore commented Nov 9, 2021

maybe a false positive in fabbot ci for typo?
"help": "The <info>completion</> command dumps the shell completion script required\nto
is suggested to be
"help": "The <info>completion</> command dumps the shell completion script required\not

the \n is LF and not not

@stof
Copy link
Member

stof commented Nov 9, 2021

Yeah, this definitely looks like a false positive

@adhocore
Copy link
Contributor Author

adhocore commented Nov 9, 2021

@stof i used auto sync upstream from github ui, so any idea how can i remove the Merge commit now?

@stof
Copy link
Member

stof commented Nov 9, 2021

Copy link
Member

@GromNaN GromNaN left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that you added comments in the script; especially since we don't have a way to write test on it for now.

I have a suggestion to avoid shell-specific code in Application class.

$suggestions->suggestValues(array_filter(array_map(function (Command $command) {
return $command->isHidden() ? null : $command->getName();
$suggestions->suggestValues(array_filter(array_map(function (Command $command) use ($input) {
return $command->isHidden() ? null : $command->getName().($input->isShell('zsh') ? "\t".$command->getDescription() : '');
Copy link
Member

@GromNaN GromNaN Nov 10, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Testing if shell is zsh seems very specific for this place.

We could use a DTO. The ZshCompletionOutput would format the value with its specificities. By implementing the __toString method, the BashCompletionOutput would not require any change.

namespace Symfony\Component\Console\Completion;

final class SuggestedValue {
    private $value;
    private $description;

    public function __construct(string $value, string $description = null) {
        $this->value = $value;
        $this->description = $description;
    }

    public function __toString(): string {
        return $this->value;
    }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or maybe some abstraction to tell/handle if the shell supports description for autocompleted suggestion

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would add complexity in all commands that implements completion. I'm not sure this is something we want.
Each command should set suggestions values, optionally with a description using this DTO.


# Ensure atleast 1 input
if [ "${i}" = "" ]; then
requestComp="${requestComp} -i\" \""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes the CompletionInput::getCurrentValue() equals to a space? it would be better to skip the value entirely.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think it still requires -i option equal or 1 more than -c option that's why

Copy link
Member

@GromNaN GromNaN Nov 10, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then, does it work with an empty string?

Suggested change
requestComp="${requestComp} -i\" \""
requestComp="${requestComp} -i\"\"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no it can't be empty

Co-authored-by: JΓ©rΓ΄me Tamarelle <[email protected]>
@fabpot fabpot modified the milestones: 5.4, 6.1 Nov 16, 2021
@fabpot fabpot modified the milestones: 6.1, 6.2 May 20, 2022
@fabpot
Copy link
Member

fabpot commented Jul 20, 2022

@adhocore @GromNaN What's the status of this PR?
I see that, at least, it needs to be rebased on current 6.2.

@GromNaN
Copy link
Member

GromNaN commented Jul 21, 2022

Hello @adhocore. I created a new PR to rebase your work and change the way descriptions are output.
Please feel free to review #47018.

@GromNaN GromNaN closed this Jul 21, 2022
fabpot added a commit that referenced this pull request Jul 22, 2022
This PR was merged into the 6.2 branch.

Discussion
----------

[Console] Zsh shell autocompletion

| Q             | A
| ------------- | ---
| Branch?       | 6.2
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Tickets       | Fix #43592
| License       | MIT
| Doc PR        | todo

Continuation of #43970

* Rebased, including bug fixes.
* Added description to completion values, implemented for commands and options names. (should be used for fish also)
```
% bin/console [TAB]
about                       -- Display information about the current project
assets:install              -- Install bundle's web assets under a public directory
cache:clear                 -- Clear the cache
cache:pool:clear            -- Clear cache pools
```

Commits
-------

3c2e1a4 Finish Zsh completion
405f207 Add zsh completion
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Console] Implement completion for zsh
5 participants