From a1e56543c3e292c68d60d9a43d4b91ac5970da95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Steitz?= Date: Fri, 31 Aug 2018 20:49:23 +0200 Subject: [PATCH] WIP: Implement didChangeConfiguration with reindexing * Handle the case where didChangeConfiguration is called before workspace/configuration request is resolved. * Implement basic cancellation signal request * Use defaults options and only apply new on request --- src/Indexer.php | 20 +++- src/LanguageServer.php | 112 +++++++++++-------- src/Protocol/ClientCapabilities.php | 5 + src/Protocol/WorkspaceClientCapabilities.php | 11 ++ src/Server/Workspace.php | 71 +++++++----- 5 files changed, 142 insertions(+), 77 deletions(-) create mode 100644 src/Protocol/WorkspaceClientCapabilities.php diff --git a/src/Indexer.php b/src/Indexer.php index 253c7d7..93812b9 100644 --- a/src/Indexer.php +++ b/src/Indexer.php @@ -85,7 +85,6 @@ class Indexer * @param Cache $cache * @param DependenciesIndex $dependenciesIndex * @param Index $sourceIndex - * @param Options $options * @param PhpDocumentLoader $documentLoader * @param \stdClass|null $composerLock */ @@ -97,7 +96,6 @@ class Indexer DependenciesIndex $dependenciesIndex, Index $sourceIndex, PhpDocumentLoader $documentLoader, - Options $options, \stdClass $composerLock = null, \stdClass $composerJson = null ) { @@ -108,11 +106,24 @@ class Indexer $this->dependenciesIndex = $dependenciesIndex; $this->sourceIndex = $sourceIndex; $this->documentLoader = $documentLoader; - $this->options = $options; $this->composerLock = $composerLock; $this->composerJson = $composerJson; $this->hasCancellationSignal = false; $this->isIndexing = false; + $this->options = new Options(); + } + + /** + * @param Options $options + */ + public function setOptions(Options $options) + { + $this->options = $options; + } + + public function getOptions(): Options + { + return $this->options; } /** @@ -156,6 +167,7 @@ class Indexer $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); @@ -243,6 +255,7 @@ class Indexer } $this->hasCancellationSignal = false; + $this->client->window->logMessage(MessageType::INFO, 'Indexing project canceled'); }); } @@ -254,6 +267,7 @@ class Indexer { return coroutine(function () use ($files) { foreach ($files as $i => $uri) { + // abort current running indexing if ($this->hasCancellationSignal) { return; } diff --git a/src/LanguageServer.php b/src/LanguageServer.php index 29cfc4c..4a04d73 100644 --- a/src/LanguageServer.php +++ b/src/LanguageServer.php @@ -117,6 +117,16 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher */ protected $cache; + /** + * @var ClientCapabilities + */ + protected $clientCapabilities; + + /** + * @var Indexer + */ + protected $indexer; + /** * @param ProtocolReader $reader * @param ProtocolWriter $writer @@ -203,6 +213,7 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher $stubsIndex = StubsIndex::read(); $this->globalIndex = new GlobalIndex($stubsIndex, $this->projectIndex); $this->rootPath = $rootPath; + $this->clientCapabilities = $capabilities; // The DefinitionResolver should look in stubs, the project source and dependencies $this->definitionResolver = new DefinitionResolver($this->globalIndex); @@ -244,6 +255,43 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher } $this->cache = $capabilities->xcacheProvider ? new ClientCache($this->client) : new FileSystemCache; + + // Index in background + $this->indexer = new Indexer( + $this->filesFinder, + $this->rootPath, + $this->client, + $this->cache, + $dependenciesIndex, + $sourceIndex, + $this->documentLoader, + $this->composerLock, + $this->composerJson + ); + } + + if ($this->textDocument === null) { + $this->textDocument = new Server\TextDocument( + $this->documentLoader, + $this->definitionResolver, + $this->client, + $this->globalIndex, + $this->composerJson, + $this->composerLock + ); + } + + if ($this->workspace === null) { + $this->workspace = new Server\Workspace( + $this->client, + $this->projectIndex, + $dependenciesIndex, + $sourceIndex, + $this->indexer, + $this->composerLock, + $this->documentLoader, + $this->composerJson + ); } $serverCapabilities = new ServerCapabilities(); @@ -286,55 +334,31 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher public function initialized(): Promise { return coroutine(function () { - list($sourceIndex, $dependenciesIndex) = $this->projectIndex->getIndexes(); - $mapper = new \JsonMapper(); - $configurationitem = new ConfigurationItem(); - $configurationitem->section = 'php'; - $configuration = yield $this->client->workspace->configuration([$configurationitem]); - $options = $mapper->map($configuration[0], new Options()); - - if ($this->rootPath) { - // Index in background - $indexer = new Indexer( - $this->filesFinder, - $this->rootPath, - $this->client, - $this->cache, - $dependenciesIndex, - $sourceIndex, - $this->documentLoader, - $options, - $this->composerLock, - $this->composerJson - ); - - $indexer->index()->otherwise('\\LanguageServer\\crash'); + if (!$this->rootPath) { + return; } - if ($this->textDocument === null) { - $this->textDocument = new Server\TextDocument( - $this->documentLoader, - $this->definitionResolver, - $this->client, - $this->globalIndex, - $this->composerJson, - $this->composerLock - ); + // request configuration if it is supported + // support comes with protocol version 3.6.0 + if ($this->clientCapabilities->workspace->configuration) { + $configurationitem = new ConfigurationItem(); + $configurationitem->section = 'php'; + $configuration = yield $this->client->workspace->configuration([$configurationitem]); + $options = $this->mapper->map($configuration[0], new Options()); } - if ($this->workspace === null) { - $this->workspace = new Server\Workspace( - $this->client, - $this->projectIndex, - $dependenciesIndex, - $sourceIndex, - $options, - $indexer, - $this->composerLock, - $this->documentLoader, - $this->composerJson - ); + // depending on the implementation of the client + // the workspace/didChangeConfiguration can be invoked before + // the response from the workspace/configuration request is resolved + if ($this->indexer->isIndexing()) { + return; } + + if ($options) { + $this->indexer->setOptions($options); + } + + $this->indexer->index()->otherwise('\\LanguageServer\\crash'); }); } diff --git a/src/Protocol/ClientCapabilities.php b/src/Protocol/ClientCapabilities.php index 5228c7d..c1ca7ea 100644 --- a/src/Protocol/ClientCapabilities.php +++ b/src/Protocol/ClientCapabilities.php @@ -24,4 +24,9 @@ class ClientCapabilities * @var bool|null */ public $xcacheProvider; + + /** + * @var WorkspaceClientCapabilities + */ + public $workspace; } diff --git a/src/Protocol/WorkspaceClientCapabilities.php b/src/Protocol/WorkspaceClientCapabilities.php new file mode 100644 index 0000000..6b11685 --- /dev/null +++ b/src/Protocol/WorkspaceClientCapabilities.php @@ -0,0 +1,11 @@ +composerLock = $composerLock; $this->documentLoader = $documentLoader; $this->composerJson = $composerJson; - $this->options = $options; $this->indexer = $indexer; } @@ -205,36 +197,32 @@ class Workspace /** * A notification sent from the client to the server to signal the change of configuration settings. * - * @param \stdClass $settings Settings as JSON object structure with php as primary key + * @param mixed $settings Settings as JSON object structure with php as primary key * @return Promise */ - public function didChangeConfiguration(\stdClass $settings): Promise + public function didChangeConfiguration($settings): Promise { - xdebug_break(); return coroutine(function () use ($settings) { + if (!property_exists($settings, 'php') || $settings->php === new \stdClass()) { + return; + } + try { - xdebug_break(); $mapper = new \JsonMapper(); - $settings = $mapper->map($settings->php, new Options); + $options = $mapper->map($settings->php, new Options); - if ($this->options == $settings) { - return; + // handle options for indexer + $currentIndexerOptions = $this->indexer->getOptions(); + $this->indexer->setOptions($options); + + if ($this->hasIndexerOptionsChanged($currentIndexerOptions, $options)) { + if ($this->indexer->isIndexing()) { + yield $this->indexer->cancel(); + } + + $this->projectIndex->wipe(); + $this->indexer->index()->otherwise('\\LanguageServer\\crash'); } - - // @TODO: get changed settings and apply them - // @TODO: check settings that affect the indexer - - if ($this->indexer->isIndexing()) { - yield $this->indexer->cancel(); - } - - $this->projectIndex->wipe(); - $this->indexer->index(); - - $this->client->window->showMessage( - MessageType::INFO, - 'Reindexing with new settings.' - ); } catch (\JsonMapper_Exception $exception) { $this->client->window->showMessage( MessageType::ERROR, @@ -244,4 +232,27 @@ class Workspace } }); } + + /** + * Compare current options with new + * + * When the new options differ from the current, then we need start + * to reindex the project folder. + * + * @param Options $current + * @param Options $new + * @return bool + */ + private function hasIndexerOptionsChanged(Options $current, Options $new): bool + { + $properties = ['fileTypes']; + + foreach ($properties as $property) { + if ($current->{$property} !== $new->{$property}) { + return true; + } + } + + return false; + } }