Merge 2ccb4d8875
into 9dc1656592
commit
691f886c37
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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.
|
||||||
*
|
*
|
||||||
|
|
|
@ -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']);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue