Skip to content

Commit 6cdee9b

Browse files
committed
Add functionality to defer to shell built-in filename completion
This change allows user to take advantage of the file/folder name completion functions that are available under BASH and ZSH normally. Path completion using ShellPathCompletion is currently only relative to the current working directory and cannot be customised. Ideally I'd like to be able to set the working directory for path completion, and also to specify globs for matching say, only .txt files. Globs are relatively straight forward to add using the _filedir/_path_files shell functions we're using, however I couldn't find and nice way to change the working directory for the functions without complexity or side-effects. I've implemented deferring to the shell using an exit code. I'm not entirely happy that this introduces code into the completion hooks that is specific to ShellPathCompletion, however I'm less keen on the idea of allowing dynamic injection of code into the shell hooks at the moment; that just seems like a bad idea for some reason. Summary of other changes: * Adds interface for Completion * BC break: removes CompletionCompletionHandler::isGlobal as it was fuzzy in meaning and can be represented better with an explicit comparison: $completion->getCommandName() == CompletionInterface::ALL_COMMANDS
1 parent cedad93 commit 6cdee9b

File tree

5 files changed

+144
-23
lines changed

5 files changed

+144
-23
lines changed

src/Completion.php

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,10 @@
44
namespace Stecman\Component\Symfony\Console\BashCompletion;
55

66

7-
class Completion {
8-
9-
const ALL_COMMANDS = null;
10-
11-
const TYPE_OPTION = 'option';
12-
const TYPE_ARGUMENT = 'argument';
7+
use Stecman\Component\Symfony\Console\BashCompletion\Completion\CompletionInterface;
138

9+
class Completion implements CompletionInterface
10+
{
1411
/**
1512
* The option/argument name this helper should be run for
1613
* @var string
@@ -31,14 +28,15 @@ class Completion {
3128
protected $targetName;
3229

3330
/**
34-
* Array or Closure
35-
* @var mixed
31+
* Array of completion results or a callback to generate completion results
32+
* The callback can be in any form accepted by call_user_func
33+
* @var callable|array
3634
*/
3735
protected $completion;
3836

3937
public static function makeGlobalHandler($targetName, $type, $completion)
4038
{
41-
return new Completion(self::ALL_COMMANDS, $targetName, $type, $completion);
39+
return new Completion(CompletionInterface::ALL_COMMANDS, $targetName, $type, $completion);
4240
}
4341

4442
public function __construct($commandName, $targetName, $type, $completion)
@@ -49,11 +47,6 @@ public function __construct($commandName, $targetName, $type, $completion)
4947
$this->completion = $completion;
5048
}
5149

52-
public function isGlobal()
53-
{
54-
return $this->commandName == '';
55-
}
56-
5750
/**
5851
* Return the result of the completion helper
5952
* @return array|mixed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
4+
namespace Stecman\Component\Symfony\Console\BashCompletion\Completion;
5+
6+
7+
interface CompletionInterface
8+
{
9+
// Sugar for indicating that a Completion should run for all command names
10+
// Intended to avoid a meaningless null parameter in the constructors of implementing classes
11+
const ALL_COMMANDS = null;
12+
13+
const TYPE_OPTION = 'option';
14+
const TYPE_ARGUMENT = 'argument';
15+
16+
/**
17+
* Return the type of input completion should be run for
18+
*
19+
* @see \Symfony\Component\Console\Command\Command::addArgument
20+
* @see \Symfony\Component\Console\Command\Command::addOption
21+
* @return string - one of the CompletionInterface::TYPE_* constants
22+
*/
23+
public function getType();
24+
25+
/**
26+
* Return the name of the command completion should be run for
27+
* If the return value is CompletionInterface::ALL_COMMANDS, the completion will be run for any command name
28+
*
29+
* @see \Symfony\Component\Console\Command\Command::setName
30+
* @return string|null
31+
*/
32+
public function getCommandName();
33+
34+
/**
35+
* Return the option/argument name the completion should be run for
36+
* CompletionInterface::getType determines whether the target name refers to an option or an argument
37+
*
38+
* @return string
39+
*/
40+
public function getTargetName();
41+
42+
/**
43+
* Execute the completion
44+
*
45+
* Returns an array of possible completions or null to indicate that no completions are available.
46+
*
47+
* @return array|null
48+
*/
49+
public function run();
50+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
4+
namespace Stecman\Component\Symfony\Console\BashCompletion\Completion;
5+
6+
/**
7+
* Shell Path Completion
8+
*
9+
* Defers completion to the calling shell's built-in path completion functionality.
10+
*/
11+
class ShellPathCompletion implements CompletionInterface
12+
{
13+
/**
14+
* Exit code set up to trigger path completion in the completion hooks
15+
* @see Stecman\Component\Symfony\Console\BashCompletion\HookFactory
16+
*/
17+
const PATH_COMPLETION_EXIT_CODE = 200;
18+
19+
protected $type;
20+
21+
protected $commandName;
22+
23+
protected $targetName;
24+
25+
public function __construct($commandName, $targetName, $type)
26+
{
27+
$this->commandName = $commandName;
28+
$this->targetName = $targetName;
29+
$this->type = $type;
30+
}
31+
32+
/**
33+
* @inheritdoc
34+
*/
35+
public function getType()
36+
{
37+
return $this->type;
38+
}
39+
40+
/**
41+
* @inheritdoc
42+
*/
43+
public function getCommandName()
44+
{
45+
return $this->commandName;
46+
}
47+
48+
/**
49+
* @inheritdoc
50+
*/
51+
public function getTargetName()
52+
{
53+
return $this->targetName;
54+
}
55+
56+
/**
57+
* Exit with the status code configured to defer completion to the shell
58+
*
59+
* @see \Stecman\Component\Symfony\Console\BashCompletion\HookFactory::$hooks
60+
*/
61+
public function run()
62+
{
63+
exit(self::PATH_COMPLETION_EXIT_CODE);
64+
}
65+
}

src/CompletionHandler.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Stecman\Component\Symfony\Console\BashCompletion;
44

5+
use Stecman\Component\Symfony\Console\BashCompletion\Completion\CompletionInterface;
56
use Symfony\Component\Console\Application as BaseApplication;
67
use Symfony\Component\Console\Command\Command as BaseCommand;
78
use Symfony\Component\Console\Input\ArrayInput;
@@ -59,7 +60,7 @@ public function addHandlers($array)
5960
$this->helpers = array_merge($this->helpers, $array);
6061
}
6162

62-
public function addHandler(Completion $helper)
63+
public function addHandler(CompletionInterface $helper)
6364
{
6465
$this->helpers[] = $helper;
6566
}
@@ -256,7 +257,7 @@ protected function formatArguments(BaseCommand $cmd)
256257
/**
257258
* @param $name
258259
* @param string $type
259-
* @return Completion
260+
* @return CompletionInterface
260261
*/
261262
protected function getCompletionHelper($name, $type)
262263
{
@@ -265,7 +266,7 @@ protected function getCompletionHelper($name, $type)
265266
continue;
266267
}
267268

268-
if ($helper->isGlobal() || $helper->getCommandName() == $this->command->getName()) {
269+
if ($helper->getCommandName() == CompletionInterface::ALL_COMMANDS || $helper->getCommandName() == $this->command->getName()) {
269270
if ($helper->getTargetName() == $name) {
270271
return $helper;
271272
}

src/HookFactory.php

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,22 @@ function %%function_name%% {
3737
RESULT="$(%%completion_command%%)";
3838
STATUS=$?;
3939
40+
local cur;
41+
_get_comp_words_by_ref -n : cur;
42+
43+
# Check if shell provided path completion is requested
44+
# @see Completion\ShellPathCompletion
45+
if [ $STATUS -eq 200 ]; then
46+
_filedir;
47+
return 0;
48+
4049
# Bail out if PHP didn't exit cleanly
41-
if [ $STATUS -ne 0 ]; then
50+
elif [ $STATUS -ne 0 ]; then
4251
echo -e "$RESULT";
4352
return $?;
4453
fi;
4554
46-
local cur;
47-
_get_comp_words_by_ref -n : cur;
48-
49-
COMPREPLY=(`compgen -W "$RESULT" -- \$cur`);
55+
COMPREPLY=(`compgen -W "$RESULT" -- $cur`);
5056
5157
__ltrim_colon_completions "$cur";
5258
};
@@ -70,8 +76,14 @@ function %%function_name%% {
7076
RESULT=("${(@f)$( %%completion_command%% )}")
7177
STATUS=$?;
7278
79+
# Check if shell provided path completion is requested
80+
# @see Completion\ShellPathCompletion
81+
if [ $STATUS -eq 200 ]; then
82+
_path_files;
83+
return 0;
84+
7385
# Bail out if PHP didn't exit cleanly
74-
if [ $STATUS -ne 0 ]; then
86+
elif [ $STATUS -ne 0 ]; then
7587
echo -e "$RESULT";
7688
return $?;
7789
fi;

0 commit comments

Comments
 (0)