1
0
Fork 0

Cache index on disk (#82)

pull/104/head
Felix Becker 2016-10-20 03:48:30 +02:00 committed by GitHub
parent 8e36e59e9a
commit 1e7260a2ea
4 changed files with 114 additions and 10 deletions

1
.gitignore vendored
View File

@ -2,4 +2,5 @@
.vscode .vscode
.idea .idea
vendor/ vendor/
.phpls/
composer.lock composer.lock

View File

@ -10,10 +10,12 @@ use LanguageServer\Protocol\{
TextDocumentSyncKind, TextDocumentSyncKind,
Message, Message,
MessageType, MessageType,
InitializeResult InitializeResult,
SymbolInformation
}; };
use AdvancedJsonRpc; use AdvancedJsonRpc;
use Sabre\Event\Loop; use Sabre\Event\Loop;
use JsonMapper;
use Exception; use Exception;
use Throwable; use Throwable;
@ -42,6 +44,12 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
private $protocolWriter; private $protocolWriter;
private $client; private $client;
/**
* The root project path that was passed to initialize()
*
* @var string
*/
private $rootPath;
private $project; private $project;
public function __construct(ProtocolReader $reader, ProtocolWriter $writer) 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 public function initialize(int $processId, ClientCapabilities $capabilities, string $rootPath = null): InitializeResult
{ {
$this->rootPath = $rootPath;
// start building project index // start building project index
if ($rootPath) { if ($rootPath !== null) {
$this->indexProject($rootPath); $this->restoreCache();
$this->indexProject();
} }
$serverCapabilities = new ServerCapabilities(); $serverCapabilities = new ServerCapabilities();
@ -124,7 +135,9 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
*/ */
public function shutdown() 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. * Parses workspace files, one at a time.
* *
* @param string $rootPath The rootPath of the workspace.
* @return void * @return void
*/ */
private function indexProject(string $rootPath) private function indexProject()
{ {
$fileList = findFilesRecursive($rootPath, '/^.+\.php$/i'); $fileList = findFilesRecursive($this->rootPath, '/^.+\.php$/i');
$numTotalFiles = count($fileList); $numTotalFiles = count($fileList);
$startTime = microtime(true); $startTime = microtime(true);
$fileNum = 0; $fileNum = 0;
$processFile = function() use (&$fileList, &$fileNum, &$processFile, $rootPath, $numTotalFiles, $startTime) { $processFile = function() use (&$fileList, &$fileNum, &$processFile, $numTotalFiles, $startTime) {
if ($fileNum < $numTotalFiles) { if ($fileNum < $numTotalFiles) {
$file = $fileList[$fileNum]; $file = $fileList[$fileNum];
$uri = pathToUri($file); $uri = pathToUri($file);
$fileNum++; $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) { if (filesize($file) > 500000) {
$this->client->window->logMessage(MessageType::INFO, "Not parsing $shortName because it exceeds size limit of 0.5MB"); $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); Loop\setTimeout($processFile, 0);
} else { } else {
$duration = (int)(microtime(true) - $startTime); $duration = (int)(microtime(true) - $startTime);
$mem = (int)(memory_get_usage(true) / (1024 * 1024)); $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->client->window->logMessage(MessageType::INFO, "All PHP files parsed in $duration seconds. $mem MiB allocated.");
$this->saveCache();
} }
}; };
Loop\setTimeout($processFile, 0); 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));
}
} }

View File

@ -160,6 +160,17 @@ class Project
$this->symbols[$fqn] = $symbol; $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 * Unsets the SymbolInformation for a specific symbol
* and removes all references pointing to that symbol * and removes all references pointing to that symbol
@ -221,6 +232,28 @@ class Project
return array_map([$this, 'getDocument'], $this->references[$fqn]); 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 * Returns the document where a symbol is defined
* *

View File

@ -21,7 +21,7 @@ class SymbolInformation
/** /**
* The kind of this symbol. * The kind of this symbol.
* *
* @var number * @var int
*/ */
public $kind; public $kind;