From 6a0d70a07c2684021bc09e2b9f9d44f8f4f3ca68 Mon Sep 17 00:00:00 2001 From: Tom Sherman Date: Fri, 30 Aug 2019 12:47:55 +0100 Subject: [PATCH] Initial config work --- src/Configuration.php | 17 +++++++ src/FilesFinder/ClientFilesFinder.php | 6 +-- src/FilesFinder/FileSystemFilesFinder.php | 9 ++-- src/FilesFinder/FilesFinder.php | 19 +++++++- src/Indexer.php | 8 ++++ src/LanguageServer.php | 46 +++++++++---------- src/Server/Workspace.php | 29 +++++++++++- .../Workspace/DidChangeConfigurationTest.php | 41 +++++++++++++++++ 8 files changed, 144 insertions(+), 31 deletions(-) create mode 100644 src/Configuration.php create mode 100644 tests/Server/Workspace/DidChangeConfigurationTest.php diff --git a/src/Configuration.php b/src/Configuration.php new file mode 100644 index 0000000..1ae32d4 --- /dev/null +++ b/src/Configuration.php @@ -0,0 +1,17 @@ +excludePatterns = $excludePatterns; + } +} diff --git a/src/FilesFinder/ClientFilesFinder.php b/src/FilesFinder/ClientFilesFinder.php index 4315ede..9911458 100644 --- a/src/FilesFinder/ClientFilesFinder.php +++ b/src/FilesFinder/ClientFilesFinder.php @@ -33,13 +33,13 @@ class ClientFilesFinder implements FilesFinder * @param string $glob * @return Promise The URIs */ - public function find(string $glob): Promise + public function find(string $glob, array $excludePatterns = []): Promise { - return $this->client->workspace->xfiles()->then(function (array $textDocuments) use ($glob) { + return $this->client->workspace->xfiles()->then(function (array $textDocuments) use ($glob, $excludePatterns) { $uris = []; foreach ($textDocuments as $textDocument) { $path = Uri\parse($textDocument->uri)['path']; - if (Glob::match($path, $glob)) { + if (matchGlobs($path, [ $glob ]) && !matchGlobs($path, $excludePatterns)) { $uris[] = $textDocument->uri; } } diff --git a/src/FilesFinder/FileSystemFilesFinder.php b/src/FilesFinder/FileSystemFilesFinder.php index a26b5d8..b62d1d1 100644 --- a/src/FilesFinder/FileSystemFilesFinder.php +++ b/src/FilesFinder/FileSystemFilesFinder.php @@ -4,6 +4,7 @@ declare(strict_types = 1); namespace LanguageServer\FilesFinder; use Webmozart\Glob\Iterator\GlobIterator; +use Webmozart\Glob\Glob; use Sabre\Event\Promise; use function Sabre\Event\coroutine; use function LanguageServer\{pathToUri, timeout}; @@ -15,15 +16,17 @@ class FileSystemFilesFinder implements FilesFinder * If the client does not support workspace/xfiles, it falls back to searching the file system directly. * * @param string $glob + * @param string[] $excludePatterns An array of globs * @return Promise */ - public function find(string $glob): Promise + public function find(string $glob, array $excludePatterns = []): Promise { - return coroutine(function () use ($glob) { + return coroutine(function () use ($glob, $excludePatterns) { $uris = []; foreach (new GlobIterator($glob) as $path) { // Exclude any directories that also match the glob pattern - if (!is_dir($path)) { + // Also exclude any path that matches one of the exclude patterns + if (!is_dir($path) && !matchGlobs($path, $excludePatterns)) { $uris[] = pathToUri($path); } diff --git a/src/FilesFinder/FilesFinder.php b/src/FilesFinder/FilesFinder.php index 81d6de5..077429a 100644 --- a/src/FilesFinder/FilesFinder.php +++ b/src/FilesFinder/FilesFinder.php @@ -17,5 +17,22 @@ interface FilesFinder * @param string $glob * @return Promise */ - public function find(string $glob): Promise; + public function find(string $glob, array $excludePatterns): Promise; +} + +/** + * Check if a path matches any of the globs + * @param string $path + * @param string[] $globs An array of globs + * @return bool + */ +function matchGlobs(string $path, array $globs): bool +{ + foreach ($globs as $glob) { + if (Glob::match($path, $glob)) { + return true; + } + } + + return false; } diff --git a/src/Indexer.php b/src/Indexer.php index 7ebff3f..8dfc184 100644 --- a/src/Indexer.php +++ b/src/Indexer.php @@ -6,6 +6,7 @@ namespace LanguageServer; use LanguageServer\Cache\Cache; use LanguageServer\FilesFinder\FilesFinder; use LanguageServer\Index\{DependenciesIndex, Index}; +use LanguageServer\Configuration; use LanguageServerProtocol\MessageType; use Webmozart\PathUtil\Path; use Sabre\Event\Promise; @@ -63,6 +64,11 @@ class Indexer */ private $composerJson; + /** + * @var Configuration + */ + private $configuration; + /** * @param FilesFinder $filesFinder * @param string $rootPath @@ -81,6 +87,7 @@ class Indexer DependenciesIndex $dependenciesIndex, Index $sourceIndex, PhpDocumentLoader $documentLoader, + Configuration $configuration, \stdClass $composerLock = null, \stdClass $composerJson = null ) { @@ -93,6 +100,7 @@ class Indexer $this->documentLoader = $documentLoader; $this->composerLock = $composerLock; $this->composerJson = $composerJson; + $this->configuration = $configuration; } /** diff --git a/src/LanguageServer.php b/src/LanguageServer.php index 38dfeb1..d8bfef6 100644 --- a/src/LanguageServer.php +++ b/src/LanguageServer.php @@ -199,6 +199,28 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher $this->definitionResolver ); + 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->composerLock, + $this->documentLoader, + $this->composerJson + ); + } + if ($rootPath !== null) { yield $this->beforeIndex($rootPath); @@ -233,35 +255,13 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher $dependenciesIndex, $sourceIndex, $this->documentLoader, + $this->workspace->configuration, $this->composerLock, $this->composerJson ); $indexer->index()->otherwise('\\LanguageServer\\crash'); } - - 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->composerLock, - $this->documentLoader, - $this->composerJson - ); - } - $serverCapabilities = new ServerCapabilities(); // Ask the client to return always full documents (because we need to rebuild the AST from scratch) $serverCapabilities->textDocumentSync = TextDocumentSyncKind::FULL; diff --git a/src/Server/Workspace.php b/src/Server/Workspace.php index fd93a9e..137352f 100644 --- a/src/Server/Workspace.php +++ b/src/Server/Workspace.php @@ -15,9 +15,11 @@ use LanguageServerProtocol\{ DependencyReference, Location }; +use LanguageServerProtocol\MessageType; use Sabre\Event\Promise; use function Sabre\Event\coroutine; use function LanguageServer\waitForEvent; +use LanguageServer\Configuration; /** * Provides method handlers for all workspace/* methods @@ -56,6 +58,11 @@ class Workspace */ public $documentLoader; + /** + * @var Configuration + */ + public $configuration; + /** * @param LanguageClient $client LanguageClient instance used to signal updated results * @param ProjectIndex $projectIndex Index that is used to wait for full index completeness @@ -63,8 +70,9 @@ class Workspace * @param DependenciesIndex $sourceIndex Index that is used on a workspace/xreferences request * @param \stdClass $composerLock The parsed composer.lock of the project, if any * @param PhpDocumentLoader $documentLoader PhpDocumentLoader instance to load documents + * @param Configuration $configuration Configuration for the language server */ - public function __construct(LanguageClient $client, ProjectIndex $projectIndex, DependenciesIndex $dependenciesIndex, Index $sourceIndex, \stdClass $composerLock = null, PhpDocumentLoader $documentLoader, \stdClass $composerJson = null) + public function __construct(LanguageClient $client, ProjectIndex $projectIndex, DependenciesIndex $dependenciesIndex, Index $sourceIndex, \stdClass $composerLock = null, PhpDocumentLoader $documentLoader, \stdClass $composerJson = null, Configuration $configuration = null) { $this->client = $client; $this->sourceIndex = $sourceIndex; @@ -73,6 +81,7 @@ class Workspace $this->composerLock = $composerLock; $this->documentLoader = $documentLoader; $this->composerJson = $composerJson; + $this->configuration = $configuration ?? new Configuration(); } /** @@ -113,6 +122,24 @@ class Workspace } } + /** + * The changed configuration notification is sent from the client to the server when the configuration changes on the client. + * + * @param array $changesToConfig + * @return void + */ + public function didChangeConfiguration(array $changesToConfig) + { + foreach ($changesToConfig['settings'] as $key => $value) { + // Only update the key if it exists in the class definition + if (property_exists(Configuration::class, $key)) { + $this->configuration->{$key} = $value; + } else { + $this->client->window->logMessage(MessageType::WARNING, "Key of \"$key\" isn't a valid configuration option."); + } + } + } + /** * The workspace references request is sent from the client to the server to locate project-wide references to a symbol given its description / metadata. * diff --git a/tests/Server/Workspace/DidChangeConfigurationTest.php b/tests/Server/Workspace/DidChangeConfigurationTest.php new file mode 100644 index 0000000..a54bda9 --- /dev/null +++ b/tests/Server/Workspace/DidChangeConfigurationTest.php @@ -0,0 +1,41 @@ +assertInstanceOf(Configuration::class, $workspace->configuration); + + $changesToConfig = [ 'excludePatterns' => ['foo', 'bar'] ]; + + $writer->on('message', function (Message $message) use ($changesToConfig) { + if ($message->body->method === 'workspace/didChangeConfiguration') { + $this->assertEquals($message->body->params->settings, $changesToConfig); + } + }); + + $workspace->didChangeConfiguration([ 'settings' => $changesToConfig ]); + Loop\tick(true); + + $this->assertEquals($workspace->configuration->excludePatterns, $changesToConfig['excludePatterns']); + } +}