Upgrade framework

This commit is contained in:
2023-11-14 16:54:35 +01:00
parent 1648a5cd42
commit 4fcf6fffcc
10548 changed files with 693138 additions and 466698 deletions

View File

@@ -0,0 +1,112 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\TabCompletion;
use Psy\TabCompletion\Matcher\AbstractMatcher;
/**
* A readline tab completion service.
*
* @author Marc Garcia <markcial@gmail.com>
*/
class AutoCompleter
{
/** @var Matcher\AbstractMatcher[] */
protected $matchers;
/**
* Register a tab completion Matcher.
*
* @param AbstractMatcher $matcher
*/
public function addMatcher(AbstractMatcher $matcher)
{
$this->matchers[] = $matcher;
}
/**
* Activate readline tab completion.
*/
public function activate()
{
\readline_completion_function([&$this, 'callback']);
}
/**
* Handle readline completion.
*
* @param string $input Readline current word
* @param int $index Current word index
* @param array $info readline_info() data
*
* @return array
*/
public function processCallback(string $input, int $index, array $info = []): array
{
// Some (Windows?) systems provide incomplete `readline_info`, so let's
// try to work around it.
$line = $info['line_buffer'];
if (isset($info['end'])) {
$line = \substr($line, 0, $info['end']);
}
if ($line === '' && $input !== '') {
$line = $input;
}
$tokens = \token_get_all('<?php '.$line);
// remove whitespaces
$tokens = \array_filter($tokens, function ($token) {
return !AbstractMatcher::tokenIs($token, AbstractMatcher::T_WHITESPACE);
});
// reset index from 0 to remove missing index number
$tokens = \array_values($tokens);
$matches = [];
foreach ($this->matchers as $matcher) {
if ($matcher->hasMatched($tokens)) {
$matches = \array_merge($matcher->getMatches($tokens), $matches);
}
}
$matches = \array_unique($matches);
return !empty($matches) ? $matches : [''];
}
/**
* The readline_completion_function callback handler.
*
* @see processCallback
*
* @param string $input
* @param int $index
*
* @return array
*/
public function callback(string $input, int $index): array
{
return $this->processCallback($input, $index, \readline_info());
}
/**
* Remove readline callback handler on destruct.
*/
public function __destruct()
{
// PHP didn't implement the whole readline API when they first switched
// to libedit. And they still haven't.
if (\function_exists('readline_callback_handler_remove')) {
\readline_callback_handler_remove();
}
}
}

View File

@@ -0,0 +1,65 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\TabCompletion\Matcher;
use Psy\Context;
use Psy\ContextAware;
/**
* An abstract tab completion Matcher which implements ContextAware.
*
* The AutoCompleter service will inject a Context instance into all
* ContextAware Matchers.
*
* @author Marc Garcia <markcial@gmail.com>
*/
abstract class AbstractContextAwareMatcher extends AbstractMatcher implements ContextAware
{
/**
* Context instance (for ContextAware interface).
*
* @var Context
*/
protected $context;
/**
* ContextAware interface.
*
* @param Context $context
*/
public function setContext(Context $context)
{
$this->context = $context;
}
/**
* Get a Context variable by name.
*
* @param string $var Variable name
*
* @return mixed
*/
protected function getVariable(string $var)
{
return $this->context->get($var);
}
/**
* Get all variables in the current Context.
*
* @return array
*/
protected function getVariables(): array
{
return $this->context->getAll();
}
}

View File

@@ -0,0 +1,76 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\TabCompletion\Matcher;
abstract class AbstractDefaultParametersMatcher extends AbstractContextAwareMatcher
{
/**
* @param \ReflectionParameter[] $reflectionParameters
*
* @return array
*/
public function getDefaultParameterCompletion(array $reflectionParameters): array
{
$parametersProcessed = [];
foreach ($reflectionParameters as $parameter) {
if (!$parameter->isDefaultValueAvailable()) {
return [];
}
$defaultValue = $this->valueToShortString($parameter->getDefaultValue());
$parametersProcessed[] = \sprintf('$%s = %s', $parameter->getName(), $defaultValue);
}
if (empty($parametersProcessed)) {
return [];
}
return [\implode(', ', $parametersProcessed).')'];
}
/**
* Takes in the default value of a parameter and turns it into a
* string representation that fits inline.
* This is not 100% true to the original (newlines are inlined, for example).
*
* @param mixed $value
*
* @return string
*/
private function valueToShortString($value): string
{
if (!\is_array($value)) {
return \json_encode($value);
}
$chunks = [];
$chunksSequential = [];
$allSequential = true;
foreach ($value as $key => $item) {
$allSequential = $allSequential && \is_numeric($key) && $key === \count($chunksSequential);
$keyString = $this->valueToShortString($key);
$itemString = $this->valueToShortString($item);
$chunks[] = "{$keyString} => {$itemString}";
$chunksSequential[] = $itemString;
}
$chunksToImplode = $allSequential ? $chunksSequential : $chunks;
return '['.\implode(', ', $chunksToImplode).']';
}
}

View File

@@ -0,0 +1,196 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\TabCompletion\Matcher;
/**
* Abstract tab completion Matcher.
*
* @author Marc Garcia <markcial@gmail.com>
*/
abstract class AbstractMatcher
{
/** Syntax types */
const CONSTANT_SYNTAX = '^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$';
const VAR_SYNTAX = '^\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$';
const MISC_OPERATORS = '+-*/^|&';
/** Token values */
const T_OPEN_TAG = 'T_OPEN_TAG';
const T_VARIABLE = 'T_VARIABLE';
const T_OBJECT_OPERATOR = 'T_OBJECT_OPERATOR';
const T_DOUBLE_COLON = 'T_DOUBLE_COLON';
const T_NEW = 'T_NEW';
const T_CLONE = 'T_CLONE';
const T_NS_SEPARATOR = 'T_NS_SEPARATOR';
const T_STRING = 'T_STRING';
const T_NAME_QUALIFIED = 'T_NAME_QUALIFIED';
const T_WHITESPACE = 'T_WHITESPACE';
const T_AND_EQUAL = 'T_AND_EQUAL';
const T_BOOLEAN_AND = 'T_BOOLEAN_AND';
const T_BOOLEAN_OR = 'T_BOOLEAN_OR';
const T_ENCAPSED_AND_WHITESPACE = 'T_ENCAPSED_AND_WHITESPACE';
const T_REQUIRE = 'T_REQUIRE';
const T_REQUIRE_ONCE = 'T_REQUIRE_ONCE';
const T_INCLUDE = 'T_INCLUDE';
const T_INCLUDE_ONCE = 'T_INCLUDE_ONCE';
/**
* Check whether this matcher can provide completions for $tokens.
*
* @param array $tokens Tokenized readline input
*
* @return bool
*/
public function hasMatched(array $tokens): bool
{
return false;
}
/**
* Get current readline input word.
*
* @param array $tokens Tokenized readline input (see token_get_all)
*
* @return string
*/
protected function getInput(array $tokens): string
{
$var = '';
$firstToken = \array_pop($tokens);
if (self::tokenIs($firstToken, self::T_STRING)) {
$var = $firstToken[1];
}
return $var;
}
/**
* Get current namespace and class (if any) from readline input.
*
* @param array $tokens Tokenized readline input (see token_get_all)
*
* @return string
*/
protected function getNamespaceAndClass(array $tokens): string
{
$class = '';
while (self::hasToken(
[self::T_NS_SEPARATOR, self::T_STRING, self::T_NAME_QUALIFIED],
$token = \array_pop($tokens)
)) {
if (self::needCompleteClass($token)) {
continue;
}
$class = $token[1].$class;
}
return $class;
}
/**
* 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
*/
abstract public function getMatches(array $tokens, array $info = []): array;
/**
* Check whether $word starts with $prefix.
*
* @param string $prefix
* @param string $word
*
* @return bool
*/
public static function startsWith(string $prefix, string $word): bool
{
return \preg_match(\sprintf('#^%s#', $prefix), $word);
}
/**
* Check whether $token matches a given syntax pattern.
*
* @param mixed $token A PHP token (see token_get_all)
* @param string $syntax A syntax pattern (default: variable pattern)
*
* @return bool
*/
public static function hasSyntax($token, string $syntax = self::VAR_SYNTAX): bool
{
if (!\is_array($token)) {
return false;
}
$regexp = \sprintf('#%s#', $syntax);
return (bool) \preg_match($regexp, $token[1]);
}
/**
* Check whether $token type is $which.
*
* @param mixed $token A PHP token (see token_get_all)
* @param string $which A PHP token type
*
* @return bool
*/
public static function tokenIs($token, string $which): bool
{
if (!\is_array($token)) {
return false;
}
return \token_name($token[0]) === $which;
}
/**
* Check whether $token is an operator.
*
* @param mixed $token A PHP token (see token_get_all)
*
* @return bool
*/
public static function isOperator($token): bool
{
if (!\is_string($token)) {
return false;
}
return \strpos(self::MISC_OPERATORS, $token) !== false;
}
public static function needCompleteClass($token): bool
{
return \in_array($token[1], ['doc', 'ls', 'show']);
}
/**
* Check whether $token type is present in $coll.
*
* @param array $coll A list of token types
* @param mixed $token A PHP token (see token_get_all)
*
* @return bool
*/
public static function hasToken(array $coll, $token): bool
{
if (!\is_array($token)) {
return false;
}
return \in_array(\token_name($token[0]), $coll);
}
}

View File

@@ -0,0 +1,87 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\TabCompletion\Matcher;
/**
* A class attribute tab completion Matcher.
*
* Given a namespace and class, this matcher provides completion for constants
* and static properties.
*
* @author Marc Garcia <markcial@gmail.com>
*/
class ClassAttributesMatcher extends AbstractMatcher
{
/**
* {@inheritdoc}
*/
public function getMatches(array $tokens, array $info = []): array
{
$input = $this->getInput($tokens);
$firstToken = \array_pop($tokens);
if (self::tokenIs($firstToken, self::T_STRING)) {
// second token is the nekudotayim operator
\array_pop($tokens);
}
$class = $this->getNamespaceAndClass($tokens);
try {
$reflection = new \ReflectionClass($class);
} catch (\ReflectionException $re) {
return [];
}
$vars = \array_merge(
\array_map(
function ($var) {
return '$'.$var;
},
\array_keys($reflection->getStaticProperties())
),
\array_keys($reflection->getConstants())
);
return \array_map(
function ($name) use ($class) {
$chunks = \explode('\\', $class);
$className = \array_pop($chunks);
return $className.'::'.$name;
},
\array_filter(
$vars,
function ($var) use ($input) {
return AbstractMatcher::startsWith($input, $var);
}
)
);
}
/**
* {@inheritdoc}
*/
public function hasMatched(array $tokens): bool
{
$token = \array_pop($tokens);
$prevToken = \array_pop($tokens);
switch (true) {
case self::tokenIs($prevToken, self::T_DOUBLE_COLON) && self::tokenIs($token, self::T_STRING):
case self::tokenIs($token, self::T_DOUBLE_COLON):
return true;
}
return false;
}
}

View File

@@ -0,0 +1,64 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\TabCompletion\Matcher;
class ClassMethodDefaultParametersMatcher extends AbstractDefaultParametersMatcher
{
public function getMatches(array $tokens, array $info = []): array
{
$openBracket = \array_pop($tokens);
$functionName = \array_pop($tokens);
$methodOperator = \array_pop($tokens);
$class = $this->getNamespaceAndClass($tokens);
try {
$reflection = new \ReflectionClass($class);
} catch (\ReflectionException $e) {
// In this case the class apparently does not exist, so we can do nothing
return [];
}
$methods = $reflection->getMethods(\ReflectionMethod::IS_STATIC);
foreach ($methods as $method) {
if ($method->getName() === $functionName[1]) {
return $this->getDefaultParameterCompletion($method->getParameters());
}
}
return [];
}
public function hasMatched(array $tokens): bool
{
$openBracket = \array_pop($tokens);
if ($openBracket !== '(') {
return false;
}
$functionName = \array_pop($tokens);
if (!self::tokenIs($functionName, self::T_STRING)) {
return false;
}
$operator = \array_pop($tokens);
if (!self::tokenIs($operator, self::T_DOUBLE_COLON)) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,84 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\TabCompletion\Matcher;
/**
* A class method tab completion Matcher.
*
* Given a namespace and class, this matcher provides completion for static
* methods.
*
* @author Marc Garcia <markcial@gmail.com>
*/
class ClassMethodsMatcher extends AbstractMatcher
{
/**
* {@inheritdoc}
*/
public function getMatches(array $tokens, array $info = []): array
{
$input = $this->getInput($tokens);
$firstToken = \array_pop($tokens);
if (self::tokenIs($firstToken, self::T_STRING)) {
// second token is the nekudotayim operator
\array_pop($tokens);
}
$class = $this->getNamespaceAndClass($tokens);
try {
$reflection = new \ReflectionClass($class);
} catch (\ReflectionException $re) {
return [];
}
if (self::needCompleteClass($tokens[1])) {
$methods = $reflection->getMethods();
} else {
$methods = $reflection->getMethods(\ReflectionMethod::IS_STATIC);
}
$methods = \array_map(function (\ReflectionMethod $method) {
return $method->getName();
}, $methods);
return \array_map(
function ($name) use ($class) {
$chunks = \explode('\\', $class);
$className = \array_pop($chunks);
return $className.'::'.$name;
},
\array_filter($methods, function ($method) use ($input) {
return AbstractMatcher::startsWith($input, $method);
})
);
}
/**
* {@inheritdoc}
*/
public function hasMatched(array $tokens): bool
{
$token = \array_pop($tokens);
$prevToken = \array_pop($tokens);
switch (true) {
case self::tokenIs($prevToken, self::T_DOUBLE_COLON) && self::tokenIs($token, self::T_STRING):
case self::tokenIs($token, self::T_DOUBLE_COLON):
return true;
}
return false;
}
}

View File

@@ -0,0 +1,77 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\TabCompletion\Matcher;
/**
* A class name tab completion Matcher.
*
* This matcher provides completion for all declared classes.
*
* @author Marc Garcia <markcial@gmail.com>
*/
class ClassNamesMatcher extends AbstractMatcher
{
/**
* {@inheritdoc}
*/
public function getMatches(array $tokens, array $info = []): array
{
$class = $this->getNamespaceAndClass($tokens);
if ($class !== '' && $class[0] === '\\') {
$class = \substr($class, 1, \strlen($class));
}
$quotedClass = \preg_quote($class);
return \array_map(
function ($className) use ($class) {
// get the number of namespace separators
$nsPos = \substr_count($class, '\\');
$pieces = \explode('\\', $className);
// $methods = Mirror::get($class);
return \implode('\\', \array_slice($pieces, $nsPos, \count($pieces)));
},
\array_filter(
\array_merge(\get_declared_classes(), \get_declared_interfaces()),
function ($className) use ($quotedClass) {
return AbstractMatcher::startsWith($quotedClass, $className);
}
)
);
}
/**
* {@inheritdoc}
*/
public function hasMatched(array $tokens): bool
{
$token = \array_pop($tokens);
$prevToken = \array_pop($tokens);
$ignoredTokens = [
self::T_INCLUDE, self::T_INCLUDE_ONCE, self::T_REQUIRE, self::T_REQUIRE_ONCE,
];
switch (true) {
case self::hasToken([$ignoredTokens], $token):
case self::hasToken([$ignoredTokens], $prevToken):
case \is_string($token) && $token === '$':
return false;
case self::hasToken([self::T_NEW, self::T_OPEN_TAG, self::T_NS_SEPARATOR, self::T_STRING], $prevToken):
case self::hasToken([self::T_NEW, self::T_OPEN_TAG, self::T_NS_SEPARATOR], $token):
case self::hasToken([self::T_OPEN_TAG, self::T_VARIABLE], $token):
case self::isOperator($token):
return true;
}
return false;
}
}

View File

@@ -0,0 +1,114 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\TabCompletion\Matcher;
use Psy\Command\Command;
/**
* A Psy Command tab completion Matcher.
*
* This matcher provides completion for all registered Psy Command names and
* aliases.
*
* @author Marc Garcia <markcial@gmail.com>
*/
class CommandsMatcher extends AbstractMatcher
{
/** @var string[] */
protected $commands = [];
/**
* CommandsMatcher constructor.
*
* @param Command[] $commands
*/
public function __construct(array $commands)
{
$this->setCommands($commands);
}
/**
* Set Commands for completion.
*
* @param Command[] $commands
*/
public function setCommands(array $commands)
{
$names = [];
foreach ($commands as $command) {
$names = \array_merge([$command->getName()], $names);
$names = \array_merge($command->getAliases(), $names);
}
$this->commands = $names;
}
/**
* Check whether a command $name is defined.
*
* @param string $name
*
* @return bool
*/
protected function isCommand(string $name): bool
{
return \in_array($name, $this->commands);
}
/**
* Check whether input matches a defined command.
*
* @param string $name
*
* @return bool
*/
protected function matchCommand(string $name): bool
{
foreach ($this->commands as $cmd) {
if ($this->startsWith($name, $cmd)) {
return true;
}
}
return false;
}
/**
* {@inheritdoc}
*/
public function getMatches(array $tokens, array $info = []): array
{
$input = $this->getInput($tokens);
return \array_filter($this->commands, function ($command) use ($input) {
return AbstractMatcher::startsWith($input, $command);
});
}
/**
* {@inheritdoc}
*/
public function hasMatched(array $tokens): bool
{
/* $openTag */ \array_shift($tokens);
$command = \array_shift($tokens);
switch (true) {
case self::tokenIs($command, self::T_STRING) &&
!$this->isCommand($command[1]) &&
$this->matchCommand($command[1]) &&
empty($tokens):
return true;
}
return false;
}
}

View File

@@ -0,0 +1,54 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\TabCompletion\Matcher;
/**
* A constant name tab completion Matcher.
*
* This matcher provides completion for all defined constants.
*
* @author Marc Garcia <markcial@gmail.com>
*/
class ConstantsMatcher extends AbstractMatcher
{
/**
* {@inheritdoc}
*/
public function getMatches(array $tokens, array $info = []): array
{
$const = $this->getInput($tokens);
return \array_filter(\array_keys(\get_defined_constants()), function ($constant) use ($const) {
return AbstractMatcher::startsWith($const, $constant);
});
}
/**
* {@inheritdoc}
*/
public function hasMatched(array $tokens): bool
{
$token = \array_pop($tokens);
$prevToken = \array_pop($tokens);
switch (true) {
case self::tokenIs($prevToken, self::T_NEW):
case self::tokenIs($prevToken, self::T_NS_SEPARATOR):
return false;
case self::hasToken([self::T_OPEN_TAG, self::T_STRING], $token):
case self::isOperator($token):
return true;
}
return false;
}
}

View File

@@ -0,0 +1,53 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\TabCompletion\Matcher;
class FunctionDefaultParametersMatcher extends AbstractDefaultParametersMatcher
{
public function getMatches(array $tokens, array $info = []): array
{
\array_pop($tokens); // open bracket
$functionName = \array_pop($tokens);
try {
$reflection = new \ReflectionFunction($functionName[1]);
} catch (\ReflectionException $e) {
return [];
}
$parameters = $reflection->getParameters();
return $this->getDefaultParameterCompletion($parameters);
}
public function hasMatched(array $tokens): bool
{
$openBracket = \array_pop($tokens);
if ($openBracket !== '(') {
return false;
}
$functionName = \array_pop($tokens);
if (!self::tokenIs($functionName, self::T_STRING)) {
return false;
}
if (!\function_exists($functionName[1])) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,56 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\TabCompletion\Matcher;
/**
* A function name tab completion Matcher.
*
* This matcher provides completion for all internal and user-defined functions.
*
* @author Marc Garcia <markcial@gmail.com>
*/
class FunctionsMatcher extends AbstractMatcher
{
/**
* {@inheritdoc}
*/
public function getMatches(array $tokens, array $info = []): array
{
$func = $this->getInput($tokens);
$functions = \get_defined_functions();
$allFunctions = \array_merge($functions['user'], $functions['internal']);
return \array_filter($allFunctions, function ($function) use ($func) {
return AbstractMatcher::startsWith($func, $function);
});
}
/**
* {@inheritdoc}
*/
public function hasMatched(array $tokens): bool
{
$token = \array_pop($tokens);
$prevToken = \array_pop($tokens);
switch (true) {
case self::tokenIs($prevToken, self::T_NEW):
return false;
case self::hasToken([self::T_OPEN_TAG, self::T_STRING], $token):
case self::isOperator($token):
return true;
}
return false;
}
}

View File

@@ -0,0 +1,85 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\TabCompletion\Matcher;
/**
* A PHP keyword tab completion Matcher.
*
* This matcher provides completion for all function-like PHP keywords.
*
* @author Marc Garcia <markcial@gmail.com>
*/
class KeywordsMatcher extends AbstractMatcher
{
protected $keywords = [
'array', 'clone', 'declare', 'die', 'echo', 'empty', 'eval', 'exit', 'include',
'include_once', 'isset', 'list', 'print', 'require', 'require_once', 'unset',
];
protected $mandatoryStartKeywords = [
'die', 'echo', 'print', 'unset',
];
/**
* Get all (completable) PHP keywords.
*
* @return array
*/
public function getKeywords(): array
{
return $this->keywords;
}
/**
* Check whether $keyword is a (completable) PHP keyword.
*
* @param string $keyword
*
* @return bool
*/
public function isKeyword(string $keyword): bool
{
return \in_array($keyword, $this->keywords);
}
/**
* {@inheritdoc}
*/
public function getMatches(array $tokens, array $info = []): array
{
$input = $this->getInput($tokens);
return \array_filter($this->keywords, function ($keyword) use ($input) {
return AbstractMatcher::startsWith($input, $keyword);
});
}
/**
* {@inheritdoc}
*/
public function hasMatched(array $tokens): bool
{
$token = \array_pop($tokens);
$prevToken = \array_pop($tokens);
switch (true) {
case self::hasToken([self::T_OPEN_TAG, self::T_VARIABLE], $token):
// case is_string($token) && $token === '$':
case self::hasToken([self::T_OPEN_TAG, self::T_VARIABLE], $prevToken) &&
self::tokenIs($token, self::T_STRING):
case self::isOperator($token):
return true;
}
return false;
}
}

View File

@@ -0,0 +1,71 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\TabCompletion\Matcher;
/**
* A MongoDB Client tab completion Matcher.
*
* This matcher provides completion for MongoClient database names.
*
* @author Marc Garcia <markcial@gmail.com>
*/
class MongoClientMatcher extends AbstractContextAwareMatcher
{
/**
* {@inheritdoc}
*/
public function getMatches(array $tokens, array $info = []): array
{
$input = $this->getInput($tokens);
$firstToken = \array_pop($tokens);
if (self::tokenIs($firstToken, self::T_STRING)) {
// second token is the object operator
\array_pop($tokens);
}
$objectToken = \array_pop($tokens);
$objectName = \str_replace('$', '', $objectToken[1]);
$object = $this->getVariable($objectName);
if (!$object instanceof \MongoClient) {
return [];
}
$list = $object->listDBs();
return \array_filter(
\array_map(function ($info) {
return $info['name'];
}, $list['databases']),
function ($var) use ($input) {
return AbstractMatcher::startsWith($input, $var);
}
);
}
/**
* {@inheritdoc}
*/
public function hasMatched(array $tokens): bool
{
$token = \array_pop($tokens);
$prevToken = \array_pop($tokens);
switch (true) {
case self::tokenIs($token, self::T_OBJECT_OPERATOR):
case self::tokenIs($prevToken, self::T_OBJECT_OPERATOR):
return true;
}
return false;
}
}

View File

@@ -0,0 +1,67 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\TabCompletion\Matcher;
/**
* A MongoDB tab completion Matcher.
*
* This matcher provides completion for Mongo collection names.
*
* @author Marc Garcia <markcial@gmail.com>
*/
class MongoDatabaseMatcher extends AbstractContextAwareMatcher
{
/**
* {@inheritdoc}
*/
public function getMatches(array $tokens, array $info = []): array
{
$input = $this->getInput($tokens);
$firstToken = \array_pop($tokens);
if (self::tokenIs($firstToken, self::T_STRING)) {
// second token is the object operator
\array_pop($tokens);
}
$objectToken = \array_pop($tokens);
$objectName = \str_replace('$', '', $objectToken[1]);
$object = $this->getVariable($objectName);
if (!$object instanceof \MongoDB) {
return [];
}
return \array_filter(
$object->getCollectionNames(),
function ($var) use ($input) {
return AbstractMatcher::startsWith($input, $var);
}
);
}
/**
* {@inheritdoc}
*/
public function hasMatched(array $tokens): bool
{
$token = \array_pop($tokens);
$prevToken = \array_pop($tokens);
switch (true) {
case self::tokenIs($token, self::T_OBJECT_OPERATOR):
case self::tokenIs($prevToken, self::T_OBJECT_OPERATOR):
return true;
}
return false;
}
}

View File

@@ -0,0 +1,78 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\TabCompletion\Matcher;
use InvalidArgumentException;
/**
* An object attribute tab completion Matcher.
*
* This matcher provides completion for properties of objects in the current
* Context.
*
* @author Marc Garcia <markcial@gmail.com>
*/
class ObjectAttributesMatcher extends AbstractContextAwareMatcher
{
/**
* {@inheritdoc}
*/
public function getMatches(array $tokens, array $info = []): array
{
$input = $this->getInput($tokens);
$firstToken = \array_pop($tokens);
if (self::tokenIs($firstToken, self::T_STRING)) {
// second token is the object operator
\array_pop($tokens);
}
$objectToken = \array_pop($tokens);
if (!\is_array($objectToken)) {
return [];
}
$objectName = \str_replace('$', '', $objectToken[1]);
try {
$object = $this->getVariable($objectName);
} catch (InvalidArgumentException $e) {
return [];
}
if (!\is_object($object)) {
return [];
}
return \array_filter(
\array_keys(\get_class_vars(\get_class($object))),
function ($var) use ($input) {
return AbstractMatcher::startsWith($input, $var);
}
);
}
/**
* {@inheritdoc}
*/
public function hasMatched(array $tokens): bool
{
$token = \array_pop($tokens);
$prevToken = \array_pop($tokens);
switch (true) {
case self::tokenIs($token, self::T_OBJECT_OPERATOR):
case self::tokenIs($prevToken, self::T_OBJECT_OPERATOR):
return true;
}
return false;
}
}

View File

@@ -0,0 +1,71 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\TabCompletion\Matcher;
class ObjectMethodDefaultParametersMatcher extends AbstractDefaultParametersMatcher
{
public function getMatches(array $tokens, array $info = []): array
{
$openBracket = \array_pop($tokens);
$functionName = \array_pop($tokens);
$methodOperator = \array_pop($tokens);
$objectToken = \array_pop($tokens);
if (!\is_array($objectToken)) {
return [];
}
$objectName = \str_replace('$', '', $objectToken[1]);
try {
$object = $this->getVariable($objectName);
$reflection = new \ReflectionObject($object);
} catch (\InvalidArgumentException $e) {
return [];
} catch (\ReflectionException $e) {
return [];
}
$methods = $reflection->getMethods();
foreach ($methods as $method) {
if ($method->getName() === $functionName[1]) {
return $this->getDefaultParameterCompletion($method->getParameters());
}
}
return [];
}
public function hasMatched(array $tokens): bool
{
$openBracket = \array_pop($tokens);
if ($openBracket !== '(') {
return false;
}
$functionName = \array_pop($tokens);
if (!self::tokenIs($functionName, self::T_STRING)) {
return false;
}
$operator = \array_pop($tokens);
if (!self::tokenIs($operator, self::T_OBJECT_OPERATOR)) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,80 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\TabCompletion\Matcher;
use InvalidArgumentException;
/**
* An object method tab completion Matcher.
*
* This matcher provides completion for methods of objects in the current
* Context.
*
* @author Marc Garcia <markcial@gmail.com>
*/
class ObjectMethodsMatcher extends AbstractContextAwareMatcher
{
/**
* {@inheritdoc}
*/
public function getMatches(array $tokens, array $info = []): array
{
$input = $this->getInput($tokens);
$firstToken = \array_pop($tokens);
if (self::tokenIs($firstToken, self::T_STRING)) {
// second token is the object operator
\array_pop($tokens);
}
$objectToken = \array_pop($tokens);
if (!\is_array($objectToken)) {
return [];
}
$objectName = \str_replace('$', '', $objectToken[1]);
try {
$object = $this->getVariable($objectName);
} catch (InvalidArgumentException $e) {
return [];
}
if (!\is_object($object)) {
return [];
}
return \array_filter(
\get_class_methods($object),
function ($var) use ($input) {
return AbstractMatcher::startsWith($input, $var) &&
// also check that we do not suggest invoking a super method(__construct, __wakeup, …)
!AbstractMatcher::startsWith('__', $var);
}
);
}
/**
* {@inheritdoc}
*/
public function hasMatched(array $tokens): bool
{
$token = \array_pop($tokens);
$prevToken = \array_pop($tokens);
switch (true) {
case self::tokenIs($token, self::T_OBJECT_OPERATOR):
case self::tokenIs($prevToken, self::T_OBJECT_OPERATOR):
return true;
}
return false;
}
}

View File

@@ -0,0 +1,51 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\TabCompletion\Matcher;
/**
* A variable name tab completion Matcher.
*
* This matcher provides completion for variable names in the current Context.
*
* @author Marc Garcia <markcial@gmail.com>
*/
class VariablesMatcher extends AbstractContextAwareMatcher
{
/**
* {@inheritdoc}
*/
public function getMatches(array $tokens, array $info = []): array
{
$var = \str_replace('$', '', $this->getInput($tokens));
return \array_filter(\array_keys($this->getVariables()), function ($variable) use ($var) {
return AbstractMatcher::startsWith($var, $variable);
});
}
/**
* {@inheritdoc}
*/
public function hasMatched(array $tokens): bool
{
$token = \array_pop($tokens);
switch (true) {
case self::hasToken([self::T_OPEN_TAG, self::T_VARIABLE], $token):
case \is_string($token) && $token === '$':
case self::isOperator($token):
return true;
}
return false;
}
}