parent
34d3d2030d
commit
bedd157636
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace LanguageServer\Cache;
|
||||||
|
|
||||||
|
use Sabre\Event\Promise;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A key/value store for caching purposes
|
||||||
|
*/
|
||||||
|
interface Cache
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Gets a value from the cache
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
* @return Promise <mixed>
|
||||||
|
*/
|
||||||
|
public function get(string $key): Promise;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a value in the cache
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
* @param mixed $value
|
||||||
|
* @return Promise
|
||||||
|
*/
|
||||||
|
public function set(string $key, $value): Promise;
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace LanguageServer\Cache;
|
||||||
|
|
||||||
|
use LanguageServer\LanguageClient;
|
||||||
|
use Sabre\Event\Promise;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Caches content through a xcache/* requests
|
||||||
|
*/
|
||||||
|
class ClientCache implements Cache
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param LanguageClient $client
|
||||||
|
*/
|
||||||
|
public function __construct(LanguageClient $client)
|
||||||
|
{
|
||||||
|
$this->client = $client;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a value from the cache
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
* @return Promise <mixed>
|
||||||
|
*/
|
||||||
|
public function get(string $key): Promise
|
||||||
|
{
|
||||||
|
return $this->client->xcache->get($key)->then('unserialize')->otherwise(function () {
|
||||||
|
// Ignore
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a value in the cache
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
* @param mixed $value
|
||||||
|
* @return Promise
|
||||||
|
*/
|
||||||
|
public function set(string $key, $value): Promise
|
||||||
|
{
|
||||||
|
return $this->client->xcache->set($key, serialize($value))->otherwise(function () {
|
||||||
|
// Ignore
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace LanguageServer\Cache;
|
||||||
|
|
||||||
|
use LanguageServer\LanguageClient;
|
||||||
|
use Sabre\Event\Promise;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Caches content on the file system
|
||||||
|
*/
|
||||||
|
class FileSystemCache implements Cache
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $cacheDir;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
if (PHP_OS === 'WINNT') {
|
||||||
|
$this->cacheDir = getenv('LOCALAPPDATA') . '\\PHP Language Server\\';
|
||||||
|
} else if (getenv('XDG_CACHE_HOME')) {
|
||||||
|
$this->cacheDir = getenv('XDG_CACHE_HOME') . '/phpls/';
|
||||||
|
} else {
|
||||||
|
$this->cacheDir = getenv('HOME') . '/.phpls/';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a value from the cache
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
* @return Promise <mixed>
|
||||||
|
*/
|
||||||
|
public function get(string $key): Promise
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$file = $this->cacheDir . urlencode($key);
|
||||||
|
if (!file_exists($file)) {
|
||||||
|
return Promise\resolve(null);
|
||||||
|
}
|
||||||
|
return Promise\resolve(unserialize(file_get_contents($file)));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return Promise\resolve(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a value in the cache
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
* @param mixed $value
|
||||||
|
* @return Promise
|
||||||
|
*/
|
||||||
|
public function set(string $key, $value): Promise
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$file = $this->cacheDir . urlencode($key);
|
||||||
|
if (!file_exists($this->cacheDir)) {
|
||||||
|
mkdir($this->cacheDir);
|
||||||
|
}
|
||||||
|
file_put_contents($file, serialize($value));
|
||||||
|
} finally {
|
||||||
|
return Promise\resolve(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace LanguageServer\Client;
|
||||||
|
|
||||||
|
use LanguageServer\ClientHandler;
|
||||||
|
use LanguageServer\Protocol\Message;
|
||||||
|
use Sabre\Event\Promise;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides method handlers for all xcache/* methods
|
||||||
|
*/
|
||||||
|
class XCache
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var ClientHandler
|
||||||
|
*/
|
||||||
|
private $handler;
|
||||||
|
|
||||||
|
public function __construct(ClientHandler $handler)
|
||||||
|
{
|
||||||
|
$this->handler = $handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
* @return Promise <mixed>
|
||||||
|
*/
|
||||||
|
public function get(string $key): Promise
|
||||||
|
{
|
||||||
|
return $this->handler->request('xcache/get', ['key' => $key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
* @param mixed $value
|
||||||
|
* @return Promise <mixed>
|
||||||
|
*/
|
||||||
|
public function set(string $key, $value): Promise
|
||||||
|
{
|
||||||
|
return $this->handler->notify('xcache/set', ['key' => $key, 'value' => $value]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -34,6 +34,17 @@ class DependenciesIndex extends AbstractAggregateIndex
|
||||||
return $this->indexes[$packageName];
|
return $this->indexes[$packageName];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $packageName
|
||||||
|
* @param Index $index
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setDependencyIndex(string $packageName, Index $index)
|
||||||
|
{
|
||||||
|
$this->indexes[$packageName] = $index;
|
||||||
|
$this->registerIndex($index);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $packageName
|
* @param string $packageName
|
||||||
* @return void
|
* @return void
|
||||||
|
|
|
@ -10,7 +10,7 @@ use Sabre\Event\EmitterTrait;
|
||||||
* Represents the index of a project or dependency
|
* Represents the index of a project or dependency
|
||||||
* Serializable for caching
|
* Serializable for caching
|
||||||
*/
|
*/
|
||||||
class Index implements ReadableIndex
|
class Index implements ReadableIndex, \Serializable
|
||||||
{
|
{
|
||||||
use EmitterTrait;
|
use EmitterTrait;
|
||||||
|
|
||||||
|
@ -185,4 +185,30 @@ class Index implements ReadableIndex
|
||||||
}
|
}
|
||||||
array_splice($this->references[$fqn], $index, 1);
|
array_splice($this->references[$fqn], $index, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $serialized
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function unserialize($serialized)
|
||||||
|
{
|
||||||
|
$data = unserialize($serialized);
|
||||||
|
foreach ($data as $prop => $val) {
|
||||||
|
$this->$prop = $val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $serialized
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function serialize()
|
||||||
|
{
|
||||||
|
return serialize([
|
||||||
|
'definitions' => $this->definitions,
|
||||||
|
'references' => $this->references,
|
||||||
|
'complete' => $this->complete,
|
||||||
|
'staticComplete' => $this->staticComplete
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,222 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace LanguageServer;
|
||||||
|
|
||||||
|
use LanguageServer\Cache\Cache;
|
||||||
|
use LanguageServer\FilesFinder\FilesFinder;
|
||||||
|
use LanguageServer\Index\{DependenciesIndex, Index};
|
||||||
|
use LanguageServer\Protocol\MessageType;
|
||||||
|
use Webmozart\PathUtil\Path;
|
||||||
|
use Composer\Semver\VersionParser;
|
||||||
|
use Sabre\Event\Promise;
|
||||||
|
use function Sabre\Event\coroutine;
|
||||||
|
|
||||||
|
class Indexer
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var The prefix for every cache item
|
||||||
|
*/
|
||||||
|
const CACHE_VERSION = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var FilesFinder
|
||||||
|
*/
|
||||||
|
private $filesFinder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $rootPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var LanguageClient
|
||||||
|
*/
|
||||||
|
private $client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Cache
|
||||||
|
*/
|
||||||
|
private $cache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var DependenciesIndex
|
||||||
|
*/
|
||||||
|
private $dependenciesIndex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Index
|
||||||
|
*/
|
||||||
|
private $sourceIndex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var PhpDocumentLoader
|
||||||
|
*/
|
||||||
|
private $documentLoader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \stdClasss
|
||||||
|
*/
|
||||||
|
private $composerLock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param FilesFinder $filesFinder
|
||||||
|
* @param string $rootPath
|
||||||
|
* @param LanguageClient $client
|
||||||
|
* @param Cache $cache
|
||||||
|
* @param DependenciesIndex $dependenciesIndex
|
||||||
|
* @param Index $sourceIndex
|
||||||
|
* @param PhpDocumentLoader $documentLoader
|
||||||
|
* @param \stdClass|null $composerLock
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
FilesFinder $filesFinder,
|
||||||
|
string $rootPath,
|
||||||
|
LanguageClient $client,
|
||||||
|
Cache $cache,
|
||||||
|
DependenciesIndex $dependenciesIndex,
|
||||||
|
Index $sourceIndex,
|
||||||
|
PhpDocumentLoader $documentLoader,
|
||||||
|
\stdClass $composerLock = null
|
||||||
|
) {
|
||||||
|
$this->filesFinder = $filesFinder;
|
||||||
|
$this->rootPath = $rootPath;
|
||||||
|
$this->client = $client;
|
||||||
|
$this->cache = $cache;
|
||||||
|
$this->dependenciesIndex = $dependenciesIndex;
|
||||||
|
$this->sourceIndex = $sourceIndex;
|
||||||
|
$this->documentLoader = $documentLoader;
|
||||||
|
$this->composerLock = $composerLock;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will read and parse the passed source files in the project and add them to the appropiate indexes
|
||||||
|
*
|
||||||
|
* @return Promise <void>
|
||||||
|
*/
|
||||||
|
public function index(): Promise
|
||||||
|
{
|
||||||
|
return coroutine(function () {
|
||||||
|
|
||||||
|
$pattern = Path::makeAbsolute('**/*.php', $this->rootPath);
|
||||||
|
$uris = yield $this->filesFinder->find($pattern);
|
||||||
|
|
||||||
|
$count = count($uris);
|
||||||
|
$startTime = microtime(true);
|
||||||
|
$this->client->window->logMessage(MessageType::INFO, "$count files total");
|
||||||
|
|
||||||
|
/** @var string[] */
|
||||||
|
$source = [];
|
||||||
|
/** @var string[][] */
|
||||||
|
$deps = [];
|
||||||
|
foreach ($uris as $uri) {
|
||||||
|
if ($this->composerLock !== null && preg_match('/\/vendor\/([^\/]+\/[^\/]+)\//', $uri, $matches)) {
|
||||||
|
// Dependency file
|
||||||
|
$packageName = $matches[1];
|
||||||
|
if (!isset($deps[$packageName])) {
|
||||||
|
$deps[$packageName] = [];
|
||||||
|
}
|
||||||
|
$deps[$packageName][] = $uri;
|
||||||
|
} else {
|
||||||
|
// Source file
|
||||||
|
$source[] = $uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index source
|
||||||
|
// Definitions and static references
|
||||||
|
$this->client->window->logMessage(MessageType::INFO, 'Indexing project for definitions and static references');
|
||||||
|
yield $this->indexFiles($source);
|
||||||
|
$this->sourceIndex->setStaticComplete();
|
||||||
|
// Dynamic references
|
||||||
|
$this->client->window->logMessage(MessageType::INFO, 'Indexing project for dynamic references');
|
||||||
|
yield $this->indexFiles($source);
|
||||||
|
$this->sourceIndex->setComplete();
|
||||||
|
|
||||||
|
// Index dependencies
|
||||||
|
$this->client->window->logMessage(MessageType::INFO, count($deps) . ' Packages');
|
||||||
|
foreach ($deps as $packageName => $files) {
|
||||||
|
// Find version of package and check cache
|
||||||
|
$packageKey = null;
|
||||||
|
$cacheKey = null;
|
||||||
|
$index = null;
|
||||||
|
foreach (array_merge($this->composerLock->packages, $this->composerLock->{'packages-dev'}) as $package) {
|
||||||
|
// Check if package name matches and version is absolute
|
||||||
|
// Dynamic constraints are not cached, because they can change every time
|
||||||
|
$packageVersion = ltrim($package->version, 'v');
|
||||||
|
if ($package->name === $packageName && strpos($packageVersion, 'dev') === false) {
|
||||||
|
$packageKey = $packageName . ':' . $packageVersion;
|
||||||
|
$cacheKey = self::CACHE_VERSION . ':' . $packageKey;
|
||||||
|
// Check cache
|
||||||
|
$index = yield $this->cache->get($cacheKey);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($index !== null) {
|
||||||
|
// Cache hit
|
||||||
|
$this->dependenciesIndex->setDependencyIndex($packageName, $index);
|
||||||
|
$this->client->window->logMessage(MessageType::INFO, "Restored $packageKey from cache");
|
||||||
|
} else {
|
||||||
|
// Cache miss
|
||||||
|
$index = $this->dependenciesIndex->getDependencyIndex($packageName);
|
||||||
|
|
||||||
|
// Index definitions and static references
|
||||||
|
$this->client->window->logMessage(MessageType::INFO, 'Indexing ' . ($packageKey ?? $packageName) . ' for definitions and static references');
|
||||||
|
yield $this->indexFiles($files);
|
||||||
|
$index->setStaticComplete();
|
||||||
|
|
||||||
|
// Index dynamic references
|
||||||
|
$this->client->window->logMessage(MessageType::INFO, 'Indexing ' . ($packageKey ?? $packageName) . ' for dynamic references');
|
||||||
|
yield $this->indexFiles($files);
|
||||||
|
$index->setComplete();
|
||||||
|
|
||||||
|
// If we know the version (cache key), save index for the dependency in the cache
|
||||||
|
if ($cacheKey !== null) {
|
||||||
|
$this->client->window->logMessage(MessageType::INFO, "Storing $packageKey in cache");
|
||||||
|
$this->cache->set($cacheKey, $index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$duration = (int)(microtime(true) - $startTime);
|
||||||
|
$mem = (int)(memory_get_usage(true) / (1024 * 1024));
|
||||||
|
$this->client->window->logMessage(
|
||||||
|
MessageType::INFO,
|
||||||
|
"All $count PHP files parsed in $duration seconds. $mem MiB allocated."
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $files
|
||||||
|
* @return Promise
|
||||||
|
*/
|
||||||
|
private function indexFiles(array $files): Promise
|
||||||
|
{
|
||||||
|
return coroutine(function () use ($files) {
|
||||||
|
foreach ($files as $i => $uri) {
|
||||||
|
// Skip open documents
|
||||||
|
if ($this->documentLoader->isOpen($uri)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give LS to the chance to handle requests while indexing
|
||||||
|
yield timeout();
|
||||||
|
$this->client->window->logMessage(MessageType::LOG, "Parsing $uri");
|
||||||
|
try {
|
||||||
|
$document = yield $this->documentLoader->load($uri);
|
||||||
|
if (!$document->isVendored()) {
|
||||||
|
$this->client->textDocument->publishDiagnostics($uri, $document->getDiagnostics());
|
||||||
|
}
|
||||||
|
} catch (ContentTooLargeException $e) {
|
||||||
|
$this->client->window->logMessage(
|
||||||
|
MessageType::INFO,
|
||||||
|
"Ignoring file {$uri} because it exceeds size limit of {$e->limit} bytes ({$e->size})"
|
||||||
|
);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->client->window->logMessage(MessageType::ERROR, "Error parsing $uri: " . (string)$e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,6 +28,13 @@ class LanguageClient
|
||||||
*/
|
*/
|
||||||
public $workspace;
|
public $workspace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles xcache/* methods
|
||||||
|
*
|
||||||
|
* @var Client\XCache
|
||||||
|
*/
|
||||||
|
public $xcache;
|
||||||
|
|
||||||
public function __construct(ProtocolReader $reader, ProtocolWriter $writer)
|
public function __construct(ProtocolReader $reader, ProtocolWriter $writer)
|
||||||
{
|
{
|
||||||
$handler = new ClientHandler($reader, $writer);
|
$handler = new ClientHandler($reader, $writer);
|
||||||
|
@ -36,5 +43,6 @@ class LanguageClient
|
||||||
$this->textDocument = new Client\TextDocument($handler, $mapper);
|
$this->textDocument = new Client\TextDocument($handler, $mapper);
|
||||||
$this->window = new Client\Window($handler);
|
$this->window = new Client\Window($handler);
|
||||||
$this->workspace = new Client\Workspace($handler, $mapper);
|
$this->workspace = new Client\Workspace($handler, $mapper);
|
||||||
|
$this->xcache = new Client\XCache($handler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,13 +17,13 @@ use LanguageServer\Protocol\{
|
||||||
use LanguageServer\FilesFinder\{FilesFinder, ClientFilesFinder, FileSystemFilesFinder};
|
use LanguageServer\FilesFinder\{FilesFinder, ClientFilesFinder, FileSystemFilesFinder};
|
||||||
use LanguageServer\ContentRetriever\{ContentRetriever, ClientContentRetriever, FileSystemContentRetriever};
|
use LanguageServer\ContentRetriever\{ContentRetriever, ClientContentRetriever, FileSystemContentRetriever};
|
||||||
use LanguageServer\Index\{DependenciesIndex, GlobalIndex, Index, ProjectIndex, StubsIndex};
|
use LanguageServer\Index\{DependenciesIndex, GlobalIndex, Index, ProjectIndex, StubsIndex};
|
||||||
|
use LanguageServer\Cache\{FileSystemCache, ClientCache};
|
||||||
use AdvancedJsonRpc;
|
use AdvancedJsonRpc;
|
||||||
use Sabre\Event\{Loop, Promise};
|
use Sabre\Event\{Loop, Promise};
|
||||||
use function Sabre\Event\coroutine;
|
use function Sabre\Event\coroutine;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
use Webmozart\PathUtil\Path;
|
use Webmozart\PathUtil\Path;
|
||||||
use Webmozart\Glob\Glob;
|
|
||||||
use Sabre\Uri;
|
use Sabre\Uri;
|
||||||
|
|
||||||
class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
||||||
|
@ -202,23 +202,39 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
||||||
|
|
||||||
if ($rootPath !== null) {
|
if ($rootPath !== null) {
|
||||||
yield $this->beforeIndex($rootPath);
|
yield $this->beforeIndex($rootPath);
|
||||||
$this->index($rootPath)->otherwise('\\LanguageServer\\crash');
|
|
||||||
|
// Find composer.json
|
||||||
|
if ($this->composerJson === null) {
|
||||||
|
$composerJsonFiles = yield $this->filesFinder->find(Path::makeAbsolute('**/composer.json', $rootPath));
|
||||||
|
if (!empty($composerJsonFiles)) {
|
||||||
|
$this->composerJson = json_decode(yield $this->contentRetriever->retrieve($composerJsonFiles[0]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find composer.lock
|
||||||
|
if ($this->composerLock === null) {
|
||||||
|
$composerLockFiles = yield $this->filesFinder->find(Path::makeAbsolute('**/composer.lock', $rootPath));
|
||||||
|
if (!empty($composerLockFiles)) {
|
||||||
|
$this->composerLock = json_decode(yield $this->contentRetriever->retrieve($composerLockFiles[0]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$cache = $capabilities->xcacheProvider ? new ClientCache($this->client) : new FileSystemCache;
|
||||||
|
|
||||||
|
// Index in background
|
||||||
|
$indexer = new Indexer(
|
||||||
|
$this->filesFinder,
|
||||||
|
$rootPath,
|
||||||
|
$this->client,
|
||||||
|
$cache,
|
||||||
|
$dependenciesIndex,
|
||||||
|
$sourceIndex,
|
||||||
|
$this->documentLoader,
|
||||||
|
$this->composerLock
|
||||||
|
);
|
||||||
|
$indexer->index()->otherwise('\\LanguageServer\\crash');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find composer.json
|
|
||||||
if ($this->composerJson === null) {
|
|
||||||
$composerJsonFiles = yield $this->filesFinder->find(Path::makeAbsolute('**/composer.json', $rootPath));
|
|
||||||
if (!empty($composerJsonFiles)) {
|
|
||||||
$this->composerJson = json_decode(yield $this->contentRetriever->retrieve($composerJsonFiles[0]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Find composer.lock
|
|
||||||
if ($this->composerLock === null) {
|
|
||||||
$composerLockFiles = yield $this->filesFinder->find(Path::makeAbsolute('**/composer.lock', $rootPath));
|
|
||||||
if (!empty($composerLockFiles)) {
|
|
||||||
$this->composerLock = json_decode(yield $this->contentRetriever->retrieve($composerLockFiles[0]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->textDocument === null) {
|
if ($this->textDocument === null) {
|
||||||
$this->textDocument = new Server\TextDocument(
|
$this->textDocument = new Server\TextDocument(
|
||||||
|
@ -298,66 +314,4 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
||||||
protected function beforeIndex(string $rootPath)
|
protected function beforeIndex(string $rootPath)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Will read and parse the passed source files in the project and add them to the appropiate indexes
|
|
||||||
*
|
|
||||||
* @param string $rootPath
|
|
||||||
* @return Promise <void>
|
|
||||||
*/
|
|
||||||
protected function index(string $rootPath): Promise
|
|
||||||
{
|
|
||||||
return coroutine(function () use ($rootPath) {
|
|
||||||
|
|
||||||
$pattern = Path::makeAbsolute('**/*.php', $rootPath);
|
|
||||||
$uris = yield $this->filesFinder->find($pattern);
|
|
||||||
|
|
||||||
$count = count($uris);
|
|
||||||
|
|
||||||
$startTime = microtime(true);
|
|
||||||
|
|
||||||
foreach (['Collecting definitions and static references', 'Collecting dynamic references'] as $run => $text) {
|
|
||||||
$this->client->window->logMessage(MessageType::INFO, $text);
|
|
||||||
foreach ($uris as $i => $uri) {
|
|
||||||
if ($this->documentLoader->isOpen($uri)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Give LS to the chance to handle requests while indexing
|
|
||||||
yield timeout();
|
|
||||||
$this->client->window->logMessage(
|
|
||||||
MessageType::LOG,
|
|
||||||
"Parsing file $i/$count: {$uri}"
|
|
||||||
);
|
|
||||||
try {
|
|
||||||
$document = yield $this->documentLoader->load($uri);
|
|
||||||
if (!$document->isVendored()) {
|
|
||||||
$this->client->textDocument->publishDiagnostics($uri, $document->getDiagnostics());
|
|
||||||
}
|
|
||||||
} catch (ContentTooLargeException $e) {
|
|
||||||
$this->client->window->logMessage(
|
|
||||||
MessageType::INFO,
|
|
||||||
"Ignoring file {$uri} because it exceeds size limit of {$e->limit} bytes ({$e->size})"
|
|
||||||
);
|
|
||||||
} catch (Exception $e) {
|
|
||||||
$this->client->window->logMessage(
|
|
||||||
MessageType::ERROR,
|
|
||||||
"Error parsing file {$uri}: " . (string)$e
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($run === 0) {
|
|
||||||
$this->projectIndex->setStaticComplete();
|
|
||||||
} else {
|
|
||||||
$this->projectIndex->setComplete();
|
|
||||||
}
|
|
||||||
$duration = (int)(microtime(true) - $startTime);
|
|
||||||
$mem = (int)(memory_get_usage(true) / (1024 * 1024));
|
|
||||||
$this->client->window->logMessage(
|
|
||||||
MessageType::INFO,
|
|
||||||
"All $count PHP files parsed in $duration seconds. $mem MiB allocated."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,4 +17,11 @@ class ClientCapabilities
|
||||||
* @var bool|null
|
* @var bool|null
|
||||||
*/
|
*/
|
||||||
public $xcontentProvider;
|
public $xcontentProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The client supports xcache/* requests
|
||||||
|
*
|
||||||
|
* @var bool|null
|
||||||
|
*/
|
||||||
|
public $xcacheProvider;
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,11 +104,7 @@ class LanguageServerTest extends TestCase
|
||||||
$promise->reject(new Exception($msg->body->params->message));
|
$promise->reject(new Exception($msg->body->params->message));
|
||||||
}
|
}
|
||||||
} else if (strpos($msg->body->params->message, 'All 25 PHP files parsed') !== false) {
|
} else if (strpos($msg->body->params->message, 'All 25 PHP files parsed') !== false) {
|
||||||
if ($run === 1) {
|
$promise->fulfill();
|
||||||
$run++;
|
|
||||||
} else {
|
|
||||||
$promise->fulfill();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue