From 1e7260a2eae071c14fe6f1e49125d9eeecd3b778 Mon Sep 17 00:00:00 2001 From: Felix Becker Date: Thu, 20 Oct 2016 03:48:30 +0200 Subject: [PATCH] Cache index on disk (#82) --- .gitignore | 1 + src/LanguageServer.php | 88 +++++++++++++++++++++++++++--- src/Project.php | 33 +++++++++++ src/Protocol/SymbolInformation.php | 2 +- 4 files changed, 114 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 2729a4c..c018fa6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ .vscode .idea vendor/ +.phpls/ composer.lock diff --git a/src/LanguageServer.php b/src/LanguageServer.php index ae4b1b9..15741d1 100644 --- a/src/LanguageServer.php +++ b/src/LanguageServer.php @@ -10,10 +10,12 @@ use LanguageServer\Protocol\{ TextDocumentSyncKind, Message, MessageType, - InitializeResult + InitializeResult, + SymbolInformation }; use AdvancedJsonRpc; use Sabre\Event\Loop; +use JsonMapper; use Exception; use Throwable; @@ -42,6 +44,12 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher private $protocolWriter; private $client; + /** + * The root project path that was passed to initialize() + * + * @var string + */ + private $rootPath; private $project; public function __construct(ProtocolReader $reader, ProtocolWriter $writer) @@ -91,9 +99,12 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher */ public function initialize(int $processId, ClientCapabilities $capabilities, string $rootPath = null): InitializeResult { + $this->rootPath = $rootPath; + // start building project index - if ($rootPath) { - $this->indexProject($rootPath); + if ($rootPath !== null) { + $this->restoreCache(); + $this->indexProject(); } $serverCapabilities = new ServerCapabilities(); @@ -124,7 +135,9 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher */ public function shutdown() { - + if ($this->rootPath !== null) { + $this->saveCache(); + } } /** @@ -140,23 +153,23 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher /** * Parses workspace files, one at a time. * - * @param string $rootPath The rootPath of the workspace. * @return void */ - private function indexProject(string $rootPath) + private function indexProject() { - $fileList = findFilesRecursive($rootPath, '/^.+\.php$/i'); + $fileList = findFilesRecursive($this->rootPath, '/^.+\.php$/i'); $numTotalFiles = count($fileList); $startTime = microtime(true); $fileNum = 0; - $processFile = function() use (&$fileList, &$fileNum, &$processFile, $rootPath, $numTotalFiles, $startTime) { + $processFile = function() use (&$fileList, &$fileNum, &$processFile, $numTotalFiles, $startTime) { if ($fileNum < $numTotalFiles) { $file = $fileList[$fileNum]; $uri = pathToUri($file); $fileNum++; - $shortName = substr($file, strlen($rootPath) + 1); + $shortName = substr($file, strlen($this->rootPath) + 1); + $this->client->window->logMessage(MessageType::INFO, "Parsing file $fileNum/$numTotalFiles: $shortName."); if (filesize($file) > 500000) { $this->client->window->logMessage(MessageType::INFO, "Not parsing $shortName because it exceeds size limit of 0.5MB"); @@ -169,14 +182,71 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher } } + if ($fileNum % 1000 === 0) { + $this->saveCache(); + } + Loop\setTimeout($processFile, 0); } else { $duration = (int)(microtime(true) - $startTime); $mem = (int)(memory_get_usage(true) / (1024 * 1024)); $this->client->window->logMessage(MessageType::INFO, "All PHP files parsed in $duration seconds. $mem MiB allocated."); + $this->saveCache(); } }; Loop\setTimeout($processFile, 0); } + + /** + * Restores the definition and reference index from the .phpls cache directory, if available + * + * @return void + */ + public function restoreCache() + { + $cacheDir = $this->rootPath . '/.phpls'; + if (is_dir($cacheDir)) { + if (file_exists($cacheDir . '/symbols.json')) { + $json = json_decode(file_get_contents($cacheDir . '/symbols.json')); + $mapper = new JsonMapper; + $symbols = $mapper->mapArray($json, [], SymbolInformation::class); + $count = count($symbols); + $this->project->setSymbols($symbols); + $this->client->window->logMessage(MessageType::INFO, "Restoring $count symbols"); + } + if (file_exists($cacheDir . '/references.json')) { + $references = json_decode(file_get_contents($cacheDir . '/references.json'), true); + $count = array_sum(array_map('count', $references)); + $this->project->setReferenceUris($references); + $this->client->window->logMessage(MessageType::INFO, "Restoring $count references"); + } + } else { + $this->client->window->logMessage(MessageType::INFO, 'No cache found'); + } + } + + /** + * Saves the definition and reference index to the .phpls cache directory + * + * @return void + */ + public function saveCache() + { + // Cache definitions, references + $cacheDir = $this->rootPath . '/.phpls'; + if (!is_dir($cacheDir)) { + mkdir($cacheDir); + } + + $symbols = $this->project->getSymbols(); + $count = count($symbols); + $this->client->window->logMessage(MessageType::INFO, "Saving $count symbols to cache"); + file_put_contents($cacheDir . "/symbols.json", json_encode($symbols, JSON_UNESCAPED_SLASHES)); + + $references = $this->project->getReferenceUris(); + $count = array_sum(array_map('count', $references)); + $this->client->window->logMessage(MessageType::INFO, "Saving $count references to cache"); + file_put_contents($cacheDir . "/references.json", json_encode($references, JSON_UNESCAPED_SLASHES)); + } } diff --git a/src/Project.php b/src/Project.php index 769a4d2..5e8a5f9 100644 --- a/src/Project.php +++ b/src/Project.php @@ -160,6 +160,17 @@ class Project $this->symbols[$fqn] = $symbol; } + /** + * Sets the SymbolInformation index + * + * @param SymbolInformation[] $symbols + * @return void + */ + public function setSymbols(array $symbols) + { + $this->symbols = $symbols; + } + /** * Unsets the SymbolInformation for a specific symbol * and removes all references pointing to that symbol @@ -221,6 +232,28 @@ class Project return array_map([$this, 'getDocument'], $this->references[$fqn]); } + /** + * 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 the document where a symbol is defined * diff --git a/src/Protocol/SymbolInformation.php b/src/Protocol/SymbolInformation.php index c2fb693..19ca6a6 100644 --- a/src/Protocol/SymbolInformation.php +++ b/src/Protocol/SymbolInformation.php @@ -21,7 +21,7 @@ class SymbolInformation /** * The kind of this symbol. * - * @var number + * @var int */ public $kind;