1
0
Fork 0

Add Index class

pull/214/head
Felix Becker 2016-12-07 08:36:33 +01:00
parent 10fb3c92e0
commit 347dd14b20
3 changed files with 190 additions and 14 deletions

164
src/Index.php Normal file
View File

@ -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]);
}
}

View File

@ -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;
}); });
} }
} }

View File

@ -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
* *