diff --git a/.gitignore b/.gitignore index e7997bf..f6b089b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ .DS_Store -.vscode .idea vendor/ .phpls/ diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..0f77709 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,29 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Listen for XDebug", + "type": "php", + "request": "launch", + "port": 9000 + }, + { + "name": "Launch currently open script", + "type": "php", + "request": "launch", + "program": "${file}", + "cwd": "${fileDirname}", + "port": 9000 + }, + { + "name": "PHPUnit", + "type": "php", + "request": "launch", + "program": "${workspaceRoot}/vendor/phpunit/phpunit/phpunit", + "cwd": "${workspaceRoot}", + "args": [ + // "--filter", "CompletionTest" + ] + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..38150bd --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +// Place your settings in this file to overwrite default and user settings. +{ + "search.exclude": { + "**/validation": true, + "**/tests/Validation/cases": true + } +} diff --git a/README.md b/README.md index 37800e0..432d874 100644 --- a/README.md +++ b/README.md @@ -170,7 +170,7 @@ Example: #### `--memory-limit=integer` (optional) Sets memory limit for language server. Equivalent to [memory-limit](http://php.net/manual/en/ini.core.php#ini.memory-limit) php.ini directive. -By default there is no memory limit. +The default is 4GB (which is way more than needed). Example: diff --git a/bin/php-language-server.php b/bin/php-language-server.php index 0e3de2a..2a2ab69 100644 --- a/bin/php-language-server.php +++ b/bin/php-language-server.php @@ -6,7 +6,7 @@ use Composer\{Factory, XdebugHandler}; $options = getopt('', ['tcp::', 'tcp-server::', 'memory-limit::']); -ini_set('memory_limit', $options['memory-limit'] ?? -1); +ini_set('memory_limit', $options['memory-limit'] ?? '4G'); foreach ([__DIR__ . '/../../../autoload.php', __DIR__ . '/../autoload.php', __DIR__ . '/../vendor/autoload.php'] as $file) { if (file_exists($file)) { diff --git a/src/Protocol/PackageDescriptor.php b/src/Protocol/PackageDescriptor.php new file mode 100644 index 0000000..e635740 --- /dev/null +++ b/src/Protocol/PackageDescriptor.php @@ -0,0 +1,25 @@ +name = $name; + } +} diff --git a/src/Protocol/SymbolDescriptor.php b/src/Protocol/SymbolDescriptor.php index fa74bcb..4116864 100644 --- a/src/Protocol/SymbolDescriptor.php +++ b/src/Protocol/SymbolDescriptor.php @@ -3,7 +3,10 @@ declare(strict_types = 1); namespace LanguageServer\Protocol; -class SymbolDescriptor extends SymbolInformation +/** + * Uniquely identifies a symbol + */ +class SymbolDescriptor { /** * The fully qualified structural element name, a globally unique identifier for the symbol. @@ -13,19 +16,17 @@ class SymbolDescriptor extends SymbolInformation public $fqsen; /** - * A package from the composer.lock file or the contents of the composer.json - * Example: https://github.com/composer/composer/blob/master/composer.lock#L10 - * Available fields may differ + * Identifies the Composer package the symbol is defined in (if any) * - * @var object|null + * @var PackageDescriptor|null */ public $package; /** - * @param string $fqsen The fully qualified structural element name, a globally unique identifier for the symbol. - * @param object $package A package from the composer.lock file or the contents of the composer.json + * @param string $fqsen The fully qualified structural element name, a globally unique identifier for the symbol. + * @param PackageDescriptor $package Identifies the Composer package the symbol is defined in */ - public function __construct(string $fqsen = null, $package = null) + public function __construct(string $fqsen = null, PackageDescriptor $package = null) { $this->fqsen = $fqsen; $this->package = $package; diff --git a/src/Server/TextDocument.php b/src/Server/TextDocument.php index 2e8e4bf..5a2819e 100644 --- a/src/Server/TextDocument.php +++ b/src/Server/TextDocument.php @@ -8,14 +8,26 @@ use LanguageServer\{ }; use LanguageServer\Index\ReadableIndex; use LanguageServer\Protocol\{ - FormattingOptions, Hover, Location, MarkedString, Position, Range, ReferenceContext, SymbolDescriptor, SymbolLocationInformation, TextDocumentIdentifier, TextDocumentItem, VersionedTextDocumentIdentifier + FormattingOptions, + Hover, + Location, + MarkedString, + Position, + Range, + ReferenceContext, + SymbolDescriptor, + PackageDescriptor, + SymbolLocationInformation, + TextDocumentIdentifier, + TextDocumentItem, + VersionedTextDocumentIdentifier }; use Microsoft\PhpParser; use Microsoft\PhpParser\Node; use Sabre\Event\Promise; use Sabre\Uri; use function LanguageServer\{ - isVendored, waitForEvent + isVendored, waitForEvent, getPackageName }; use function Sabre\Event\coroutine; @@ -368,9 +380,10 @@ class TextDocument return []; } // Handle definition nodes + $fqn = DefinitionResolver::getDefinedFqn($node); while (true) { if ($fqn) { - $def = $this->index->getDefinition($definedFqn); + $def = $this->index->getDefinition($fqn); } else { // Handle reference nodes $def = $this->definitionResolver->resolveReferenceNodeToDefinition($node); @@ -388,25 +401,14 @@ class TextDocument ) { return []; } - $symbol = new SymbolDescriptor; - foreach (get_object_vars($def->symbolInformation) as $prop => $val) { - $symbol->$prop = $val; - } - $symbol->fqsen = $def->fqn; + // if Definition is inside a dependency, use the package name $packageName = getPackageName($def->symbolInformation->location->uri, $this->composerJson); - if ($packageName && $this->composerLock !== null) { - // Definition is inside a dependency - foreach (array_merge($this->composerLock->packages, $this->composerLock->{'packages-dev'}) as $package) { - if ($package->name === $packageName) { - $symbol->package = $package; - break; - } - } - } else if ($this->composerJson !== null) { - // Definition belongs to a root package - $symbol->package = $this->composerJson; + // else use the package name of the root package (if exists) + if (!$packageName && $this->composerJson !== null) { + $packageName = $this->composerJson->name; } - return [new SymbolLocationInformation($symbol, $symbol->location)]; + $descriptor = new SymbolDescriptor($def->fqn, new PackageDescriptor($packageName)); + return [new SymbolLocationInformation($descriptor, $def->symbolInformation->location)]; }); } } diff --git a/src/Server/Workspace.php b/src/Server/Workspace.php index d2f8cec..61db068 100644 --- a/src/Server/Workspace.php +++ b/src/Server/Workspace.php @@ -10,13 +10,15 @@ use LanguageServer\Protocol\{ FileEvent, SymbolInformation, SymbolDescriptor, + PackageDescriptor, ReferenceInformation, DependencyReference, - Location + Location, + MessageType }; use Sabre\Event\Promise; use function Sabre\Event\coroutine; -use function LanguageServer\{waitForEvent, getPackageName}; +use function LanguageServer\waitForEvent; /** * Provides method handlers for all workspace/* methods @@ -33,7 +35,7 @@ class Workspace * * @var ProjectIndex */ - private $index; + private $projectIndex; /** * @var DependenciesIndex @@ -57,17 +59,17 @@ class Workspace /** * @param LanguageClient $client LanguageClient instance used to signal updated results - * @param ProjectIndex $index Index that is searched on a workspace/symbol request + * @param ProjectIndex $projectIndex Index that is used to wait for full index completeness * @param DependenciesIndex $dependenciesIndex Index that is used on a workspace/xreferences request * @param DependenciesIndex $sourceIndex Index that is used on a workspace/xreferences request * @param \stdClass $composerLock The parsed composer.lock of the project, if any * @param PhpDocumentLoader $documentLoader PhpDocumentLoader instance to load documents */ - public function __construct(LanguageClient $client, ProjectIndex $index, DependenciesIndex $dependenciesIndex, Index $sourceIndex, \stdClass $composerLock = null, PhpDocumentLoader $documentLoader, \stdClass $composerJson = null) + public function __construct(LanguageClient $client, ProjectIndex $projectIndex, DependenciesIndex $dependenciesIndex, Index $sourceIndex, \stdClass $composerLock = null, PhpDocumentLoader $documentLoader, \stdClass $composerJson = null) { $this->client = $client; $this->sourceIndex = $sourceIndex; - $this->index = $index; + $this->projectIndex = $projectIndex; $this->dependenciesIndex = $dependenciesIndex; $this->composerLock = $composerLock; $this->documentLoader = $documentLoader; @@ -84,11 +86,11 @@ class Workspace { return coroutine(function () use ($query) { // Wait until indexing for definitions finished - if (!$this->index->isStaticComplete()) { - yield waitForEvent($this->index, 'static-complete'); + if (!$this->sourceIndex->isStaticComplete()) { + yield waitForEvent($this->sourceIndex, 'static-complete'); } $symbols = []; - foreach ($this->index->getDefinitions() as $fqn => $definition) { + foreach ($this->sourceIndex->getDefinitions() as $fqn => $definition) { if ($query === '' || stripos($fqn, $query) !== false) { $symbols[] = $definition->symbolInformation; } @@ -126,8 +128,8 @@ class Workspace return []; } // Wait until indexing finished - if (!$this->index->isComplete()) { - yield waitForEvent($this->index, 'complete'); + if (!$this->projectIndex->isComplete()) { + yield waitForEvent($this->projectIndex, 'complete'); } /** Map from URI to array of referenced FQNs in dependencies */ $refs = []; @@ -146,38 +148,11 @@ class Workspace $refInfos = []; foreach ($refs as $uri => $fqns) { foreach ($fqns as $fqn) { - $def = $this->dependenciesIndex->getDefinition($fqn); - $symbol = new SymbolDescriptor; - $symbol->fqsen = $fqn; - foreach (get_object_vars($def->symbolInformation) as $prop => $val) { - $symbol->$prop = $val; - } - // Find out package name - $packageName = getPackageName($def->symbolInformation->location->uri, $this->composerJson); - foreach (array_merge($this->composerLock->packages, $this->composerLock->{'packages-dev'}) as $package) { - if ($package->name === $packageName) { - $symbol->package = $package; - break; - } - } - // If there was no FQSEN provided, check if query attributes match - if (!isset($query->fqsen)) { - $matches = true; - foreach (get_object_vars($query) as $prop => $val) { - if ($query->$prop != $symbol->$prop) { - $matches = false; - break; - } - } - if (!$matches) { - continue; - } - } $doc = yield $this->documentLoader->getOrLoad($uri); foreach ($doc->getReferenceNodesByFqn($fqn) as $node) { $refInfo = new ReferenceInformation; $refInfo->reference = Location::fromNode($node); - $refInfo->symbol = $symbol; + $refInfo->symbol = $query; $refInfos[] = $refInfo; } }