Skip to content

Commit

Permalink
commands have completion for arguments, early stage file matcher and
Browse files Browse the repository at this point in the history
operator matcher added
  • Loading branch information
Markcial committed Feb 27, 2015
1 parent 442e86b commit 04471e1
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 9 deletions.
12 changes: 12 additions & 0 deletions src/Psy/TabCompletion/Matcher/AbstractContextAwareMatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,18 @@ protected function getVariable($var)
return $this->context->get($var);
}

/**
* Returns true if the variable exists.
*
* @param $var Variable name
*
* @return bool
*/
protected function hasVariable($var)
{
return (bool) $this->getVariable($var);
}

/**
* Get all variables in the current Context.
*
Expand Down
1 change: 1 addition & 0 deletions src/Psy/TabCompletion/Matcher/AbstractMatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ abstract class AbstractMatcher
const T_CLONE = 'T_CLONE';
const T_NS_SEPARATOR = 'T_NS_SEPARATOR';
const T_STRING = 'T_STRING';
const T_LNUMBER = 'T_LNUMBER';
const T_WHITESPACE = 'T_WHITESPACE';
const T_AND_EQUAL = 'T_AND_EQUAL';
const T_BOOLEAN_AND = 'T_BOOLEAN_AND';
Expand Down
78 changes: 69 additions & 9 deletions src/Psy/TabCompletion/Matcher/CommandsMatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
namespace Psy\TabCompletion\Matcher;

use Psy\Command\Command;
use Symfony\Component\Console\Input\InputOption;

/**
* A Psy Command tab completion Matcher.
Expand All @@ -24,6 +25,9 @@
class CommandsMatcher extends AbstractMatcher
{
/** @var string[] */
protected $commandNames = array();

/** @var Command[] */
protected $commands = array();

/**
Expand All @@ -43,22 +47,34 @@ public function __construct(array $commands)
*/
public function setCommands(array $commands)
{
$names = array();
foreach ($commands as $command) {
$names = array_merge(array($command->getName()), $names);
$names = array_merge($command->getAliases(), $names);
// use the same object so is leaks the lowest memory possible
foreach ($commands as &$command) {
$this->commands[$command->getName()] = &$command;
foreach ($command->getAliases() as $alias) {
$this->commands[$alias] = &$command;
}
$this->commandNames = array_keys($this->commands);
}
$this->commands = $names;
}

/**
* @param $name
*
* @return bool
*/
protected function isCommand($name)
{
return in_array($name, $this->commands);
return in_array($name, $this->commandNames);
}

/**
* @param $name
*
* @return bool
*/
protected function matchCommand($name)
{
foreach ($this->commands as $cmd) {
foreach ($this->commandNames as $cmd) {
if ($this->startsWith($name, $cmd)) {
return true;
}
Expand All @@ -73,8 +89,45 @@ protected function matchCommand($name)
public function getMatches(array $tokens, array $info = array())
{
$input = $this->getInput($tokens);
$prevToken = array_pop($tokens);
if (self::tokenIs($prevToken, self::T_STRING) && $prevToken[1] === $input) {
$prevToken = array_pop($tokens);
}

if (is_string($prevToken) && $prevToken === '-') {
// user is asking for the command parameters
// php open tag
array_shift($tokens);
// the command
$commandToken = array_shift($tokens);
$commandName = $commandToken[1];
$command = &$this->commands[$commandName];

return array_filter($this->commands, function ($command) use ($input) {
$options = $command->getDefinition()->getOptions();
$shorts = array_filter(array_map(function (InputOption $option) {
if ($shortcut = $option->getShortcut()) {
return $shortcut;
}
}, $options));

$matches = array_filter(
array_values($shorts),
function ($short) use ($input) {
return AbstractMatcher::startsWith($input, $short);
}
);

if (! empty($matches)) {
return array_map(
function ($opt) {
return '-' . $opt;
},
$matches
);
}
}

return array_filter($this->commandNames, function ($command) use ($input) {
return AbstractMatcher::startsWith($input, $command);
});
}
Expand All @@ -86,12 +139,19 @@ public function hasMatched(array $tokens)
{
/* $openTag */ array_shift($tokens);
$command = array_shift($tokens);
$dash = null;
if (count($tokens) > 0) {
$dash = array_shift($tokens);
}

switch (true) {
case self::tokenIs($command, self::T_STRING) &&
$this->isCommand($command[1]) &&
empty($tokens):
case self::tokenIs($command, self::T_STRING) &&
!$this->isCommand($command[1]) &&
$this->matchCommand($command[1]) &&
empty($tokens):
$dash === '-':
return true;
}

Expand Down
71 changes: 71 additions & 0 deletions src/Psy/TabCompletion/Matcher/FilesMatcher.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

namespace Psy\TabCompletion\Matcher;

class FilesMatcher extends AbstractMatcher
{
/**
* Provide tab completion matches for readline input.
*
* @param array $tokens information substracted with get_token_all
* @param array $info readline_info object
*
* @return array The matches resulting from the query
*/
public function getMatches(array $tokens, array $info = array())
{
$input = $this->getInput($tokens);

$quote = '"';
if (strlen($input) > 1) {
if (strlen($input) > 1 && $input[0] === "'" || $input[0] === '"') {
$quote = substr($input, 0, 1);
$input = substr($input, 1, strlen($input));
}
}

$pathFolders = explode(PATH_SEPARATOR, get_include_path());
$relativePaths = array_map(function ($path) {
$paths = glob($path . DIRECTORY_SEPARATOR . '*.*');

return array_map(function ($filePath) use ($path) {
return str_replace($path . DIRECTORY_SEPARATOR, '', $filePath);
}, $paths);
}, $pathFolders);

// flatten the array
$relativePaths = call_user_func_array('array_merge', $relativePaths);

return array_filter(
$relativePaths,
function ($file) use ($input, $quote) {
return AbstractMatcher::startsWith($input, $file);
}
);
}

/**
* {@inheritDoc}
*/
public function hasMatched(array $tokens)
{
$token = array_pop($tokens);
$prevToken = array_pop($tokens);
$priorToken = array_pop($tokens);

$fileManipulatorTokens = array(
self::T_INCLUDE, self::T_INCLUDE_ONCE, self::T_REQUIRE, self::T_REQUIRE_ONCE,
);

switch (true) {
case is_string($token) && $token === '"' && self::hasToken($fileManipulatorTokens, $prevToken):
case is_string($prevToken) && $prevToken === '"' && self::hasToken($fileManipulatorTokens, $priorToken):
case self::tokenIs($token, self::T_ENCAPSED_AND_WHITESPACE) &&
self::hasToken($fileManipulatorTokens, $prevToken):
case self::hasToken($fileManipulatorTokens, $token):
return true;
}

return false;
}
}
44 changes: 44 additions & 0 deletions src/Psy/TabCompletion/Matcher/OperatorsMatcher.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

namespace Psy\TabCompletion\Matcher;

class OperatorsMatcher extends AbstractContextAwareMatcher
{
/**
* Provide tab completion matches for readline input.
*
* @param array $tokens information substracted with get_token_all
* @param array $info readline_info object
*
* @return array The matches resulting from the query
*/
public function getMatches(array $tokens, array $info = array())
{
$input = $this->getInput($tokens);
$prevToken = array_pop($tokens);

if (self::hasToken(array(self::T_LNUMBER, self::T_STRING), $prevToken)) {
return str_split(self::MISC_OPERATORS);
}

return array($prevToken . $prevToken);
}

/**
* {@inheritDoc}
*/
public function hasMatched(array $tokens)
{
$token = array_pop($tokens);

switch (true) {
case self::tokenIs($token, self::T_LNUMBER):
case is_string($token) && in_array($token, str_split(self::MISC_OPERATORS)):
case self::tokenIs($token, self::T_VARIABLE) && $this->hasVariable($token[1]):
case self::tokenIs($token, self::T_STRING) && array_key_exists($token[1], get_defined_constants()):
return true;
}

return false;
}
}

0 comments on commit 04471e1

Please sign in to comment.