1
0
Fork 0
php-language-server/src/Server/Workspace.php

191 lines
7.1 KiB
PHP
Raw Normal View History

<?php
declare(strict_types = 1);
namespace LanguageServer\Server;
use LanguageServer\{LanguageClient, Project, PhpDocumentLoader};
use LanguageServer\Index\{ProjectIndex, DependenciesIndex, Index};
use LanguageServer\Protocol\{SymbolInformation, SymbolDescriptor, ReferenceInformation, DependencyReference, Location};
use Sabre\Event\Promise;
2017-01-27 17:56:08 +00:00
use Rx\Observable;
use function Sabre\Event\coroutine;
2017-01-25 00:38:11 +00:00
use function LanguageServer\waitForEvent;
/**
* Provides method handlers for all workspace/* methods
*/
class Workspace
{
/**
2016-12-13 00:51:02 +00:00
* The symbol index for the workspace
*
2016-12-13 00:51:02 +00:00
* @var ProjectIndex
*/
2016-12-13 00:51:02 +00:00
private $index;
2016-12-13 00:51:02 +00:00
/**
* @var DependenciesIndex
*/
private $dependenciesIndex;
/**
* @var Index
*/
private $sourceIndex;
/**
* @var \stdClass
*/
public $composerLock;
/**
* @var PhpDocumentLoader
*/
public $documentLoader;
/**
* @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
*/
public function __construct(ProjectIndex $index, DependenciesIndex $dependenciesIndex, Index $sourceIndex, \stdClass $composerLock = null, PhpDocumentLoader $documentLoader)
{
$this->sourceIndex = $sourceIndex;
2016-12-13 00:51:02 +00:00
$this->index = $index;
$this->dependenciesIndex = $dependenciesIndex;
$this->composerLock = $composerLock;
$this->documentLoader = $documentLoader;
}
/**
* 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-27 17:56:08 +00:00
* @return Observable Will yield JSON Patch Operations that eventually result in SymbolInformation[]
*/
2017-01-26 20:15:33 +00:00
public function symbol(string $query): Observable
{
2017-01-27 17:56:08 +00:00
return Observable::just(null)
// Wait for indexing event if not yet finished
->flatMap(function () {
if (!$this->index->isStaticComplete()) {
return observableFromEvent($this->index, 'static-complete')->take(1);
2017-01-25 00:38:11 +00:00
}
2017-01-27 17:56:08 +00:00
})
// Get definitions from complete index
->flatMap(function () {
return Observable::fromArray($this->index->getDefinitions());
})
// Filter by matching FQN to query
->filter(function (Definition $def) use ($query) {
return $query === '' || stripos($def->fqn, $query) !== false;
})
// Send each SymbolInformation
->map(function (Definition $def) use ($query) {
return new Operation\Add('/-', $def->symbolInformation);
})
// Initialize with an empty array
->startWith(new Operation\Replace('/', []));
}
/**
* 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.
2017-01-27 17:56:08 +00:00
* @return Observable ReferenceInformation[]
*/
2017-01-27 17:56:08 +00:00
public function xreferences($query, array $files = null): Observable
{
2017-01-27 17:56:08 +00:00
return Observable::just(null)
->flatMap(function () {
if ($this->composerLock === null) {
return Observable::empty();
}
2017-01-27 17:56:08 +00:00
// Wait until indexing finished
if (!$this->index->isComplete()) {
return observableFromEvent($this->index, 'complete')->take(1);
}
})
// Get all definition FQNs in dependencies
->flatMap(function () {
if (isset($query->fqsen)) {
$fqns = [$this->dependenciesIndex->getDefinition($query->fqsen)];
} else {
$fqns = $this->dependenciesIndex->getDefinitions();
}
return Observable::fromArray($fqns);
})
// Get all URIs in the project source that reference those definitions
->flatMap(function (Definition $def) {
return Observable::fromArray($this->sourceIndex->getReferenceUris($fqn));
})
->distinct()
->flatMap(function (string $uri) {
return $this->documentLoader->getOrLoad($uri);
$symbol = new SymbolDescriptor;
$symbol->fqsen = $fqn;
foreach (get_object_vars($def->symbolInformation) as $prop => $val) {
$symbol->$prop = $val;
}
// Find out package name
preg_match('/\/vendor\/([^\/]+\/[^\/]+)\//', $def->symbolInformation->location->uri, $matches);
$packageName = $matches[1];
foreach (array_merge($this->composerLock->packages, $this->composerLock->{'packages-dev'}) as $package) {
if ($package->name === $packageName) {
$symbol->package = $package;
break;
}
2017-01-27 17:56:08 +00:00
}
})
// 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;
}
}
2017-01-27 17:56:08 +00:00
if (!$matches) {
continue;
}
}
2017-01-27 17:56:08 +00:00
foreach ($doc->getReferenceNodesByFqn($fqn) as $node) {
$refInfo = new ReferenceInformation;
$refInfo->reference = Location::fromNode($node);
$refInfo->symbol = $symbol;
$refInfos[] = $refInfo;
}
})
->flatMap(function (PhpDocument $doc) use ($fqn) {
})
$refInfos = [];
foreach ($refs as $uri => $fqns) {
foreach ($fqns as $fqn) {
}
}
return $refInfos;
2017-01-27 17:56:08 +00:00
})
->startWith(new Operation\Replace('/', []));
}
/**
* @return DependencyReference[]
*/
public function xdependencies(): array
{
if ($this->composerLock === null) {
return [];
}
$dependencyReferences = [];
foreach ($this->composerLock->packages as $package) {
$dependencyReferences[] = new DependencyReference($package);
}
return $dependencyReferences;
}
}