Start indexing after initialization
The indexer is moved to the method initialized, so we can request configurations from the client to init the indexer itself.pull/308/head
parent
a5417cdf72
commit
e317e8c743
|
@ -4,6 +4,7 @@ declare(strict_types = 1);
|
|||
namespace LanguageServer\Client;
|
||||
|
||||
use LanguageServer\ClientHandler;
|
||||
use LanguageServer\Protocol\ConfigurationItem;
|
||||
use LanguageServer\Protocol\TextDocumentIdentifier;
|
||||
use Sabre\Event\Promise;
|
||||
use JsonMapper;
|
||||
|
@ -44,4 +45,24 @@ class Workspace
|
|||
return $this->mapper->mapArray($textDocuments, [], TextDocumentIdentifier::class);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The workspace/configuration request is sent from the server to the
|
||||
* client to fetch configuration settings from the client.
|
||||
*
|
||||
* The request can fetch n configuration settings in one roundtrip.
|
||||
* The order of the returned configuration settings correspond to the order
|
||||
* of the passed ConfigurationItems (e.g. the first item in the response is
|
||||
* the result for the first configuration item in the params).
|
||||
*
|
||||
* @param ConfigurationItem[] $items
|
||||
* @return Promise
|
||||
*/
|
||||
public function configuration(array $items): Promise
|
||||
{
|
||||
return $this->handler->request(
|
||||
'workspace/configuration',
|
||||
['items' => $items]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ class ProjectIndex extends AbstractAggregateIndex
|
|||
/**
|
||||
* @return ReadableIndex[]
|
||||
*/
|
||||
protected function getIndexes(): array
|
||||
public function getIndexes(): array
|
||||
{
|
||||
return [$this->sourceIndex, $this->dependenciesIndex];
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ declare(strict_types = 1);
|
|||
namespace LanguageServer;
|
||||
|
||||
use LanguageServer\Protocol\{
|
||||
ConfigurationItem,
|
||||
ServerCapabilities,
|
||||
ClientCapabilities,
|
||||
TextDocumentSyncKind,
|
||||
|
@ -15,7 +16,7 @@ use LanguageServer\Protocol\{
|
|||
use LanguageServer\FilesFinder\{FilesFinder, ClientFilesFinder, FileSystemFilesFinder};
|
||||
use LanguageServer\ContentRetriever\{ContentRetriever, ClientContentRetriever, FileSystemContentRetriever};
|
||||
use LanguageServer\Index\{DependenciesIndex, GlobalIndex, Index, ProjectIndex, StubsIndex};
|
||||
use LanguageServer\Cache\{FileSystemCache, ClientCache};
|
||||
use LanguageServer\Cache\{Cache, FileSystemCache, ClientCache};
|
||||
use AdvancedJsonRpc;
|
||||
use Sabre\Event\Promise;
|
||||
use function Sabre\Event\coroutine;
|
||||
|
@ -106,6 +107,16 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
|||
*/
|
||||
protected $definitionResolver;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $rootPath;
|
||||
|
||||
/**
|
||||
* @var Cache
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* @param ProtocolReader $reader
|
||||
* @param ProtocolWriter $writer
|
||||
|
@ -162,14 +173,18 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
|||
*
|
||||
* @param ClientCapabilities $capabilities The capabilities provided by the client (editor)
|
||||
* @param string|null $rootPath The rootPath of the workspace. Is null if no folder is open.
|
||||
* @param int|null $processId The process Id of the parent process that started the server. Is null if the process has not been started by another process. If the parent process is not alive then the server should exit (see exit notification) its process.
|
||||
* @param Options $initializationOptions The options send from client to initialize the server
|
||||
* @param int|null $processId The process Id of the parent process that started the server.
|
||||
* Is null if the process has not been started by another process.
|
||||
* If the parent process is not alive then the server should exit
|
||||
* (see exit notification) its process.
|
||||
* @return Promise <InitializeResult>
|
||||
*/
|
||||
public function initialize(ClientCapabilities $capabilities, string $rootPath = null, int $processId = null, Options $initializationOptions = null): Promise
|
||||
{
|
||||
return coroutine(function () use ($capabilities, $rootPath, $processId, $initializationOptions) {
|
||||
|
||||
public function initialize(
|
||||
ClientCapabilities $capabilities,
|
||||
string $rootPath = null,
|
||||
int $processId = null
|
||||
): Promise {
|
||||
return coroutine(function () use ($capabilities, $rootPath, $processId) {
|
||||
if ($capabilities->xfilesProvider) {
|
||||
$this->filesFinder = new ClientFilesFinder($this->client);
|
||||
} else {
|
||||
|
@ -187,82 +202,48 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
|||
$this->projectIndex = new ProjectIndex($sourceIndex, $dependenciesIndex, $this->composerJson);
|
||||
$stubsIndex = StubsIndex::read();
|
||||
$this->globalIndex = new GlobalIndex($stubsIndex, $this->projectIndex);
|
||||
$initializationOptions = $initializationOptions ?? new Options;
|
||||
$this->rootPath = $rootPath;
|
||||
|
||||
// The DefinitionResolver should look in stubs, the project source and dependencies
|
||||
$this->definitionResolver = new DefinitionResolver($this->globalIndex);
|
||||
|
||||
$this->documentLoader = new PhpDocumentLoader(
|
||||
$this->contentRetriever,
|
||||
$this->projectIndex,
|
||||
$this->definitionResolver
|
||||
);
|
||||
|
||||
if ($rootPath !== null) {
|
||||
yield $this->beforeIndex($rootPath);
|
||||
if ($this->rootPath !== null) {
|
||||
yield $this->beforeIndex($this->rootPath);
|
||||
|
||||
// Find composer.json
|
||||
if ($this->composerJson === null) {
|
||||
$composerJsonFiles = yield $this->filesFinder->find(Path::makeAbsolute('**/composer.json', $rootPath));
|
||||
$composerJsonFiles = yield $this->filesFinder->find(
|
||||
Path::makeAbsolute('**/composer.json', $this->rootPath)
|
||||
);
|
||||
sortUrisLevelOrder($composerJsonFiles);
|
||||
|
||||
if (!empty($composerJsonFiles)) {
|
||||
$this->composerJson = json_decode(yield $this->contentRetriever->retrieve($composerJsonFiles[0]));
|
||||
$this->composerJson = json_decode(
|
||||
yield $this->contentRetriever->retrieve($composerJsonFiles[0])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Find composer.lock
|
||||
if ($this->composerLock === null) {
|
||||
$composerLockFiles = yield $this->filesFinder->find(Path::makeAbsolute('**/composer.lock', $rootPath));
|
||||
$composerLockFiles = yield $this->filesFinder->find(
|
||||
Path::makeAbsolute('**/composer.lock', $this->rootPath)
|
||||
);
|
||||
sortUrisLevelOrder($composerLockFiles);
|
||||
|
||||
if (!empty($composerLockFiles)) {
|
||||
$this->composerLock = json_decode(yield $this->contentRetriever->retrieve($composerLockFiles[0]));
|
||||
$this->composerLock = json_decode(
|
||||
yield $this->contentRetriever->retrieve($composerLockFiles[0])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$cache = $capabilities->xcacheProvider ? new ClientCache($this->client) : new FileSystemCache;
|
||||
|
||||
// Index in background
|
||||
$indexer = new Indexer(
|
||||
$this->filesFinder,
|
||||
$rootPath,
|
||||
$this->client,
|
||||
$cache,
|
||||
$dependenciesIndex,
|
||||
$sourceIndex,
|
||||
$this->documentLoader,
|
||||
$initializationOptions,
|
||||
$this->composerLock,
|
||||
$this->composerJson,
|
||||
$initializationOptions
|
||||
);
|
||||
$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,
|
||||
$indexer,
|
||||
$initializationOptions
|
||||
);
|
||||
$this->cache = $capabilities->xcacheProvider ? new ClientCache($this->client) : new FileSystemCache;
|
||||
}
|
||||
|
||||
$serverCapabilities = new ServerCapabilities();
|
||||
|
@ -295,10 +276,71 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The initialized notification is sent from the client to the server after
|
||||
* the client received the result of the initialize request but before the
|
||||
* client is sending any other request or notification to the server.
|
||||
*
|
||||
* @return Promise
|
||||
*/
|
||||
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->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,
|
||||
$options,
|
||||
$this->composerLock,
|
||||
$this->documentLoader,
|
||||
$this->composerJson
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The shutdown request is sent from the client to the server. It asks the server to shut down, but to not exit
|
||||
* (otherwise the response might not be delivered correctly to the client). There is a separate exit notification that
|
||||
* asks the server to exit.
|
||||
* (otherwise the response might not be delivered correctly to the client). There is a separate exit notification
|
||||
* that asks the server to exit.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace LanguageServer\Protocol;
|
||||
|
||||
class ConfigurationItem
|
||||
{
|
||||
/**
|
||||
* The scope to get the configuration section for.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public $scopeUri;
|
||||
|
||||
/**
|
||||
* The configuration section asked for.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public $section;
|
||||
}
|
|
@ -30,7 +30,7 @@ class LanguageServerTest extends TestCase
|
|||
public function testInitialize()
|
||||
{
|
||||
$server = new LanguageServer(new MockProtocolStream, new MockProtocolStream);
|
||||
$result = $server->initialize(new ClientCapabilities, __DIR__, getmypid(), new Options)->wait();
|
||||
$result = $server->initialize(new ClientCapabilities, __DIR__, getmypid())->wait();
|
||||
|
||||
$serverCapabilities = new ServerCapabilities();
|
||||
$serverCapabilities->textDocumentSync = TextDocumentSyncKind::FULL;
|
||||
|
@ -56,8 +56,13 @@ class LanguageServerTest extends TestCase
|
|||
$promise = new Promise;
|
||||
$input = new MockProtocolStream;
|
||||
$output = new MockProtocolStream;
|
||||
$output->on('message', function (Message $msg) use ($promise) {
|
||||
if ($msg->body->method === 'window/logMessage' && $promise->state === Promise::PENDING) {
|
||||
$output->on('message', function (Message $msg) use ($promise, $input) {
|
||||
if ($msg->body->method === 'workspace/configuration') {
|
||||
$result = new \stdClass();
|
||||
$result->fileTypes = ['.php'];
|
||||
|
||||
$input->write(new Message(new AdvancedJsonRpc\SuccessResponse($msg->body->id, [$result])));
|
||||
} elseif ($msg->body->method === 'window/logMessage' && $promise->state === Promise::PENDING) {
|
||||
if ($msg->body->params->type === MessageType::ERROR) {
|
||||
$promise->reject(new Exception($msg->body->params->message));
|
||||
} else if (preg_match('/All \d+ PHP files parsed/', $msg->body->params->message)) {
|
||||
|
@ -67,7 +72,8 @@ class LanguageServerTest extends TestCase
|
|||
});
|
||||
$server = new LanguageServer($input, $output);
|
||||
$capabilities = new ClientCapabilities;
|
||||
$server->initialize($capabilities, realpath(__DIR__ . '/../fixtures'), getmypid(), new Options);
|
||||
$server->initialize($capabilities, realpath(__DIR__ . '/../fixtures'), getmypid());
|
||||
$server->initialized();
|
||||
$this->assertTrue($promise->wait());
|
||||
}
|
||||
|
||||
|
@ -81,7 +87,12 @@ class LanguageServerTest extends TestCase
|
|||
$output = new MockProtocolStream;
|
||||
$run = 1;
|
||||
$output->on('message', function (Message $msg) use ($promise, $input, $rootPath, &$filesCalled, &$contentCalled, &$run) {
|
||||
if ($msg->body->method === 'textDocument/xcontent') {
|
||||
if ($msg->body->method === 'workspace/configuration') {
|
||||
$result = new \stdClass();
|
||||
$result->fileTypes = ['.php'];
|
||||
|
||||
$input->write(new Message(new AdvancedJsonRpc\SuccessResponse($msg->body->id, [$result])));
|
||||
} elseif ($msg->body->method === 'textDocument/xcontent') {
|
||||
// Document content requested
|
||||
$contentCalled = true;
|
||||
$textDocumentItem = new TextDocumentItem;
|
||||
|
@ -115,7 +126,8 @@ class LanguageServerTest extends TestCase
|
|||
$capabilities = new ClientCapabilities;
|
||||
$capabilities->xfilesProvider = true;
|
||||
$capabilities->xcontentProvider = true;
|
||||
$server->initialize($capabilities, $rootPath, getmypid(), new Options);
|
||||
$server->initialize($capabilities, $rootPath, getmypid())->wait();
|
||||
$server->initialized();
|
||||
$promise->wait();
|
||||
$this->assertTrue($filesCalled);
|
||||
$this->assertTrue($contentCalled);
|
||||
|
@ -126,13 +138,14 @@ class LanguageServerTest extends TestCase
|
|||
$promise = new Promise;
|
||||
$input = new MockProtocolStream;
|
||||
$output = new MockProtocolStream;
|
||||
$options = new Options;
|
||||
$options->setFileTypes([
|
||||
'.php',
|
||||
'.inc'
|
||||
]);
|
||||
$output->on('message', function (Message $msg) use ($promise, &$allFilesParsed) {
|
||||
if ($msg->body->method === 'window/logMessage' && $promise->state === Promise::PENDING) {
|
||||
|
||||
$output->on('message', function (Message $msg) use ($promise, $input) {
|
||||
if ($msg->body->method === 'workspace/configuration') {
|
||||
$result = new \stdClass();
|
||||
$result->fileTypes = ['.php', '.inc'];
|
||||
|
||||
$input->write(new Message(new AdvancedJsonRpc\SuccessResponse($msg->body->id, [$result])));
|
||||
} elseif ($msg->body->method === 'window/logMessage' && $promise->state === Promise::PENDING) {
|
||||
if ($msg->body->params->type === MessageType::ERROR) {
|
||||
$promise->reject(new Exception($msg->body->params->message));
|
||||
} elseif (preg_match('/All \d+ PHP files parsed/', $msg->body->params->message)) {
|
||||
|
@ -142,7 +155,8 @@ class LanguageServerTest extends TestCase
|
|||
});
|
||||
$server = new LanguageServer($input, $output);
|
||||
$capabilities = new ClientCapabilities;
|
||||
$server->initialize($capabilities, realpath(__DIR__ . '/../fixtures'), getmypid(), $options);
|
||||
$server->initialize($capabilities, realpath(__DIR__ . '/../fixtures'), getmypid());
|
||||
$server->initialized();
|
||||
$this->assertTrue($promise->wait());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue