Cache index on disk (#82)
parent
8e36e59e9a
commit
1e7260a2ea
|
@ -2,4 +2,5 @@
|
||||||
.vscode
|
.vscode
|
||||||
.idea
|
.idea
|
||||||
vendor/
|
vendor/
|
||||||
|
.phpls/
|
||||||
composer.lock
|
composer.lock
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
*
|
*
|
||||||
|
|
|
@ -21,7 +21,7 @@ class SymbolInformation
|
||||||
/**
|
/**
|
||||||
* The kind of this symbol.
|
* The kind of this symbol.
|
||||||
*
|
*
|
||||||
* @var number
|
* @var int
|
||||||
*/
|
*/
|
||||||
public $kind;
|
public $kind;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue