Merge b7bcf00ab3
into 3d8318bd03
commit
1e09837cb7
|
@ -0,0 +1 @@
|
||||||
|
<?php
|
|
@ -53,6 +53,11 @@ class Indexer
|
||||||
*/
|
*/
|
||||||
private $documentLoader;
|
private $documentLoader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Options
|
||||||
|
*/
|
||||||
|
private $options;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \stdClasss
|
* @var \stdClasss
|
||||||
*/
|
*/
|
||||||
|
@ -70,6 +75,7 @@ class Indexer
|
||||||
* @param Cache $cache
|
* @param Cache $cache
|
||||||
* @param DependenciesIndex $dependenciesIndex
|
* @param DependenciesIndex $dependenciesIndex
|
||||||
* @param Index $sourceIndex
|
* @param Index $sourceIndex
|
||||||
|
* @param Options $options
|
||||||
* @param PhpDocumentLoader $documentLoader
|
* @param PhpDocumentLoader $documentLoader
|
||||||
* @param \stdClass|null $composerLock
|
* @param \stdClass|null $composerLock
|
||||||
*/
|
*/
|
||||||
|
@ -81,6 +87,7 @@ class Indexer
|
||||||
DependenciesIndex $dependenciesIndex,
|
DependenciesIndex $dependenciesIndex,
|
||||||
Index $sourceIndex,
|
Index $sourceIndex,
|
||||||
PhpDocumentLoader $documentLoader,
|
PhpDocumentLoader $documentLoader,
|
||||||
|
Options $options,
|
||||||
\stdClass $composerLock = null,
|
\stdClass $composerLock = null,
|
||||||
\stdClass $composerJson = null
|
\stdClass $composerJson = null
|
||||||
) {
|
) {
|
||||||
|
@ -91,6 +98,7 @@ class Indexer
|
||||||
$this->dependenciesIndex = $dependenciesIndex;
|
$this->dependenciesIndex = $dependenciesIndex;
|
||||||
$this->sourceIndex = $sourceIndex;
|
$this->sourceIndex = $sourceIndex;
|
||||||
$this->documentLoader = $documentLoader;
|
$this->documentLoader = $documentLoader;
|
||||||
|
$this->options = $options;
|
||||||
$this->composerLock = $composerLock;
|
$this->composerLock = $composerLock;
|
||||||
$this->composerJson = $composerJson;
|
$this->composerJson = $composerJson;
|
||||||
}
|
}
|
||||||
|
@ -103,8 +111,8 @@ class Indexer
|
||||||
public function index(): Promise
|
public function index(): Promise
|
||||||
{
|
{
|
||||||
return coroutine(function () {
|
return coroutine(function () {
|
||||||
|
$fileTypes = implode(',', $this->options->fileTypes);
|
||||||
$pattern = Path::makeAbsolute('**/*.php', $this->rootPath);
|
$pattern = Path::makeAbsolute('**/*{' . $fileTypes . '}', $this->rootPath);
|
||||||
$uris = yield $this->filesFinder->find($pattern);
|
$uris = yield $this->filesFinder->find($pattern);
|
||||||
|
|
||||||
$count = count($uris);
|
$count = count($uris);
|
||||||
|
@ -213,7 +221,7 @@ class Indexer
|
||||||
yield timeout();
|
yield timeout();
|
||||||
$this->client->window->logMessage(MessageType::LOG, "Parsing $uri");
|
$this->client->window->logMessage(MessageType::LOG, "Parsing $uri");
|
||||||
try {
|
try {
|
||||||
$document = yield $this->documentLoader->load($uri);
|
$document = yield $this->documentLoader->load($uri, $this->options->fileSizeLimit);
|
||||||
if (!isVendored($document, $this->composerJson)) {
|
if (!isVendored($document, $this->composerJson)) {
|
||||||
$this->client->textDocument->publishDiagnostics($uri, $document->getDiagnostics());
|
$this->client->textDocument->publishDiagnostics($uri, $document->getDiagnostics());
|
||||||
}
|
}
|
||||||
|
|
|
@ -163,11 +163,12 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
||||||
* @param ClientCapabilities $capabilities The capabilities provided by the client (editor)
|
* @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 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 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
|
||||||
* @return Promise <InitializeResult>
|
* @return Promise <InitializeResult>
|
||||||
*/
|
*/
|
||||||
public function initialize(ClientCapabilities $capabilities, string $rootPath = null, int $processId = null): Promise
|
public function initialize(ClientCapabilities $capabilities, string $rootPath = null, int $processId = null, Options $initializationOptions = null): Promise
|
||||||
{
|
{
|
||||||
return coroutine(function () use ($capabilities, $rootPath, $processId) {
|
return coroutine(function () use ($capabilities, $rootPath, $processId, $initializationOptions) {
|
||||||
|
|
||||||
if ($capabilities->xfilesProvider) {
|
if ($capabilities->xfilesProvider) {
|
||||||
$this->filesFinder = new ClientFilesFinder($this->client);
|
$this->filesFinder = new ClientFilesFinder($this->client);
|
||||||
|
@ -186,6 +187,7 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
||||||
$this->projectIndex = new ProjectIndex($sourceIndex, $dependenciesIndex, $this->composerJson);
|
$this->projectIndex = new ProjectIndex($sourceIndex, $dependenciesIndex, $this->composerJson);
|
||||||
$stubsIndex = StubsIndex::read();
|
$stubsIndex = StubsIndex::read();
|
||||||
$this->globalIndex = new GlobalIndex($stubsIndex, $this->projectIndex);
|
$this->globalIndex = new GlobalIndex($stubsIndex, $this->projectIndex);
|
||||||
|
$initializationOptions = $initializationOptions ?? new Options;
|
||||||
|
|
||||||
// The DefinitionResolver should look in stubs, the project source and dependencies
|
// The DefinitionResolver should look in stubs, the project source and dependencies
|
||||||
$this->definitionResolver = new DefinitionResolver($this->globalIndex);
|
$this->definitionResolver = new DefinitionResolver($this->globalIndex);
|
||||||
|
@ -230,8 +232,10 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
||||||
$dependenciesIndex,
|
$dependenciesIndex,
|
||||||
$sourceIndex,
|
$sourceIndex,
|
||||||
$this->documentLoader,
|
$this->documentLoader,
|
||||||
|
$initializationOptions,
|
||||||
$this->composerLock,
|
$this->composerLock,
|
||||||
$this->composerJson
|
$this->composerJson,
|
||||||
|
$initializationOptions
|
||||||
);
|
);
|
||||||
$indexer->index()->otherwise('\\LanguageServer\\crash');
|
$indexer->index()->otherwise('\\LanguageServer\\crash');
|
||||||
}
|
}
|
||||||
|
@ -255,7 +259,9 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
||||||
$sourceIndex,
|
$sourceIndex,
|
||||||
$this->composerLock,
|
$this->composerLock,
|
||||||
$this->documentLoader,
|
$this->documentLoader,
|
||||||
$this->composerJson
|
$this->composerJson,
|
||||||
|
$indexer,
|
||||||
|
$initializationOptions
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace LanguageServer;
|
||||||
|
|
||||||
|
class Options
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* File types the indexer should process
|
||||||
|
*
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
public $fileTypes = ['.php'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum file size to index
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $fileSizeLimit = 150000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate/Filter input and set options for file types
|
||||||
|
*
|
||||||
|
* @param string[] $fileTypes List of file types
|
||||||
|
*/
|
||||||
|
public function setFileTypes(array $fileTypes)
|
||||||
|
{
|
||||||
|
$fileTypes = filter_var_array($fileTypes, FILTER_SANITIZE_STRING);
|
||||||
|
$fileTypes = filter_var($fileTypes, FILTER_CALLBACK, ['options' => [$this, 'filterFileTypes']]);
|
||||||
|
$fileTypes = array_filter($fileTypes, 'strlen');
|
||||||
|
$fileTypes = array_values($fileTypes);
|
||||||
|
|
||||||
|
$this->fileTypes = !empty($fileTypes) ? $fileTypes : $this->fileTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate/Filter input and set option for file size limit
|
||||||
|
*
|
||||||
|
* @param string $fileSizeLimit Size in human readable format or -1 for unlimited
|
||||||
|
*/
|
||||||
|
public function setFileSizeLimit(string $fileSizeLimit)
|
||||||
|
{
|
||||||
|
$fileSizeLimit = filter_var($fileSizeLimit, FILTER_SANITIZE_STRING);
|
||||||
|
|
||||||
|
if ($fileSizeLimit === '-1') {
|
||||||
|
$this->fileSizeLimit = PHP_INT_MAX;
|
||||||
|
} else {
|
||||||
|
$this->fileSizeLimit = $this->convertFileSize($fileSizeLimit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter valid file type
|
||||||
|
*
|
||||||
|
* @param string $fileType The file type to filter
|
||||||
|
* @return string|bool If valid it returns the file type, otherwise false
|
||||||
|
*/
|
||||||
|
private function filterFileTypes(string $fileType)
|
||||||
|
{
|
||||||
|
$fileType = trim($fileType);
|
||||||
|
|
||||||
|
if (empty($fileType)) {
|
||||||
|
return $fileType;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (substr($fileType, 0, 1) !== '.') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $fileType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert human readable file size to byte
|
||||||
|
*
|
||||||
|
* @param string $fileSize
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
private function convertFileSize(string $fileSize)
|
||||||
|
{
|
||||||
|
preg_match('/(\d+)(\w)/', $fileSize, $match);
|
||||||
|
$sizes = 'KMG';
|
||||||
|
$size = (int) $match[1];
|
||||||
|
$factor = strpos($sizes, strtoupper($match[2])) + 1;
|
||||||
|
|
||||||
|
return $size * pow(1000, $factor);
|
||||||
|
}
|
||||||
|
}
|
|
@ -100,13 +100,12 @@ class PhpDocumentLoader
|
||||||
* The document is NOT added to the list of open documents, but definitions are registered.
|
* The document is NOT added to the list of open documents, but definitions are registered.
|
||||||
*
|
*
|
||||||
* @param string $uri
|
* @param string $uri
|
||||||
|
* @param int $limit
|
||||||
* @return Promise <PhpDocument>
|
* @return Promise <PhpDocument>
|
||||||
*/
|
*/
|
||||||
public function load(string $uri): Promise
|
public function load(string $uri, int $limit): Promise
|
||||||
{
|
{
|
||||||
return coroutine(function () use ($uri) {
|
return coroutine(function () use ($uri, $limit) {
|
||||||
|
|
||||||
$limit = 150000;
|
|
||||||
$content = yield $this->contentRetriever->retrieve($uri);
|
$content = yield $this->contentRetriever->retrieve($uri);
|
||||||
$size = strlen($content);
|
$size = strlen($content);
|
||||||
if ($size > $limit) {
|
if ($size > $limit) {
|
||||||
|
|
|
@ -5,6 +5,7 @@ namespace LanguageServer\Tests;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use LanguageServer\LanguageServer;
|
use LanguageServer\LanguageServer;
|
||||||
|
use LanguageServer\Options;
|
||||||
use LanguageServer\Protocol\{
|
use LanguageServer\Protocol\{
|
||||||
Message,
|
Message,
|
||||||
ClientCapabilities,
|
ClientCapabilities,
|
||||||
|
@ -29,7 +30,7 @@ class LanguageServerTest extends TestCase
|
||||||
public function testInitialize()
|
public function testInitialize()
|
||||||
{
|
{
|
||||||
$server = new LanguageServer(new MockProtocolStream, new MockProtocolStream);
|
$server = new LanguageServer(new MockProtocolStream, new MockProtocolStream);
|
||||||
$result = $server->initialize(new ClientCapabilities, __DIR__, getmypid())->wait();
|
$result = $server->initialize(new ClientCapabilities, __DIR__, getmypid(), new Options)->wait();
|
||||||
|
|
||||||
$serverCapabilities = new ServerCapabilities();
|
$serverCapabilities = new ServerCapabilities();
|
||||||
$serverCapabilities->textDocumentSync = TextDocumentSyncKind::FULL;
|
$serverCapabilities->textDocumentSync = TextDocumentSyncKind::FULL;
|
||||||
|
@ -60,14 +61,14 @@ class LanguageServerTest extends TestCase
|
||||||
if ($msg->body->params->type === MessageType::ERROR) {
|
if ($msg->body->params->type === MessageType::ERROR) {
|
||||||
$promise->reject(new Exception($msg->body->params->message));
|
$promise->reject(new Exception($msg->body->params->message));
|
||||||
} else if (preg_match('/All \d+ PHP files parsed/', $msg->body->params->message)) {
|
} else if (preg_match('/All \d+ PHP files parsed/', $msg->body->params->message)) {
|
||||||
$promise->fulfill();
|
$promise->fulfill(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$server = new LanguageServer($input, $output);
|
$server = new LanguageServer($input, $output);
|
||||||
$capabilities = new ClientCapabilities;
|
$capabilities = new ClientCapabilities;
|
||||||
$server->initialize($capabilities, realpath(__DIR__ . '/../fixtures'), getmypid());
|
$server->initialize($capabilities, realpath(__DIR__ . '/../fixtures'), getmypid(), new Options);
|
||||||
$promise->wait();
|
$this->assertTrue($promise->wait());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testIndexingWithFilesAndContentRequests()
|
public function testIndexingWithFilesAndContentRequests()
|
||||||
|
@ -114,9 +115,34 @@ class LanguageServerTest extends TestCase
|
||||||
$capabilities = new ClientCapabilities;
|
$capabilities = new ClientCapabilities;
|
||||||
$capabilities->xfilesProvider = true;
|
$capabilities->xfilesProvider = true;
|
||||||
$capabilities->xcontentProvider = true;
|
$capabilities->xcontentProvider = true;
|
||||||
$server->initialize($capabilities, $rootPath, getmypid());
|
$server->initialize($capabilities, $rootPath, getmypid(), new Options);
|
||||||
$promise->wait();
|
$promise->wait();
|
||||||
$this->assertTrue($filesCalled);
|
$this->assertTrue($filesCalled);
|
||||||
$this->assertTrue($contentCalled);
|
$this->assertTrue($contentCalled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testIndexingMultipleFileTypes()
|
||||||
|
{
|
||||||
|
$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) {
|
||||||
|
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)) {
|
||||||
|
$promise->fulfill(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$server = new LanguageServer($input, $output);
|
||||||
|
$capabilities = new ClientCapabilities;
|
||||||
|
$server->initialize($capabilities, realpath(__DIR__ . '/../fixtures'), getmypid(), $options);
|
||||||
|
$this->assertTrue($promise->wait());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LanguageServer\Tests;
|
||||||
|
|
||||||
|
use LanguageServer\Options;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class OptionsTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testFileTypesOption()
|
||||||
|
{
|
||||||
|
$expected = [
|
||||||
|
'.php',
|
||||||
|
'.valid'
|
||||||
|
];
|
||||||
|
|
||||||
|
$options = new Options;
|
||||||
|
$options->setFileTypes([
|
||||||
|
'.php',
|
||||||
|
false,
|
||||||
|
12345,
|
||||||
|
'.valid'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertSame($expected, $options->fileTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testConvertFileSize()
|
||||||
|
{
|
||||||
|
$options = new Options();
|
||||||
|
|
||||||
|
$options->setFileSizeLimit('150K');
|
||||||
|
$this->assertEquals(150000, $options->fileSizeLimit);
|
||||||
|
|
||||||
|
$options->setFileSizeLimit('15M');
|
||||||
|
$this->assertEquals(15000000, $options->fileSizeLimit);
|
||||||
|
|
||||||
|
$options->setFileSizeLimit('15G');
|
||||||
|
$this->assertEquals(15000000000, $options->fileSizeLimit);
|
||||||
|
|
||||||
|
$options->setFileSizeLimit('-1');
|
||||||
|
$this->assertEquals(PHP_INT_MAX, $options->fileSizeLimit);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue