1
0
Fork 0
pull/752/merge
Tom Sherman 2019-08-30 13:32:26 +00:00 committed by GitHub
commit 691f886c37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 145 additions and 32 deletions

17
src/Configuration.php Normal file
View File

@ -0,0 +1,17 @@
<?php
declare(strict_types = 1);
namespace LanguageServer;
class Configuration
{
/**
* @var string[]
*/
public $excludePatterns;
public function __construct(array $excludePatterns = [])
{
$this->excludePatterns = $excludePatterns;
}
}

View File

@ -33,13 +33,13 @@ class ClientFilesFinder implements FilesFinder
* @param string $glob * @param string $glob
* @return Promise <string[]> The URIs * @return Promise <string[]> 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 = []; $uris = [];
foreach ($textDocuments as $textDocument) { foreach ($textDocuments as $textDocument) {
$path = Uri\parse($textDocument->uri)['path']; $path = Uri\parse($textDocument->uri)['path'];
if (Glob::match($path, $glob)) { if (matchGlobs($path, [ $glob ]) && !matchGlobs($path, $excludePatterns)) {
$uris[] = $textDocument->uri; $uris[] = $textDocument->uri;
} }
} }

View File

@ -15,15 +15,17 @@ class FileSystemFilesFinder implements FilesFinder
* If the client does not support workspace/xfiles, it falls back to searching the file system directly. * If the client does not support workspace/xfiles, it falls back to searching the file system directly.
* *
* @param string $glob * @param string $glob
* @param string[] $excludePatterns An array of globs
* @return Promise <string[]> * @return Promise <string[]>
*/ */
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 = []; $uris = [];
foreach (new GlobIterator($glob) as $path) { foreach (new GlobIterator($glob) as $path) {
// Exclude any directories that also match the glob pattern // 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); $uris[] = pathToUri($path);
} }

View File

@ -4,6 +4,7 @@ declare(strict_types = 1);
namespace LanguageServer\FilesFinder; namespace LanguageServer\FilesFinder;
use Sabre\Event\Promise; use Sabre\Event\Promise;
use Webmozart\Glob\Glob;
/** /**
* Interface for finding files in the workspace * Interface for finding files in the workspace
@ -17,5 +18,22 @@ interface FilesFinder
* @param string $glob * @param string $glob
* @return Promise <string[]> * @return Promise <string[]>
*/ */
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;
} }

View File

@ -6,6 +6,7 @@ namespace LanguageServer;
use LanguageServer\Cache\Cache; use LanguageServer\Cache\Cache;
use LanguageServer\FilesFinder\FilesFinder; use LanguageServer\FilesFinder\FilesFinder;
use LanguageServer\Index\{DependenciesIndex, Index}; use LanguageServer\Index\{DependenciesIndex, Index};
use LanguageServer\Configuration;
use LanguageServerProtocol\MessageType; use LanguageServerProtocol\MessageType;
use Webmozart\PathUtil\Path; use Webmozart\PathUtil\Path;
use Sabre\Event\Promise; use Sabre\Event\Promise;
@ -63,6 +64,11 @@ class Indexer
*/ */
private $composerJson; private $composerJson;
/**
* @var Configuration
*/
private $configuration;
/** /**
* @param FilesFinder $filesFinder * @param FilesFinder $filesFinder
* @param string $rootPath * @param string $rootPath
@ -81,6 +87,7 @@ class Indexer
DependenciesIndex $dependenciesIndex, DependenciesIndex $dependenciesIndex,
Index $sourceIndex, Index $sourceIndex,
PhpDocumentLoader $documentLoader, PhpDocumentLoader $documentLoader,
Configuration $configuration,
\stdClass $composerLock = null, \stdClass $composerLock = null,
\stdClass $composerJson = null \stdClass $composerJson = null
) { ) {
@ -93,6 +100,7 @@ class Indexer
$this->documentLoader = $documentLoader; $this->documentLoader = $documentLoader;
$this->composerLock = $composerLock; $this->composerLock = $composerLock;
$this->composerJson = $composerJson; $this->composerJson = $composerJson;
$this->configuration = $configuration;
} }
/** /**
@ -105,7 +113,7 @@ class Indexer
return coroutine(function () { return coroutine(function () {
$pattern = Path::makeAbsolute('**/*.php', $this->rootPath); $pattern = Path::makeAbsolute('**/*.php', $this->rootPath);
$uris = yield $this->filesFinder->find($pattern); $uris = yield $this->filesFinder->find($pattern, $this->configuration->excludePatterns);
$count = count($uris); $count = count($uris);
$startTime = microtime(true); $startTime = microtime(true);

View File

@ -199,6 +199,28 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
$this->definitionResolver $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) { if ($rootPath !== null) {
yield $this->beforeIndex($rootPath); yield $this->beforeIndex($rootPath);
@ -233,35 +255,13 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
$dependenciesIndex, $dependenciesIndex,
$sourceIndex, $sourceIndex,
$this->documentLoader, $this->documentLoader,
$this->workspace->configuration,
$this->composerLock, $this->composerLock,
$this->composerJson $this->composerJson
); );
$indexer->index()->otherwise('\\LanguageServer\\crash'); $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(); $serverCapabilities = new ServerCapabilities();
// Ask the client to return always full documents (because we need to rebuild the AST from scratch) // Ask the client to return always full documents (because we need to rebuild the AST from scratch)
$serverCapabilities->textDocumentSync = TextDocumentSyncKind::FULL; $serverCapabilities->textDocumentSync = TextDocumentSyncKind::FULL;

View File

@ -15,9 +15,11 @@ use LanguageServerProtocol\{
DependencyReference, DependencyReference,
Location Location
}; };
use LanguageServerProtocol\MessageType;
use Sabre\Event\Promise; use Sabre\Event\Promise;
use function Sabre\Event\coroutine; use function Sabre\Event\coroutine;
use function LanguageServer\waitForEvent; use function LanguageServer\waitForEvent;
use LanguageServer\Configuration;
/** /**
* Provides method handlers for all workspace/* methods * Provides method handlers for all workspace/* methods
@ -56,6 +58,11 @@ class Workspace
*/ */
public $documentLoader; public $documentLoader;
/**
* @var Configuration
*/
public $configuration;
/** /**
* @param LanguageClient $client LanguageClient instance used to signal updated results * @param LanguageClient $client LanguageClient instance used to signal updated results
* @param ProjectIndex $projectIndex Index that is used to wait for full index completeness * @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 DependenciesIndex $sourceIndex Index that is used on a workspace/xreferences request
* @param \stdClass $composerLock The parsed composer.lock of the project, if any * @param \stdClass $composerLock The parsed composer.lock of the project, if any
* @param PhpDocumentLoader $documentLoader PhpDocumentLoader instance to load documents * @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->client = $client;
$this->sourceIndex = $sourceIndex; $this->sourceIndex = $sourceIndex;
@ -73,6 +81,7 @@ class Workspace
$this->composerLock = $composerLock; $this->composerLock = $composerLock;
$this->documentLoader = $documentLoader; $this->documentLoader = $documentLoader;
$this->composerJson = $composerJson; $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. * The workspace references request is sent from the client to the server to locate project-wide references to a symbol given its description / metadata.
* *

View File

@ -0,0 +1,41 @@
<?php
declare(strict_types = 1);
namespace LanguageServer\Tests\Server\Workspace;
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
use LanguageServer\{DefinitionResolver, LanguageClient, PhpDocumentLoader, Server};
use LanguageServer\Index\{DependenciesIndex, Index, ProjectIndex};
use LanguageServer\Message;
use LanguageServer\Tests\MockProtocolStream;
use LanguageServer\Tests\Server\ServerTestCase;
use LanguageServer\Server\Workspace;
use Sabre\Event\Loop;
use LanguageServer\Configuration;
class DidChangeConfigurationTest extends ServerTestCase
{
public function testChangeConfiguration()
{
$client = new LanguageClient(new MockProtocolStream(), $writer = new MockProtocolStream());
$projectIndex = new ProjectIndex($sourceIndex = new Index(), $dependenciesIndex = new DependenciesIndex());
$definitionResolver = new DefinitionResolver($projectIndex);
$loader = new PhpDocumentLoader(new FileSystemContentRetriever(), $projectIndex, $definitionResolver);
$workspace = new Server\Workspace($client, $projectIndex, $dependenciesIndex, $sourceIndex, null, $loader, null, new Configuration([ 'foo' ]));
$this->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']);
}
}