Add Index class
parent
10fb3c92e0
commit
347dd14b20
|
@ -0,0 +1,164 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace LanguageServer;
|
||||||
|
|
||||||
|
use LanguageServer\Protocol\{SymbolInformation, TextDocumentIdentifier, ClientCapabilities};
|
||||||
|
use phpDocumentor\Reflection\DocBlockFactory;
|
||||||
|
use Sabre\Event\Promise;
|
||||||
|
use function Sabre\Event\coroutine;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the index of a project or dependency
|
||||||
|
* Serializable for caching
|
||||||
|
*/
|
||||||
|
class Index
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* An associative array that maps fully qualified symbol names to Definitions
|
||||||
|
*
|
||||||
|
* @var Definition[]
|
||||||
|
*/
|
||||||
|
private $definitions = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An associative array that maps fully qualified symbol names to arrays of document URIs that reference the symbol
|
||||||
|
*
|
||||||
|
* @var string[][]
|
||||||
|
*/
|
||||||
|
private $references = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an associative array [string => Definition] that maps fully qualified symbol names
|
||||||
|
* to Definitions
|
||||||
|
*
|
||||||
|
* @return Definitions[]
|
||||||
|
*/
|
||||||
|
public function getDefinitions()
|
||||||
|
{
|
||||||
|
return $this->definitions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Definition object by a specific FQN
|
||||||
|
*
|
||||||
|
* @param string $fqn
|
||||||
|
* @param bool $globalFallback Whether to fallback to global if the namespaced FQN was not found
|
||||||
|
* @return Definition|null
|
||||||
|
*/
|
||||||
|
public function getDefinition(string $fqn, $globalFallback = false)
|
||||||
|
{
|
||||||
|
if (isset($this->definitions[$fqn])) {
|
||||||
|
return $this->definitions[$fqn];
|
||||||
|
} else if ($globalFallback) {
|
||||||
|
$parts = explode('\\', $fqn);
|
||||||
|
$fqn = end($parts);
|
||||||
|
return $this->getDefinition($fqn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a definition
|
||||||
|
*
|
||||||
|
* @param string $fqn The fully qualified name of the symbol
|
||||||
|
* @param string $definition The Definition object
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setDefinition(string $fqn, Definition $definition)
|
||||||
|
{
|
||||||
|
$this->definitions[$fqn] = $definition;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the Definition index
|
||||||
|
*
|
||||||
|
* @param Definition[] $definitions Map from FQN to Definition
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setDefinitions(array $definitions)
|
||||||
|
{
|
||||||
|
$this->definitions = $definitions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsets the Definition for a specific symbol
|
||||||
|
* and removes all references pointing to that symbol
|
||||||
|
*
|
||||||
|
* @param string $fqn The fully qualified name of the symbol
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function removeDefinition(string $fqn)
|
||||||
|
{
|
||||||
|
unset($this->definitions[$fqn]);
|
||||||
|
unset($this->references[$fqn]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a document URI as a referencee of a specific symbol
|
||||||
|
*
|
||||||
|
* @param string $fqn The fully qualified name of the symbol
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function addReferenceUri(string $fqn, string $uri)
|
||||||
|
{
|
||||||
|
if (!isset($this->references[$fqn])) {
|
||||||
|
$this->references[$fqn] = [];
|
||||||
|
}
|
||||||
|
// TODO: use DS\Set instead of searching array
|
||||||
|
if (array_search($uri, $this->references[$fqn], true) === false) {
|
||||||
|
$this->references[$fqn][] = $uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a document URI as the container for a specific symbol
|
||||||
|
*
|
||||||
|
* @param string $fqn The fully qualified name of the symbol
|
||||||
|
* @param string $uri The URI
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function removeReferenceUri(string $fqn, string $uri)
|
||||||
|
{
|
||||||
|
if (!isset($this->references[$fqn])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$index = array_search($fqn, $this->references[$fqn], true);
|
||||||
|
if ($index === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
array_splice($this->references[$fqn], $index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an associative array [string => string[]] that maps fully qualified symbol names
|
||||||
|
* to URIs of the document where the symbol is referenced
|
||||||
|
*
|
||||||
|
* @return string[][]
|
||||||
|
*/
|
||||||
|
public function getReferenceUris()
|
||||||
|
{
|
||||||
|
return $this->references;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the reference index
|
||||||
|
*
|
||||||
|
* @param string[][] $references an associative array [string => string[]] from FQN to URIs
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setReferenceUris(array $references)
|
||||||
|
{
|
||||||
|
$this->references = $references;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the given FQN is defined in the project
|
||||||
|
*
|
||||||
|
* @param string $fqn The fully qualified name of the symbol
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isDefined(string $fqn): bool
|
||||||
|
{
|
||||||
|
return isset($this->definitions[$fqn]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -183,29 +183,33 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
||||||
private function indexProject(): Promise
|
private function indexProject(): Promise
|
||||||
{
|
{
|
||||||
return coroutine(function () {
|
return coroutine(function () {
|
||||||
$textDocuments = yield $this->findPhpFiles();
|
$pattern = Path::makeAbsolute('**/{*.php,composer.lock}', $this->rootPath);
|
||||||
$count = count($textDocuments);
|
$phpPattern = Path::makeAbsolute('**/*.php', $this->rootPath);
|
||||||
|
$composerLockPattern = Path::makeAbsolute('**/composer.lock', $this->rootPath);
|
||||||
|
$uris = yield $this->findFiles($pattern);
|
||||||
|
$count = count($uris);
|
||||||
|
|
||||||
$startTime = microtime(true);
|
$startTime = microtime(true);
|
||||||
|
|
||||||
foreach ($textDocuments as $i => $textDocument) {
|
foreach ($uris as $i => $uri) {
|
||||||
// Give LS to the chance to handle requests while indexing
|
// Give LS to the chance to handle requests while indexing
|
||||||
yield timeout();
|
yield timeout();
|
||||||
|
if (Glob::match())
|
||||||
$this->client->window->logMessage(
|
$this->client->window->logMessage(
|
||||||
MessageType::LOG,
|
MessageType::LOG,
|
||||||
"Parsing file $i/$count: {$textDocument->uri}"
|
"Parsing file $i/$count: {$uri}"
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
yield $this->project->loadDocument($textDocument->uri);
|
yield $this->project->loadDocument($uri);
|
||||||
} catch (ContentTooLargeException $e) {
|
} catch (ContentTooLargeException $e) {
|
||||||
$this->client->window->logMessage(
|
$this->client->window->logMessage(
|
||||||
MessageType::INFO,
|
MessageType::INFO,
|
||||||
"Ignoring file {$textDocument->uri} because it exceeds size limit of {$e->limit} bytes ({$e->size})"
|
"Ignoring file {$uri} because it exceeds size limit of {$e->limit} bytes ({$e->size})"
|
||||||
);
|
);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$this->client->window->logMessage(
|
$this->client->window->logMessage(
|
||||||
MessageType::ERROR,
|
MessageType::ERROR,
|
||||||
"Error parsing file {$textDocument->uri}: " . (string)$e
|
"Error parsing file {$uri}: " . (string)$e
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -223,29 +227,29 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
||||||
* Returns all PHP files in the workspace.
|
* Returns all PHP files in the workspace.
|
||||||
* If the client does not support workspace/files, it falls back to searching the file system directly.
|
* If the client does not support workspace/files, it falls back to searching the file system directly.
|
||||||
*
|
*
|
||||||
* @return Promise <TextDocumentIdentifier[]>
|
* @param string $pattern
|
||||||
|
* @return Promise <string[]>
|
||||||
*/
|
*/
|
||||||
private function findPhpFiles(): Promise
|
private function findFiles(string $pattern): Promise
|
||||||
{
|
{
|
||||||
return coroutine(function () {
|
return coroutine(function () {
|
||||||
$textDocuments = [];
|
$uris = [];
|
||||||
$pattern = Path::makeAbsolute('**/*.php', $this->rootPath);
|
|
||||||
if ($this->clientCapabilities->xfilesProvider) {
|
if ($this->clientCapabilities->xfilesProvider) {
|
||||||
// Use xfiles request
|
// Use xfiles request
|
||||||
foreach (yield $this->client->workspace->xfiles() as $textDocument) {
|
foreach (yield $this->client->workspace->xfiles() as $textDocument) {
|
||||||
$path = Uri\parse($textDocument->uri)['path'];
|
$path = Uri\parse($textDocument->uri)['path'];
|
||||||
if (Glob::match($path, $pattern)) {
|
if (Glob::match($path, $pattern)) {
|
||||||
$textDocuments[] = $textDocument;
|
$uris[] = $textDocument->uri;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Use the file system
|
// Use the file system
|
||||||
foreach (new GlobIterator($pattern) as $path) {
|
foreach (new GlobIterator($pattern) as $path) {
|
||||||
$textDocuments[] = new TextDocumentIdentifier(pathToUri($path));
|
$uris[] = pathToUri($path);
|
||||||
yield timeout();
|
yield timeout();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $textDocuments;
|
return $uris;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,14 @@ class Project
|
||||||
*/
|
*/
|
||||||
private $documents = [];
|
private $documents = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Associative array from package identifier to index
|
||||||
|
* The empty string represents the project itself
|
||||||
|
*
|
||||||
|
* @var Index[]
|
||||||
|
*/
|
||||||
|
private $indexes = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An associative array that maps fully qualified symbol names to Definitions
|
* An associative array that maps fully qualified symbol names to Definitions
|
||||||
*
|
*
|
||||||
|
|
Loading…
Reference in New Issue