2016-09-30 09:30:08 +00:00
|
|
|
<?php
|
2016-09-30 09:54:49 +00:00
|
|
|
declare(strict_types = 1);
|
2016-09-30 09:30:08 +00:00
|
|
|
|
|
|
|
namespace LanguageServer\Server;
|
|
|
|
|
2017-01-11 01:08:52 +00:00
|
|
|
use LanguageServer\{LanguageClient, Project, PhpDocumentLoader};
|
|
|
|
use LanguageServer\Index\{ProjectIndex, DependenciesIndex, Index};
|
2017-03-01 10:18:37 +00:00
|
|
|
use LanguageServer\Protocol\{
|
|
|
|
FileChangeType,
|
|
|
|
FileEvent,
|
|
|
|
SymbolInformation,
|
|
|
|
SymbolDescriptor,
|
|
|
|
ReferenceInformation,
|
|
|
|
DependencyReference,
|
|
|
|
Location
|
|
|
|
};
|
2017-01-11 01:08:52 +00:00
|
|
|
use Sabre\Event\Promise;
|
|
|
|
use function Sabre\Event\coroutine;
|
2017-02-07 22:20:12 +00:00
|
|
|
use function LanguageServer\{waitForEvent, getPackageName};
|
2016-09-30 09:30:08 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Provides method handlers for all workspace/* methods
|
|
|
|
*/
|
|
|
|
class Workspace
|
|
|
|
{
|
2017-03-01 10:18:37 +00:00
|
|
|
/**
|
|
|
|
* @var LanguageClient
|
|
|
|
*/
|
|
|
|
public $client;
|
|
|
|
|
2016-09-30 09:30:08 +00:00
|
|
|
/**
|
2016-12-13 00:51:02 +00:00
|
|
|
* The symbol index for the workspace
|
2016-09-30 09:30:08 +00:00
|
|
|
*
|
2016-12-13 00:51:02 +00:00
|
|
|
* @var ProjectIndex
|
2016-09-30 09:30:08 +00:00
|
|
|
*/
|
2016-12-13 00:51:02 +00:00
|
|
|
private $index;
|
2016-09-30 09:30:08 +00:00
|
|
|
|
2016-12-13 00:51:02 +00:00
|
|
|
/**
|
2017-01-11 01:08:52 +00:00
|
|
|
* @var DependenciesIndex
|
|
|
|
*/
|
|
|
|
private $dependenciesIndex;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var Index
|
|
|
|
*/
|
|
|
|
private $sourceIndex;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var \stdClass
|
|
|
|
*/
|
|
|
|
public $composerLock;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var PhpDocumentLoader
|
|
|
|
*/
|
|
|
|
public $documentLoader;
|
|
|
|
|
|
|
|
/**
|
2017-03-01 10:18:37 +00:00
|
|
|
* @param LanguageClient $client LanguageClient instance used to signal updated results
|
2017-01-11 01:08:52 +00:00
|
|
|
* @param ProjectIndex $index Index that is searched on a workspace/symbol request
|
|
|
|
* @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
|
2016-12-13 00:51:02 +00:00
|
|
|
*/
|
2017-03-01 10:18:37 +00:00
|
|
|
public function __construct(LanguageClient $client, ProjectIndex $index, DependenciesIndex $dependenciesIndex, Index $sourceIndex, \stdClass $composerLock = null, PhpDocumentLoader $documentLoader, \stdClass $composerJson = null)
|
2016-09-30 09:30:08 +00:00
|
|
|
{
|
2017-03-01 10:18:37 +00:00
|
|
|
$this->client = $client;
|
2017-01-11 01:08:52 +00:00
|
|
|
$this->sourceIndex = $sourceIndex;
|
2016-12-13 00:51:02 +00:00
|
|
|
$this->index = $index;
|
2017-01-11 01:08:52 +00:00
|
|
|
$this->dependenciesIndex = $dependenciesIndex;
|
|
|
|
$this->composerLock = $composerLock;
|
|
|
|
$this->documentLoader = $documentLoader;
|
2017-02-07 22:20:12 +00:00
|
|
|
$this->composerJson = $composerJson;
|
2016-09-30 09:30:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The workspace symbol request is sent from the client to the server to list project-wide symbols matching the query string.
|
|
|
|
*
|
|
|
|
* @param string $query
|
2017-01-25 00:38:11 +00:00
|
|
|
* @return Promise <SymbolInformation[]>
|
2016-09-30 09:30:08 +00:00
|
|
|
*/
|
2017-01-25 00:38:11 +00:00
|
|
|
public function symbol(string $query): Promise
|
2016-09-30 09:30:08 +00:00
|
|
|
{
|
2017-01-25 00:38:11 +00:00
|
|
|
return coroutine(function () use ($query) {
|
|
|
|
// Wait until indexing for definitions finished
|
|
|
|
if (!$this->index->isStaticComplete()) {
|
|
|
|
yield waitForEvent($this->index, 'static-complete');
|
2016-10-11 12:42:56 +00:00
|
|
|
}
|
2017-01-25 00:38:11 +00:00
|
|
|
$symbols = [];
|
|
|
|
foreach ($this->index->getDefinitions() as $fqn => $definition) {
|
|
|
|
if ($query === '' || stripos($fqn, $query) !== false) {
|
|
|
|
$symbols[] = $definition->symbolInformation;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $symbols;
|
|
|
|
});
|
2016-09-30 09:30:08 +00:00
|
|
|
}
|
2017-01-11 01:08:52 +00:00
|
|
|
|
2017-03-01 10:18:37 +00:00
|
|
|
/**
|
|
|
|
* The watched files notification is sent from the client to the server when the client detects changes to files watched by the language client.
|
|
|
|
*
|
|
|
|
* @param FileEvent[] $changes
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function didChangeWatchedFiles(array $changes)
|
|
|
|
{
|
|
|
|
foreach ($changes as $change) {
|
|
|
|
if ($change->type === FileChangeType::DELETED) {
|
|
|
|
$this->client->textDocument->publishDiagnostics($change->uri, []);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-11 01:08:52 +00:00
|
|
|
/**
|
|
|
|
* The workspace references request is sent from the client to the server to locate project-wide references to a symbol given its description / metadata.
|
|
|
|
*
|
|
|
|
* @param SymbolDescriptor $query Partial metadata about the symbol that is being searched for.
|
|
|
|
* @param string[] $files An optional list of files to restrict the search to.
|
|
|
|
* @return ReferenceInformation[]
|
|
|
|
*/
|
|
|
|
public function xreferences($query, array $files = null): Promise
|
|
|
|
{
|
|
|
|
return coroutine(function () use ($query, $files) {
|
|
|
|
if ($this->composerLock === null) {
|
|
|
|
return [];
|
|
|
|
}
|
2017-01-25 00:38:11 +00:00
|
|
|
// Wait until indexing finished
|
|
|
|
if (!$this->index->isComplete()) {
|
|
|
|
yield waitForEvent($this->index, 'complete');
|
|
|
|
}
|
2017-01-11 01:08:52 +00:00
|
|
|
/** Map from URI to array of referenced FQNs in dependencies */
|
|
|
|
$refs = [];
|
|
|
|
// Get all references TO dependencies
|
|
|
|
$fqns = isset($query->fqsen) ? [$query->fqsen] : array_values($this->dependenciesIndex->getDefinitions());
|
|
|
|
foreach ($fqns as $fqn) {
|
|
|
|
foreach ($this->sourceIndex->getReferenceUris($fqn) as $uri) {
|
|
|
|
if (!isset($refs[$uri])) {
|
|
|
|
$refs[$uri] = [];
|
|
|
|
}
|
|
|
|
if (array_search($uri, $refs[$uri]) === false) {
|
|
|
|
$refs[$uri][] = $fqn;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$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
|
2017-02-07 22:20:12 +00:00
|
|
|
$packageName = getPackageName($def->symbolInformation->location->uri, $this->composerJson);
|
2017-02-06 15:35:16 +00:00
|
|
|
foreach (array_merge($this->composerLock->packages, $this->composerLock->{'packages-dev'}) as $package) {
|
2017-01-11 01:08:52 +00:00
|
|
|
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;
|
|
|
|
$refInfos[] = $refInfo;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $refInfos;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return DependencyReference[]
|
|
|
|
*/
|
|
|
|
public function xdependencies(): array
|
|
|
|
{
|
|
|
|
if ($this->composerLock === null) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
$dependencyReferences = [];
|
2017-02-06 15:35:16 +00:00
|
|
|
foreach (array_merge($this->composerLock->packages, $this->composerLock->{'packages-dev'}) as $package) {
|
2017-01-11 01:08:52 +00:00
|
|
|
$dependencyReferences[] = new DependencyReference($package);
|
|
|
|
}
|
|
|
|
return $dependencyReferences;
|
|
|
|
}
|
2016-09-30 09:30:08 +00:00
|
|
|
}
|