Stand: SMTP-Test, Admin-Mail-Tab, Notifiable-Fix, Lazy-Quill
- Fix: Notifiable-Trait zum User-Model hinzugefuegt (behebt notify()-500er) - Installer: SMTP-Verbindungstest mit EsmtpTransport + Ueberspringen-Link - Admin: Neuer E-Mail-Tab mit SMTP-Konfiguration + Verbindungstest - Admin: Lazy Quill-Initialisierung (nur sichtbare Locale wird geladen) - Uebersetzungen: 17 neue Mail-Keys in allen 6 Sprachen Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
112
vendor/psy/psysh/src/TabCompletion/AutoCompleter.php
vendored
Executable file
112
vendor/psy/psysh/src/TabCompletion/AutoCompleter.php
vendored
Executable file
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Psy Shell.
|
||||
*
|
||||
* (c) 2012-2026 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, $info), $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();
|
||||
}
|
||||
}
|
||||
}
|
||||
32
vendor/psy/psysh/src/TabCompletion/AutoloadWarmer/AutoloadWarmerInterface.php
vendored
Executable file
32
vendor/psy/psysh/src/TabCompletion/AutoloadWarmer/AutoloadWarmerInterface.php
vendored
Executable file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Psy Shell.
|
||||
*
|
||||
* (c) 2012-2026 Justin Hileman
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Psy\TabCompletion\AutoloadWarmer;
|
||||
|
||||
/**
|
||||
* Autoload warmer for tab completion.
|
||||
*
|
||||
* Autoload warmers load classes from autoloaders at startup to enable
|
||||
* better tab completion. This leverages PHP's existing autoload system
|
||||
* rather than maintaining a separate list of available classes.
|
||||
*/
|
||||
interface AutoloadWarmerInterface
|
||||
{
|
||||
/**
|
||||
* Warm up the autoloader by loading classes.
|
||||
*
|
||||
* This uses PHP's autoload system (class_exists, etc.) to actually load
|
||||
* classes, making them available for tab completion via get_declared_classes().
|
||||
*
|
||||
* @return int Number of classes/interfaces/traits that were loaded
|
||||
*/
|
||||
public function warm(): int;
|
||||
}
|
||||
546
vendor/psy/psysh/src/TabCompletion/AutoloadWarmer/ComposerAutoloadWarmer.php
vendored
Executable file
546
vendor/psy/psysh/src/TabCompletion/AutoloadWarmer/ComposerAutoloadWarmer.php
vendored
Executable file
@@ -0,0 +1,546 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Psy Shell.
|
||||
*
|
||||
* (c) 2012-2026 Justin Hileman
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Psy\TabCompletion\AutoloadWarmer;
|
||||
|
||||
use Psy\Shell;
|
||||
|
||||
/**
|
||||
* Composer autoload warmer.
|
||||
*
|
||||
* Loads classes from Composer's autoload configuration. By default, loads
|
||||
* application classes but excludes vendor packages and test files to balance
|
||||
* startup time with completion quality.
|
||||
*
|
||||
* Example configuration:
|
||||
*
|
||||
* // Enable autoload warming with default settings
|
||||
* $config->setWarmAutoload(true);
|
||||
*
|
||||
* // Disable autoload warming (default)
|
||||
* $config->setWarmAutoload(false);
|
||||
*
|
||||
* // Configure ComposerAutoloadWarmer options
|
||||
* $config->setWarmAutoload([
|
||||
* 'includeVendor' => true, // Include all vendor packages
|
||||
* 'includeTests' => false, // Exclude test namespaces
|
||||
* 'includeNamespaces' => ['App\\', 'Lib\\'], // Only these namespaces
|
||||
* 'excludeNamespaces' => ['App\\Legacy\\'], // Exclude specific namespaces
|
||||
* ]);
|
||||
*
|
||||
* // Include specific vendor packages only
|
||||
* $config->setWarmAutoload([
|
||||
* 'includeVendorNamespaces' => ['Symfony\\', 'Doctrine\\'],
|
||||
* ]);
|
||||
*
|
||||
* // Include all vendor except specific packages
|
||||
* $config->setWarmAutoload([
|
||||
* 'includeVendor' => true,
|
||||
* 'excludeVendorNamespaces' => ['Symfony\\Debug\\'],
|
||||
* ]);
|
||||
*
|
||||
* // Use custom warmers only
|
||||
* $config->setWarmAutoload([
|
||||
* 'warmers' => [$myCustomWarmer],
|
||||
* ]);
|
||||
*
|
||||
* // Combine custom warmers with configured ComposerAutoloadWarmer
|
||||
* $config->setWarmAutoload([
|
||||
* 'warmers' => [$myCustomWarmer],
|
||||
* 'includeVendorNamespaces' => ['Symfony\\'],
|
||||
* ]);
|
||||
*/
|
||||
class ComposerAutoloadWarmer implements AutoloadWarmerInterface
|
||||
{
|
||||
private bool $includeVendor;
|
||||
private bool $includeTests;
|
||||
private array $includeNamespaces;
|
||||
private array $excludeNamespaces;
|
||||
private array $includeVendorNamespaces;
|
||||
private array $excludeVendorNamespaces;
|
||||
private ?string $vendorDir = null;
|
||||
private ?string $pharPrefix = null;
|
||||
|
||||
private const KNOWN_BAD_NAMESPACES = [
|
||||
'Psy\\Readline\\Hoa\\',
|
||||
'Composer\\', // Autoloading Composer classes breaks Composer autoloading :grimacing:
|
||||
];
|
||||
|
||||
/**
|
||||
* PsySH's php-scoper prefix pattern for PHAR builds.
|
||||
*
|
||||
* Vendor dependencies in the PHAR are prefixed with "_Psy<hash>\" to avoid
|
||||
* conflicts with user dependencies. The hash is randomly generated per build.
|
||||
*/
|
||||
private const PHAR_SCOPED_PREFIX_PATTERN = '/^_Psy[a-f0-9]+\\\\/';
|
||||
|
||||
/**
|
||||
* @param array $config Configuration options
|
||||
* @param string|null $vendorDir Optional vendor directory path (auto-detected if not provided)
|
||||
*/
|
||||
public function __construct(array $config = [], ?string $vendorDir = null)
|
||||
{
|
||||
$hasVendorFilters = isset($config['includeVendorNamespaces']) || isset($config['excludeVendorNamespaces']);
|
||||
|
||||
// Validate conflicting config
|
||||
if ($hasVendorFilters && isset($config['includeVendor']) && $config['includeVendor'] === false) {
|
||||
throw new \InvalidArgumentException('Cannot use includeVendorNamespaces or excludeVendorNamespaces when includeVendor is false');
|
||||
}
|
||||
|
||||
// Vendor namespace filters imply includeVendor: true
|
||||
$this->includeVendor = $config['includeVendor'] ?? $hasVendorFilters;
|
||||
$this->includeTests = $config['includeTests'] ?? false;
|
||||
$this->includeNamespaces = $this->normalizeNamespaces($config['includeNamespaces'] ?? []);
|
||||
$this->excludeNamespaces = $this->normalizeNamespaces($config['excludeNamespaces'] ?? []);
|
||||
$this->includeVendorNamespaces = $this->normalizeNamespaces($config['includeVendorNamespaces'] ?? []);
|
||||
$this->excludeVendorNamespaces = $this->normalizeNamespaces($config['excludeVendorNamespaces'] ?? []);
|
||||
|
||||
// Cache PHAR prefix to avoid repeated Phar::running() calls
|
||||
if (Shell::isPhar()) {
|
||||
$runningPhar = \Phar::running(false);
|
||||
$this->pharPrefix = 'phar://'.$runningPhar.'/';
|
||||
}
|
||||
|
||||
$vendorDir = $vendorDir ?? $this->findVendorDir();
|
||||
if ($vendorDir !== null) {
|
||||
$resolvedVendorDir = \realpath($vendorDir);
|
||||
if ($resolvedVendorDir !== false) {
|
||||
$this->vendorDir = \str_replace('\\', '/', $resolvedVendorDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function warm(): int
|
||||
{
|
||||
// Get count of already-loaded classes before we start
|
||||
$beforeCount = \count(\get_declared_classes()) +
|
||||
\count(\get_declared_interfaces()) +
|
||||
\count(\get_declared_traits());
|
||||
|
||||
$classes = $this->getClassNames();
|
||||
foreach ($classes as $class) {
|
||||
try {
|
||||
// Skip if already loaded (check without autoloading first)
|
||||
if (
|
||||
\class_exists($class, false) ||
|
||||
\interface_exists($class, false) ||
|
||||
\trait_exists($class, false)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to load the class/interface/trait
|
||||
// The autoload parameter (true) will trigger autoloading
|
||||
\class_exists($class, true) ||
|
||||
\interface_exists($class, true) ||
|
||||
\trait_exists($class, true);
|
||||
} catch (\Throwable $e) {
|
||||
// Ignore classes that fail to load
|
||||
// This is expected for classes with missing dependencies, etc.
|
||||
}
|
||||
}
|
||||
|
||||
// Return the number of newly loaded classes
|
||||
$afterCount = \count(\get_declared_classes()) +
|
||||
\count(\get_declared_interfaces()) +
|
||||
\count(\get_declared_traits());
|
||||
|
||||
return $afterCount - $beforeCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discover classes from available sources.
|
||||
*
|
||||
* Uses two complementary strategies:
|
||||
* 1. ClassLoader's classmap (from optimized autoload or registered classes)
|
||||
* 2. ClassMapGenerator to scan PSR-4 directories (if available, safe, no side effects)
|
||||
*
|
||||
* Both strategies are attempted and results are combined, since ClassLoader
|
||||
* may have an optimized classmap while ClassMapGenerator can discover new
|
||||
* classes added during development.
|
||||
*
|
||||
* @internal This method is exported for testability and is not part of the public API
|
||||
*
|
||||
* @return string[] Fully-qualified class names
|
||||
*/
|
||||
public function getClassNames(): array
|
||||
{
|
||||
$autoloadMap = $this->getAutoloadClassMap();
|
||||
$generatedMap = $this->generateClassMap();
|
||||
|
||||
return $this->classesFromClassMap(\array_merge($autoloadMap, $generatedMap));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get class map from the registered Composer ClassLoader, if available.
|
||||
*
|
||||
* This map is populated by running `composer dump-autoload`
|
||||
*
|
||||
* @return array Map of class name => file path
|
||||
*/
|
||||
private function getAutoloadClassMap(): array
|
||||
{
|
||||
// If we found a project vendor dir, try to register their autoloader (if it hasn't been already)
|
||||
// Skip if vendor dir is inside a PHAR (don't re-require the PHAR's autoloader)
|
||||
if ($this->vendorDir !== null && \substr($this->vendorDir, 0, 7) !== 'phar://') {
|
||||
$projectAutoload = $this->vendorDir.'/autoload.php';
|
||||
if (\file_exists($projectAutoload)) {
|
||||
try {
|
||||
require_once $projectAutoload;
|
||||
} catch (\Throwable $e) {
|
||||
// Ignore autoloader errors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (\spl_autoload_functions() as $autoloader) {
|
||||
if (!\is_array($autoloader)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$loader = $autoloader[0] ?? null;
|
||||
if (!\is_object($loader) || \get_class($loader) !== 'Composer\Autoload\ClassLoader') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if this loader contains scoped classes (indicates it's from the PHAR)
|
||||
if (\method_exists($loader, 'getClassMap')) {
|
||||
$classMap = $loader->getClassMap();
|
||||
foreach (\array_keys($classMap) as $class) {
|
||||
if (\preg_match(self::PHAR_SCOPED_PREFIX_PATTERN, $class)) {
|
||||
continue 2; // Skip to next autoloader
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we have an explicit vendor dir, check if this loader serves that directory
|
||||
if ($this->vendorDir !== null) {
|
||||
$hasTargetPaths = false;
|
||||
|
||||
if (\method_exists($loader, 'getPrefixesPsr4')) {
|
||||
$prefixes = $loader->getPrefixesPsr4();
|
||||
foreach ($prefixes as $namespace => $paths) {
|
||||
foreach ($paths as $path) {
|
||||
if (\strpos($path, $this->vendorDir.'/') === 0) {
|
||||
$hasTargetPaths = true;
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$hasTargetPaths) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (\method_exists($loader, 'getClassMap')) {
|
||||
return $loader->getClassMap();
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan autoload directories using a Composer ClassMapGenerator, if available.
|
||||
*
|
||||
* @return array Map of class name => file path
|
||||
*/
|
||||
private function generateClassMap(): array
|
||||
{
|
||||
if ($this->vendorDir === null || !\class_exists('Composer\\ClassMapGenerator\\ClassMapGenerator', true)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Get PSR-4 mappings from Composer
|
||||
$psr4File = $this->vendorDir.'/composer/autoload_psr4.php';
|
||||
if (!\file_exists($psr4File)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
$psr4Map = require $psr4File;
|
||||
if (!\is_array($psr4Map)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$generator = new \Composer\ClassMapGenerator\ClassMapGenerator();
|
||||
|
||||
foreach ($psr4Map as $prefix => $paths) {
|
||||
foreach ($paths as $path) {
|
||||
if (!\is_dir($path) || $this->shouldSkipPath($path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$generator->scanPaths($path);
|
||||
} catch (\Throwable $e) {
|
||||
// Ignore errors (permissions, malformed files, etc.)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $generator->getClassMap()->getMap();
|
||||
} catch (\Throwable $e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the vendor directory by checking registered autoloaders, falling
|
||||
* back to filesystem search.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
private function findVendorDir(): ?string
|
||||
{
|
||||
// When running the PsySH PHAR, skip autoloader detection. It will just return the internal
|
||||
// vendor directory.
|
||||
if ($this->pharPrefix !== null) {
|
||||
// Try to find from autoloader
|
||||
foreach (\spl_autoload_functions() as $autoloader) {
|
||||
if (!\is_array($autoloader)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$loader = $autoloader[0] ?? null;
|
||||
if (!\is_object($loader)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$reflection = new \ReflectionClass($loader);
|
||||
$loaderPath = $reflection->getFileName();
|
||||
$normalizedPath = \str_replace('\\', '/', $loaderPath);
|
||||
|
||||
// Skip any other PHAR-based autoloaders
|
||||
if (\strpos($normalizedPath, 'phar://') === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// ClassLoader is typically at vendor/composer/ClassLoader.php
|
||||
if (\strpos($normalizedPath, '/vendor/composer/') !== false) {
|
||||
return \dirname($loaderPath, 2);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// Ignore and try next autoloader
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Walk up the directory tree from cwd looking for vendor directory
|
||||
$dir = \getcwd();
|
||||
$root = \dirname($dir);
|
||||
|
||||
while ($dir !== $root) {
|
||||
$vendorDir = $dir.'/vendor';
|
||||
if (\is_dir($vendorDir) && \file_exists($vendorDir.'/composer/autoload_psr4.php')) {
|
||||
return $vendorDir;
|
||||
}
|
||||
|
||||
$root = $dir;
|
||||
$dir = \dirname($dir);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize namespace prefixes.
|
||||
*
|
||||
* Removes leading backslash and ensures trailing backslash.
|
||||
*
|
||||
* @param string[] $namespaces
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
private function normalizeNamespaces(array $namespaces): array
|
||||
{
|
||||
return \array_map(function ($namespace) {
|
||||
return \trim($namespace, '\\').'\\';
|
||||
}, $namespaces);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a path should be skipped based on configuration.
|
||||
*
|
||||
* @param string $path File or directory path
|
||||
*
|
||||
* @return bool True if the path should be skipped
|
||||
*/
|
||||
private function shouldSkipPath(string $path): bool
|
||||
{
|
||||
$normalizedPath = \str_replace('\\', '/', $path);
|
||||
|
||||
// Skip paths from PsySH's own PHAR; these are PsySH's bundled dependencies, not user dependencies
|
||||
if ($this->isPathFromPhar($normalizedPath)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if path is under vendor directory
|
||||
if (!$this->includeVendor && $this->vendorDir !== null) {
|
||||
// Resolve relative paths like "vendor/composer/../../src/Cache.php" before comparing
|
||||
$resolvedPath = \realpath($path);
|
||||
if ($resolvedPath === false) {
|
||||
// File doesn't exist, permissions, etc. /shrug
|
||||
return true;
|
||||
}
|
||||
|
||||
$resolvedPath = \str_replace('\\', '/', $resolvedPath);
|
||||
if (\strpos($resolvedPath, $this->vendorDir.'/') === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check test paths
|
||||
if (!$this->includeTests) {
|
||||
$patterns = ['/test/', '/tests/', '/spec/', '/specs/'];
|
||||
foreach ($patterns as $pattern) {
|
||||
if (\stripos($normalizedPath, $pattern) !== false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get classes from class map, filtered based on configured namespace rules and excluded paths.
|
||||
*
|
||||
* @param array $classMap Map of class name => file path
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
private function classesFromClassMap(array $classMap): array
|
||||
{
|
||||
// First filter the map by path
|
||||
$classMap = \array_filter($classMap, function ($path) {
|
||||
return !$this->shouldSkipPath($path);
|
||||
});
|
||||
|
||||
$classes = \array_keys($classMap);
|
||||
|
||||
// Then filter by namespace
|
||||
return \array_values(
|
||||
\array_filter($classes, function ($class) use ($classMap) {
|
||||
// Exclude PsySH's scoped PHAR dependencies (e.g., _Psy3684f4474398\Symfony\...)
|
||||
if (\preg_match(self::PHAR_SCOPED_PREFIX_PATTERN, $class)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Hardcode excluding known-bad classes
|
||||
foreach (self::KNOWN_BAD_NAMESPACES as $namespace) {
|
||||
if (\stripos($class, $namespace) === 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$isVendorClass = $this->isVendorClass($class, $classMap);
|
||||
|
||||
// Apply vendor-specific exclude filters
|
||||
if ($isVendorClass && !empty($this->excludeVendorNamespaces)) {
|
||||
foreach ($this->excludeVendorNamespaces as $namespace) {
|
||||
if (\stripos($class, $namespace) === 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply general exclude filters
|
||||
foreach ($this->excludeNamespaces as $namespace) {
|
||||
if (\stripos($class, $namespace) === 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply vendor-specific include filters
|
||||
if ($isVendorClass && !empty($this->includeVendorNamespaces)) {
|
||||
foreach ($this->includeVendorNamespaces as $namespace) {
|
||||
if (\stripos($class, $namespace) === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false; // Vendor class doesn't match vendor filters
|
||||
}
|
||||
|
||||
// Apply general include filters
|
||||
if (!empty($this->includeNamespaces)) {
|
||||
foreach ($this->includeNamespaces as $namespace) {
|
||||
if (\stripos($class, $namespace) === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// No include filters provided, and didn't match exclude filters
|
||||
return true;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a class is from the vendor directory.
|
||||
*
|
||||
* @param string $class Class name
|
||||
* @param array $classMap Map of class name => file path
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isVendorClass(string $class, array $classMap): bool
|
||||
{
|
||||
if ($this->vendorDir === null || !isset($classMap[$class])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$path = $classMap[$class];
|
||||
|
||||
// Resolve relative paths like "vendor/composer/../../src/Cache.php" before comparing
|
||||
// This ensures consistency with shouldSkipPath() logic
|
||||
$resolvedPath = \realpath($path);
|
||||
if ($resolvedPath === false) {
|
||||
// If realpath fails, fall back to raw path comparison
|
||||
// This matches the behavior in shouldSkipPath()
|
||||
$normalizedPath = \str_replace('\\', '/', $path);
|
||||
|
||||
return \strpos($normalizedPath, $this->vendorDir.'/') === 0;
|
||||
}
|
||||
|
||||
$normalizedPath = \str_replace('\\', '/', $resolvedPath);
|
||||
|
||||
return \strpos($normalizedPath, $this->vendorDir.'/') === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a path is from PsySH's own PHAR.
|
||||
*
|
||||
* @param string $path File path to check
|
||||
*
|
||||
* @return bool True if the path is from PsySH's PHAR
|
||||
*/
|
||||
private function isPathFromPhar(string $path): bool
|
||||
{
|
||||
if ($this->pharPrefix === null || \strpos($path, 'phar://') !== 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return \strpos($path, $this->pharPrefix) === 0;
|
||||
}
|
||||
}
|
||||
65
vendor/psy/psysh/src/TabCompletion/Matcher/AbstractContextAwareMatcher.php
vendored
Executable file
65
vendor/psy/psysh/src/TabCompletion/Matcher/AbstractContextAwareMatcher.php
vendored
Executable file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Psy Shell.
|
||||
*
|
||||
* (c) 2012-2026 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();
|
||||
}
|
||||
}
|
||||
74
vendor/psy/psysh/src/TabCompletion/Matcher/AbstractDefaultParametersMatcher.php
vendored
Executable file
74
vendor/psy/psysh/src/TabCompletion/Matcher/AbstractDefaultParametersMatcher.php
vendored
Executable file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Psy Shell.
|
||||
*
|
||||
* (c) 2012-2026 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
|
||||
*/
|
||||
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).']';
|
||||
}
|
||||
}
|
||||
183
vendor/psy/psysh/src/TabCompletion/Matcher/AbstractMatcher.php
vendored
Executable file
183
vendor/psy/psysh/src/TabCompletion/Matcher/AbstractMatcher.php
vendored
Executable file
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Psy Shell.
|
||||
*
|
||||
* (c) 2012-2026 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)
|
||||
*/
|
||||
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)
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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)
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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)
|
||||
*/
|
||||
public static function isOperator($token): bool
|
||||
{
|
||||
if (!\is_string($token)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// @phan-suppress-next-line PhanParamSuspiciousOrder - intentionally searching for token in constant string
|
||||
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)
|
||||
*/
|
||||
public static function hasToken(array $coll, $token): bool
|
||||
{
|
||||
if (!\is_array($token)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return \in_array(\token_name($token[0]), $coll);
|
||||
}
|
||||
}
|
||||
87
vendor/psy/psysh/src/TabCompletion/Matcher/ClassAttributesMatcher.php
vendored
Executable file
87
vendor/psy/psysh/src/TabCompletion/Matcher/ClassAttributesMatcher.php
vendored
Executable file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Psy Shell.
|
||||
*
|
||||
* (c) 2012-2026 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;
|
||||
}
|
||||
}
|
||||
64
vendor/psy/psysh/src/TabCompletion/Matcher/ClassMethodDefaultParametersMatcher.php
vendored
Executable file
64
vendor/psy/psysh/src/TabCompletion/Matcher/ClassMethodDefaultParametersMatcher.php
vendored
Executable file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Psy Shell.
|
||||
*
|
||||
* (c) 2012-2026 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;
|
||||
}
|
||||
}
|
||||
84
vendor/psy/psysh/src/TabCompletion/Matcher/ClassMethodsMatcher.php
vendored
Executable file
84
vendor/psy/psysh/src/TabCompletion/Matcher/ClassMethodsMatcher.php
vendored
Executable file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Psy Shell.
|
||||
*
|
||||
* (c) 2012-2026 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;
|
||||
}
|
||||
}
|
||||
78
vendor/psy/psysh/src/TabCompletion/Matcher/ClassNamesMatcher.php
vendored
Executable file
78
vendor/psy/psysh/src/TabCompletion/Matcher/ClassNamesMatcher.php
vendored
Executable file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Psy Shell.
|
||||
*
|
||||
* (c) 2012-2026 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, interfaces, and traits.
|
||||
*
|
||||
* @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(), \get_declared_traits()),
|
||||
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;
|
||||
}
|
||||
}
|
||||
110
vendor/psy/psysh/src/TabCompletion/Matcher/CommandsMatcher.php
vendored
Executable file
110
vendor/psy/psysh/src/TabCompletion/Matcher/CommandsMatcher.php
vendored
Executable file
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Psy Shell.
|
||||
*
|
||||
* (c) 2012-2026 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
|
||||
*/
|
||||
protected function isCommand(string $name): bool
|
||||
{
|
||||
return \in_array($name, $this->commands);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether input matches a defined command.
|
||||
*
|
||||
* @param string $name
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
54
vendor/psy/psysh/src/TabCompletion/Matcher/ConstantsMatcher.php
vendored
Executable file
54
vendor/psy/psysh/src/TabCompletion/Matcher/ConstantsMatcher.php
vendored
Executable file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Psy Shell.
|
||||
*
|
||||
* (c) 2012-2026 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;
|
||||
}
|
||||
}
|
||||
53
vendor/psy/psysh/src/TabCompletion/Matcher/FunctionDefaultParametersMatcher.php
vendored
Executable file
53
vendor/psy/psysh/src/TabCompletion/Matcher/FunctionDefaultParametersMatcher.php
vendored
Executable file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Psy Shell.
|
||||
*
|
||||
* (c) 2012-2026 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;
|
||||
}
|
||||
}
|
||||
56
vendor/psy/psysh/src/TabCompletion/Matcher/FunctionsMatcher.php
vendored
Executable file
56
vendor/psy/psysh/src/TabCompletion/Matcher/FunctionsMatcher.php
vendored
Executable file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Psy Shell.
|
||||
*
|
||||
* (c) 2012-2026 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::hasToken([self::T_NEW, self::T_OBJECT_OPERATOR], $prevToken):
|
||||
return false;
|
||||
case self::hasToken([self::T_OPEN_TAG, self::T_STRING], $token):
|
||||
case self::isOperator($token):
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
83
vendor/psy/psysh/src/TabCompletion/Matcher/KeywordsMatcher.php
vendored
Executable file
83
vendor/psy/psysh/src/TabCompletion/Matcher/KeywordsMatcher.php
vendored
Executable file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Psy Shell.
|
||||
*
|
||||
* (c) 2012-2026 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 string[]
|
||||
*/
|
||||
public function getKeywords(): array
|
||||
{
|
||||
return $this->keywords;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether $keyword is a (completable) PHP keyword.
|
||||
*
|
||||
* @param string $keyword
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
147
vendor/psy/psysh/src/TabCompletion/Matcher/MagicMethodsMatcher.php
vendored
Executable file
147
vendor/psy/psysh/src/TabCompletion/Matcher/MagicMethodsMatcher.php
vendored
Executable file
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Psy Shell.
|
||||
*
|
||||
* (c) 2012-2026 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;
|
||||
use Psy\Util\Docblock;
|
||||
|
||||
/**
|
||||
* A magic method tab completion Matcher.
|
||||
*
|
||||
* This matcher provides completion for magic methods declared via @method
|
||||
* docblock tags on classes and objects.
|
||||
*/
|
||||
class MagicMethodsMatcher 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)) {
|
||||
// Pop the operator (-> or ::) which is now at the end
|
||||
$operatorToken = \array_pop($tokens);
|
||||
} else {
|
||||
// First token IS the operator
|
||||
$operatorToken = $firstToken;
|
||||
}
|
||||
|
||||
// Determine if this is instance (->) or static (::) context
|
||||
$isStatic = self::tokenIs($operatorToken, self::T_DOUBLE_COLON);
|
||||
|
||||
if ($isStatic) {
|
||||
return $this->getStaticMatches($tokens, $input);
|
||||
}
|
||||
|
||||
return $this->getInstanceMatches($tokens, $input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get magic method matches for instance context ($obj->).
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
private function getInstanceMatches(array $tokens, string $input): array
|
||||
{
|
||||
$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 [];
|
||||
}
|
||||
|
||||
try {
|
||||
$reflection = new \ReflectionClass($object);
|
||||
} catch (\ReflectionException $e) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$magicMethods = Docblock::getMagicMethods($reflection);
|
||||
|
||||
$matches = [];
|
||||
foreach ($magicMethods as $method) {
|
||||
if (self::startsWith($input, $method->getName())) {
|
||||
$matches[] = $method->getName();
|
||||
}
|
||||
}
|
||||
|
||||
return $matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get magic method matches for static context (Class::).
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
private function getStaticMatches(array $tokens, string $input): array
|
||||
{
|
||||
$class = $this->getNamespaceAndClass($tokens);
|
||||
|
||||
try {
|
||||
$reflection = new \ReflectionClass($class);
|
||||
} catch (\ReflectionException $e) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$magicMethods = Docblock::getMagicMethods($reflection);
|
||||
|
||||
// For static context, only return static magic methods
|
||||
// unless this is a command like `doc` or `ls` that wants all methods
|
||||
$needAll = self::needCompleteClass($tokens[1] ?? null);
|
||||
|
||||
$matches = [];
|
||||
foreach ($magicMethods as $method) {
|
||||
if ($needAll || $method->isStatic()) {
|
||||
if (self::startsWith($input, $method->getName())) {
|
||||
$chunks = \explode('\\', $class);
|
||||
$className = \array_pop($chunks);
|
||||
$matches[] = $className.'::'.$method->getName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hasMatched(array $tokens): bool
|
||||
{
|
||||
$token = \array_pop($tokens);
|
||||
$prevToken = \array_pop($tokens);
|
||||
|
||||
switch (true) {
|
||||
// Instance: $obj-> or $obj->meth
|
||||
case self::tokenIs($token, self::T_OBJECT_OPERATOR):
|
||||
case self::tokenIs($prevToken, self::T_OBJECT_OPERATOR):
|
||||
// Static: Class:: or Class::meth
|
||||
case self::tokenIs($token, self::T_DOUBLE_COLON):
|
||||
case self::tokenIs($prevToken, self::T_DOUBLE_COLON) && self::tokenIs($token, self::T_STRING):
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
144
vendor/psy/psysh/src/TabCompletion/Matcher/MagicPropertiesMatcher.php
vendored
Executable file
144
vendor/psy/psysh/src/TabCompletion/Matcher/MagicPropertiesMatcher.php
vendored
Executable file
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Psy Shell.
|
||||
*
|
||||
* (c) 2012-2026 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;
|
||||
use Psy\Util\Docblock;
|
||||
|
||||
/**
|
||||
* A magic property tab completion Matcher.
|
||||
*
|
||||
* This matcher provides completion for magic properties declared via
|
||||
* {@literal @}property, {@literal @}property-read, and {@literal @}property-write
|
||||
* docblock tags on classes and objects.
|
||||
*/
|
||||
class MagicPropertiesMatcher 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)) {
|
||||
// Pop the operator (-> or ::) which is now at the end
|
||||
$operatorToken = \array_pop($tokens);
|
||||
} else {
|
||||
// First token IS the operator
|
||||
$operatorToken = $firstToken;
|
||||
}
|
||||
|
||||
// Determine if this is instance (->) or static (::) context
|
||||
$isStatic = self::tokenIs($operatorToken, self::T_DOUBLE_COLON);
|
||||
|
||||
if ($isStatic) {
|
||||
return $this->getStaticMatches($tokens, $input);
|
||||
}
|
||||
|
||||
return $this->getInstanceMatches($tokens, $input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get magic property matches for instance context ($obj->).
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
private function getInstanceMatches(array $tokens, string $input): array
|
||||
{
|
||||
$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 [];
|
||||
}
|
||||
|
||||
try {
|
||||
$reflection = new \ReflectionClass($object);
|
||||
} catch (\ReflectionException $e) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$magicProperties = Docblock::getMagicProperties($reflection);
|
||||
|
||||
$matches = [];
|
||||
foreach ($magicProperties as $property) {
|
||||
if (self::startsWith($input, $property->getName())) {
|
||||
$matches[] = $property->getName();
|
||||
}
|
||||
}
|
||||
|
||||
return $matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get magic property matches for static context (Class::).
|
||||
*
|
||||
* Static magic properties are less common but we support them for completeness.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
private function getStaticMatches(array $tokens, string $input): array
|
||||
{
|
||||
$class = $this->getNamespaceAndClass($tokens);
|
||||
|
||||
try {
|
||||
$reflection = new \ReflectionClass($class);
|
||||
} catch (\ReflectionException $e) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$magicProperties = Docblock::getMagicProperties($reflection);
|
||||
|
||||
$matches = [];
|
||||
foreach ($magicProperties as $property) {
|
||||
if (self::startsWith($input, $property->getName())) {
|
||||
$chunks = \explode('\\', $class);
|
||||
$className = \array_pop($chunks);
|
||||
$matches[] = $className.'::$'.$property->getName();
|
||||
}
|
||||
}
|
||||
|
||||
return $matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hasMatched(array $tokens): bool
|
||||
{
|
||||
$token = \array_pop($tokens);
|
||||
$prevToken = \array_pop($tokens);
|
||||
|
||||
switch (true) {
|
||||
// Instance: $obj-> or $obj->prop
|
||||
case self::tokenIs($token, self::T_OBJECT_OPERATOR):
|
||||
case self::tokenIs($prevToken, self::T_OBJECT_OPERATOR):
|
||||
// Static: Class:: or Class::$prop
|
||||
case self::tokenIs($token, self::T_DOUBLE_COLON):
|
||||
case self::tokenIs($prevToken, self::T_DOUBLE_COLON) && self::tokenIs($token, self::T_STRING):
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
71
vendor/psy/psysh/src/TabCompletion/Matcher/MongoClientMatcher.php
vendored
Executable file
71
vendor/psy/psysh/src/TabCompletion/Matcher/MongoClientMatcher.php
vendored
Executable file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Psy Shell.
|
||||
*
|
||||
* (c) 2012-2026 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;
|
||||
}
|
||||
}
|
||||
67
vendor/psy/psysh/src/TabCompletion/Matcher/MongoDatabaseMatcher.php
vendored
Executable file
67
vendor/psy/psysh/src/TabCompletion/Matcher/MongoDatabaseMatcher.php
vendored
Executable file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Psy Shell.
|
||||
*
|
||||
* (c) 2012-2026 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;
|
||||
}
|
||||
}
|
||||
78
vendor/psy/psysh/src/TabCompletion/Matcher/ObjectAttributesMatcher.php
vendored
Executable file
78
vendor/psy/psysh/src/TabCompletion/Matcher/ObjectAttributesMatcher.php
vendored
Executable file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Psy Shell.
|
||||
*
|
||||
* (c) 2012-2026 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;
|
||||
}
|
||||
}
|
||||
71
vendor/psy/psysh/src/TabCompletion/Matcher/ObjectMethodDefaultParametersMatcher.php
vendored
Executable file
71
vendor/psy/psysh/src/TabCompletion/Matcher/ObjectMethodDefaultParametersMatcher.php
vendored
Executable file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Psy Shell.
|
||||
*
|
||||
* (c) 2012-2026 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;
|
||||
}
|
||||
}
|
||||
80
vendor/psy/psysh/src/TabCompletion/Matcher/ObjectMethodsMatcher.php
vendored
Executable file
80
vendor/psy/psysh/src/TabCompletion/Matcher/ObjectMethodsMatcher.php
vendored
Executable file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Psy Shell.
|
||||
*
|
||||
* (c) 2012-2026 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;
|
||||
}
|
||||
}
|
||||
51
vendor/psy/psysh/src/TabCompletion/Matcher/VariablesMatcher.php
vendored
Executable file
51
vendor/psy/psysh/src/TabCompletion/Matcher/VariablesMatcher.php
vendored
Executable file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Psy Shell.
|
||||
*
|
||||
* (c) 2012-2026 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user