From 6d1f1ff1d3e1e5d73f9d24554b07fdac35e6fd15 Mon Sep 17 00:00:00 2001 From: Felix Becker Date: Sun, 30 Oct 2016 23:14:45 +0100 Subject: [PATCH 01/24] Add workspace/xGlob method --- composer.json | 3 ++- src/Client/Workspace.php | 44 ++++++++++++++++++++++++++++++++++++++++ src/LanguageClient.php | 8 ++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 src/Client/Workspace.php diff --git a/composer.json b/composer.json index 6d9159ba..ffb47761 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,8 @@ "sabre/event": "^5.0", "felixfbecker/advanced-json-rpc": "^2.0", "squizlabs/php_codesniffer" : "^2.7", - "symfony/debug": "^3.1" + "symfony/debug": "^3.1", + "netresearch/jsonmapper": "^1.0" }, "minimum-stability": "dev", "prefer-stable": true, diff --git a/src/Client/Workspace.php b/src/Client/Workspace.php new file mode 100644 index 00000000..567ccebb --- /dev/null +++ b/src/Client/Workspace.php @@ -0,0 +1,44 @@ +handler = $handler; + $this->mapper = $mapper; + } + + /** + * Returns a list of all files in the workspace that match a glob pattern + * + * @param string $pattern A glob pattern + * @return Promise Array of documents that match the glob pattern + */ + public function xGlob(string $pattern): Promise + { + return $this->handler->request('workspace/xGlob', ['pattern' => $pattern])->then(function ($textDocuments) { + return $this->mapper->mapArray($textDocuments, [], TextDocumentIdentifier::class); + }); + } +} diff --git a/src/LanguageClient.php b/src/LanguageClient.php index 1f3c42a8..70ff952e 100644 --- a/src/LanguageClient.php +++ b/src/LanguageClient.php @@ -19,11 +19,19 @@ class LanguageClient */ public $window; + /** + * Handles workspace/* methods + * + * @var Client\Workspace + */ + public $workspace; + public function __construct(ProtocolReader $reader, ProtocolWriter $writer) { $handler = new ClientHandler($reader, $writer); $this->textDocument = new Client\TextDocument($handler); $this->window = new Client\Window($handler); + $this->workspace = new Client\Workspace($handler, $mapper); } } From f2925a2650ef614591316dd765c1a853345bc7bb Mon Sep 17 00:00:00 2001 From: Felix Becker Date: Mon, 31 Oct 2016 00:06:36 +0100 Subject: [PATCH 02/24] Use workspace/xGlob for indexing --- src/LanguageClient.php | 3 +++ src/LanguageServer.php | 46 ++++++++++++++++++++++++------------------ 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/src/LanguageClient.php b/src/LanguageClient.php index 70ff952e..4745a4db 100644 --- a/src/LanguageClient.php +++ b/src/LanguageClient.php @@ -3,6 +3,8 @@ namespace LanguageServer; +use JsonMapper; + class LanguageClient { /** @@ -29,6 +31,7 @@ class LanguageClient public function __construct(ProtocolReader $reader, ProtocolWriter $writer) { $handler = new ClientHandler($reader, $writer); + $mapper = new JsonMapper; $this->textDocument = new Client\TextDocument($handler); $this->window = new Client\Window($handler); diff --git a/src/LanguageServer.php b/src/LanguageServer.php index 11a45dbd..aa0cb044 100644 --- a/src/LanguageServer.php +++ b/src/LanguageServer.php @@ -14,6 +14,7 @@ }; use AdvancedJsonRpc; use Sabre\Event\Loop; +use function Sabre\Event\coroutine; use Exception; use Throwable; @@ -38,6 +39,11 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher public $completionItem; public $codeLens; + /** + * ClientCapabilities + */ + private $clientCapabilities; + private $protocolReader; private $protocolWriter; private $client; @@ -155,38 +161,38 @@ public function exit() */ private function indexProject() { - $fileList = findFilesRecursive($this->rootPath, '/^.+\.php$/i'); - $numTotalFiles = count($fileList); + coroutine(function () { + $textDocuments = $this->client->workspace->xGlob('**/*.php'); + $count = count($textDocuments); + + $startTime = microtime(true); - $startTime = microtime(true); - $fileNum = 0; + foreach ($textDocuments as $i => $textDocument) { - $processFile = function () use (&$fileList, &$fileNum, &$processFile, $numTotalFiles, $startTime) { - if ($fileNum < $numTotalFiles) { - $file = $fileList[$fileNum]; - $uri = pathToUri($file); - $fileNum++; - $shortName = substr($file, strlen($this->rootPath) + 1); + // Give LS to the chance to handle requests while indexing + Loop\tick(); + + try { + $shortName = substr(uriToPath($textDocument->uri), strlen($this->rootPath) + 1); + } catch (Exception $e) { + $shortName = $textDocument->uri; + } if (filesize($file) > 500000) { $this->client->window->logMessage(MessageType::INFO, "Not parsing $shortName because it exceeds size limit of 0.5MB"); } else { - $this->client->window->logMessage(MessageType::INFO, "Parsing file $fileNum/$numTotalFiles: $shortName."); + $this->client->window->logMessage(MessageType::INFO, "Parsing file $i/$count: $shortName."); try { - $this->project->loadDocument($uri); + $this->project->loadDocument($textDocument->uri); } catch (Exception $e) { $this->client->window->logMessage(MessageType::ERROR, "Error parsing file $shortName: " . (string)$e); } } - - Loop\setTimeout($processFile, 0); - } else { - $duration = (int)(microtime(true) - $startTime); - $mem = (int)(memory_get_usage(true) / (1024 * 1024)); - $this->client->window->logMessage(MessageType::INFO, "All PHP files parsed in $duration seconds. $mem MiB allocated."); } - }; - Loop\setTimeout($processFile, 0); + $duration = (int)(microtime(true) - $startTime); + $mem = (int)(memory_get_usage(true) / (1024 * 1024)); + $this->client->window->logMessage(MessageType::INFO, "All PHP files parsed in $duration seconds. $mem MiB allocated."); + }); } } From b554ec5e984d2d57cb1edafdeea4d0b7e6fbb779 Mon Sep 17 00:00:00 2001 From: Felix Becker Date: Tue, 1 Nov 2016 09:31:21 +0100 Subject: [PATCH 03/24] Fixes --- src/LanguageServer.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/LanguageServer.php b/src/LanguageServer.php index aa0cb044..8406e7d9 100644 --- a/src/LanguageServer.php +++ b/src/LanguageServer.php @@ -162,13 +162,12 @@ public function exit() private function indexProject() { coroutine(function () { - $textDocuments = $this->client->workspace->xGlob('**/*.php'); + $textDocuments = yield $this->client->workspace->xGlob('**/*.php'); $count = count($textDocuments); $startTime = microtime(true); foreach ($textDocuments as $i => $textDocument) { - // Give LS to the chance to handle requests while indexing Loop\tick(); From 8116305966e5b28dbc15d3c486bbf3e10fdd9681 Mon Sep 17 00:00:00 2001 From: Felix Becker Date: Thu, 3 Nov 2016 09:11:44 +0100 Subject: [PATCH 04/24] Rename xGlob to _glob --- src/Client/Workspace.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Client/Workspace.php b/src/Client/Workspace.php index 567ccebb..cad08d32 100644 --- a/src/Client/Workspace.php +++ b/src/Client/Workspace.php @@ -35,9 +35,9 @@ public function __construct(ClientHandler $handler, JsonMapper $mapper) * @param string $pattern A glob pattern * @return Promise Array of documents that match the glob pattern */ - public function xGlob(string $pattern): Promise + public function _glob(string $pattern): Promise { - return $this->handler->request('workspace/xGlob', ['pattern' => $pattern])->then(function ($textDocuments) { + return $this->handler->request('workspace/_glob', ['pattern' => $pattern])->then(function ($textDocuments) { return $this->mapper->mapArray($textDocuments, [], TextDocumentIdentifier::class); }); } From 0060045a1e8e58aa886085309b86f6c3d93c0cc8 Mon Sep 17 00:00:00 2001 From: Felix Becker Date: Thu, 3 Nov 2016 10:37:19 +0100 Subject: [PATCH 05/24] Make it work --- src/Client/Workspace.php | 2 +- src/LanguageServer.php | 110 +++++++++++++++++----------- src/Protocol/ClientCapabilities.php | 5 +- 3 files changed, 73 insertions(+), 44 deletions(-) diff --git a/src/Client/Workspace.php b/src/Client/Workspace.php index cad08d32..7d85f99f 100644 --- a/src/Client/Workspace.php +++ b/src/Client/Workspace.php @@ -35,7 +35,7 @@ public function __construct(ClientHandler $handler, JsonMapper $mapper) * @param string $pattern A glob pattern * @return Promise Array of documents that match the glob pattern */ - public function _glob(string $pattern): Promise + public function xglob(string $pattern): Promise { return $this->handler->request('workspace/_glob', ['pattern' => $pattern])->then(function ($textDocuments) { return $this->mapper->mapArray($textDocuments, [], TextDocumentIdentifier::class); diff --git a/src/LanguageServer.php b/src/LanguageServer.php index 8406e7d9..cdc76417 100644 --- a/src/LanguageServer.php +++ b/src/LanguageServer.php @@ -17,6 +17,7 @@ use function Sabre\Event\coroutine; use Exception; use Throwable; +use Generator; class LanguageServer extends AdvancedJsonRpc\Dispatcher { @@ -61,32 +62,44 @@ public function __construct(ProtocolReader $reader, ProtocolWriter $writer) parent::__construct($this, '/'); $this->protocolReader = $reader; $this->protocolReader->on('message', function (Message $msg) { - // Ignore responses, this is the handler for requests and notifications - if (AdvancedJsonRpc\Response::isResponse($msg->body)) { - return; - } - $result = null; - $error = null; - try { - // Invoke the method handler to get a result - $result = $this->dispatch($msg->body); - } catch (AdvancedJsonRpc\Error $e) { - // If a ResponseError is thrown, send it back in the Response - $error = $e; - } catch (Throwable $e) { - // If an unexpected error occured, send back an INTERNAL_ERROR error response - $error = new AdvancedJsonRpc\Error($e->getMessage(), AdvancedJsonRpc\ErrorCode::INTERNAL_ERROR, null, $e); - } - // Only send a Response for a Request - // Notifications do not send Responses - if (AdvancedJsonRpc\Request::isRequest($msg->body)) { - if ($error !== null) { - $responseBody = new AdvancedJsonRpc\ErrorResponse($msg->body->id, $error); - } else { - $responseBody = new AdvancedJsonRpc\SuccessResponse($msg->body->id, $result); + coroutine(function () use ($msg) { + // Ignore responses, this is the handler for requests and notifications + if (AdvancedJsonRpc\Response::isResponse($msg->body)) { + return; } - $this->protocolWriter->write(new Message($responseBody)); - } + $result = null; + $error = null; + try { + // Invoke the method handler to get a result + $result = $this->dispatch($msg->body); + if ($result instanceof Generator) { + $result = yield from $result; + } else if ($result instanceof Promise) { + $result = yield $result; + } + } catch (AdvancedJsonRpc\Error $e) { + // If a ResponseError is thrown, send it back in the Response + $error = $e; + } catch (Throwable $e) { + // If an unexpected error occured, send back an INTERNAL_ERROR error response + $error = new AdvancedJsonRpc\Error((string)$e, AdvancedJsonRpc\ErrorCode::INTERNAL_ERROR, null, $e); + } + // Only send a Response for a Request + // Notifications do not send Responses + if (AdvancedJsonRpc\Request::isRequest($msg->body)) { + if ($error !== null) { + $responseBody = new AdvancedJsonRpc\ErrorResponse($msg->body->id, $error); + } else { + $responseBody = new AdvancedJsonRpc\SuccessResponse($msg->body->id, $result); + } + $this->protocolWriter->write(new Message($responseBody)); + } + })->otherwise(function ($err) { + // Crash process + Loop\nextTick(function () use ($err) { + throw $err; + }); + }); }); $this->protocolWriter = $writer; $this->client = new LanguageClient($reader, $writer); @@ -107,11 +120,18 @@ public function __construct(ProtocolReader $reader, ProtocolWriter $writer) */ public function initialize(int $processId, ClientCapabilities $capabilities, string $rootPath = null): InitializeResult { + $this->rootPath = $rootPath; + $this->clientCapabilities = $capabilities; // start building project index if ($rootPath !== null) { - $this->indexProject(); + $this->indexProject()->otherwise(function ($err) { + // Crash process + Loop\nextTick(function () use ($err) { + throw $err; + }); + }); } $serverCapabilities = new ServerCapabilities(); @@ -157,35 +177,41 @@ public function exit() /** * Parses workspace files, one at a time. * - * @return void + * @return Promise */ private function indexProject() { - coroutine(function () { - $textDocuments = yield $this->client->workspace->xGlob('**/*.php'); - $count = count($textDocuments); + return coroutine(function () { + if ($this->clientCapabilities->xglobProvider) { + $textDocuments = yield $this->client->workspace->xglob('**/*.php'); + $uris = array_map(function ($textDocument) { + return $textDocument->uri; + }, $textDocuments); + } else { + $uris = array_map(function ($path) { + return pathToUri($path); + }, findFilesRecursive($this->rootPath, '/^.+\.php$/i')); + } + $count = count($uris); $startTime = microtime(true); - foreach ($textDocuments as $i => $textDocument) { + foreach ($uris as $i => $uri) { // Give LS to the chance to handle requests while indexing Loop\tick(); try { - $shortName = substr(uriToPath($textDocument->uri), strlen($this->rootPath) + 1); + $shortName = substr(uriToPath($uri), strlen($this->rootPath) + 1); } catch (Exception $e) { - $shortName = $textDocument->uri; + $shortName = $uri; } - if (filesize($file) > 500000) { - $this->client->window->logMessage(MessageType::INFO, "Not parsing $shortName because it exceeds size limit of 0.5MB"); - } else { - $this->client->window->logMessage(MessageType::INFO, "Parsing file $i/$count: $shortName."); - try { - $this->project->loadDocument($textDocument->uri); - } catch (Exception $e) { - $this->client->window->logMessage(MessageType::ERROR, "Error parsing file $shortName: " . (string)$e); - } + + $this->client->window->logMessage(MessageType::INFO, "Parsing file $i/$count: $shortName."); + try { + $this->project->loadDocument($uri); + } catch (Exception $e) { + $this->client->window->logMessage(MessageType::ERROR, "Error parsing file $shortName: " . (string)$e); } } diff --git a/src/Protocol/ClientCapabilities.php b/src/Protocol/ClientCapabilities.php index c85e44bf..45ab02ad 100644 --- a/src/Protocol/ClientCapabilities.php +++ b/src/Protocol/ClientCapabilities.php @@ -4,5 +4,8 @@ class ClientCapabilities { - + /** + * @var bool|null + */ + public $xglobProvider; } From b9aeea2523d8c46cb16eb8086b08e8e2a53d3ee7 Mon Sep 17 00:00:00 2001 From: Felix Becker Date: Thu, 3 Nov 2016 11:52:51 +0100 Subject: [PATCH 06/24] Add globWorkspace helper --- src/LanguageServer.php | 57 ++++++++++++++++--------- src/utils.php | 19 --------- tests/Utils/RecursiveFileSearchTest.php | 21 --------- 3 files changed, 37 insertions(+), 60 deletions(-) delete mode 100644 tests/Utils/RecursiveFileSearchTest.php diff --git a/src/LanguageServer.php b/src/LanguageServer.php index cdc76417..ce74c0be 100644 --- a/src/LanguageServer.php +++ b/src/LanguageServer.php @@ -18,6 +18,8 @@ use Exception; use Throwable; use Generator; +use Webmozart\Glob\Iterator\GlobIterator; +use Webmozart\PathUril\Path; class LanguageServer extends AdvancedJsonRpc\Dispatcher { @@ -182,32 +184,15 @@ public function exit() private function indexProject() { return coroutine(function () { - if ($this->clientCapabilities->xglobProvider) { - $textDocuments = yield $this->client->workspace->xglob('**/*.php'); - $uris = array_map(function ($textDocument) { - return $textDocument->uri; - }, $textDocuments); - } else { - $uris = array_map(function ($path) { - return pathToUri($path); - }, findFilesRecursive($this->rootPath, '/^.+\.php$/i')); - } + $textDocuments = yield $this->globWorkspace('**/*.php'); $count = count($uris); $startTime = microtime(true); - foreach ($uris as $i => $uri) { + foreach ($textDocuments as $i => $textDocument) { // Give LS to the chance to handle requests while indexing Loop\tick(); - - try { - $shortName = substr(uriToPath($uri), strlen($this->rootPath) + 1); - } catch (Exception $e) { - $shortName = $uri; - } - - - $this->client->window->logMessage(MessageType::INFO, "Parsing file $i/$count: $shortName."); + $this->client->window->logMessage(MessageType::INFO, "Parsing file $i/$count: {$textDocument->uri}"); try { $this->project->loadDocument($uri); } catch (Exception $e) { @@ -220,4 +205,36 @@ private function indexProject() $this->client->window->logMessage(MessageType::INFO, "All PHP files parsed in $duration seconds. $mem MiB allocated."); }); } + + /** + * Returns all files matching a glob pattern. + * If the client does not support workspace/xglob, it falls back to globbing the file system directly. + * + * @param string $pattern + * @return Promise + */ + private function globWorkspace(string $pattern): Promise + { + if ($this->clientCapabilities->xglobProvider) { + // Use xglob request + return $this->client->workspace->xglob($pattern); + } else { + // Use the file system + $promise = new Promise; + $textDocuments = []; + $pattern = Path::makeAbsolute($pattern, $this->rootPath); + $iterator = new GlobIterator($pattern); + $next = function () use ($iterator, &$textDocuments, $promise, &$next) { + if (!$iterator->valid()) { + $promise->resolve($textDocuments); + return; + } + $textDocuments[] = new TextDocumentIdentifier(pathToUri($iterator->current())); + $iterator->next(); + Loop\setTimeout($next, 0); + }; + Loop\setTimeout($next, 0); + return $promise; + } + } } diff --git a/src/utils.php b/src/utils.php index 810fbc10..a190c8cf 100644 --- a/src/utils.php +++ b/src/utils.php @@ -5,25 +5,6 @@ use InvalidArgumentException; -/** - * Recursively Searches files with matching filename, starting at $path. - * - * @param string $path - * @param string $pattern - * @return array - */ -function findFilesRecursive(string $path, string $pattern): array -{ - $dir = new \RecursiveDirectoryIterator($path); - $ite = new \RecursiveIteratorIterator($dir); - $files = new \RegexIterator($ite, $pattern, \RegexIterator::GET_MATCH); - $fileList = []; - foreach ($files as $file) { - $fileList = array_merge($fileList, $file); - } - return $fileList; -} - /** * Transforms an absolute file path into a URI as used by the language server protocol. * diff --git a/tests/Utils/RecursiveFileSearchTest.php b/tests/Utils/RecursiveFileSearchTest.php deleted file mode 100644 index 671ebb1e..00000000 --- a/tests/Utils/RecursiveFileSearchTest.php +++ /dev/null @@ -1,21 +0,0 @@ -assertEquals([ - $path . DIRECTORY_SEPARATOR . 'a.txt', - $path . DIRECTORY_SEPARATOR . 'search' . DIRECTORY_SEPARATOR . 'b.txt', - $path . DIRECTORY_SEPARATOR . 'search' . DIRECTORY_SEPARATOR . 'here' . DIRECTORY_SEPARATOR . 'c.txt', - ], $files); - } -} From 35a296b7f3c97e318dad9ee1773e77348687819f Mon Sep 17 00:00:00 2001 From: Felix Becker Date: Sun, 6 Nov 2016 14:47:36 +0100 Subject: [PATCH 07/24] Make it work --- composer.json | 4 +- src/Client/TextDocument.php | 26 +++++++- src/LanguageClient.php | 2 +- src/LanguageServer.php | 59 +++++++------------ src/Project.php | 22 +++++-- src/Protocol/ClientCapabilities.php | 9 +++ src/Protocol/TextDocumentContentResult.php | 11 ++++ src/utils.php | 30 ++++++++++ tests/NodeVisitor/DefinitionCollectorTest.php | 5 +- tests/PhpDocumentTest.php | 5 +- tests/ProjectTest.php | 12 +++- tests/Server/ServerTestCase.php | 4 +- .../Definition/GlobalFallbackTest.php | 4 +- tests/Server/TextDocument/DidChangeTest.php | 5 +- tests/Server/TextDocument/DidCloseTest.php | 4 +- tests/Server/TextDocument/FormattingTest.php | 6 +- tests/Server/TextDocument/ParseErrorsTest.php | 7 ++- .../References/GlobalFallbackTest.php | 4 +- 18 files changed, 152 insertions(+), 67 deletions(-) create mode 100644 src/Protocol/TextDocumentContentResult.php diff --git a/composer.json b/composer.json index ffb47761..62a9217b 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,9 @@ "felixfbecker/advanced-json-rpc": "^2.0", "squizlabs/php_codesniffer" : "^2.7", "symfony/debug": "^3.1", - "netresearch/jsonmapper": "^1.0" + "netresearch/jsonmapper": "^1.0", + "webmozart/path-util": "^2.3", + "webmozart/glob": "^4.1" }, "minimum-stability": "dev", "prefer-stable": true, diff --git a/src/Client/TextDocument.php b/src/Client/TextDocument.php index d5314e4c..d96f7c11 100644 --- a/src/Client/TextDocument.php +++ b/src/Client/TextDocument.php @@ -6,6 +6,7 @@ use LanguageServer\ClientHandler; use LanguageServer\Protocol\Message; use Sabre\Event\Promise; +use JsonMapper; /** * Provides method handlers for all textDocument/* methods @@ -17,9 +18,15 @@ class TextDocument */ private $handler; - public function __construct(ClientHandler $handler) + /** + * @var JsonMapper + */ + private $mapper; + + public function __construct(ClientHandler $handler, JsonMapper $mapper) { $this->handler = $handler; + $this->mapper = $mapper; } /** @@ -36,4 +43,21 @@ public function publishDiagnostics(string $uri, array $diagnostics): Promise 'diagnostics' => $diagnostics ]); } + + /** + * The content request is sent from a server to a client + * to request the current content of a text document identified by the URI + * + * @param TextDocumentIdentifier $textDocument The document to get the content for + * @return Promise The document's current content + */ + public function xcontent(TextDocumentIdentifier $textDocument): Promise + { + return $this->handler->request( + 'textDocument/xcontent', + ['textDocument' => $textDocument] + )->then(function ($result) { + return $this->mapper->map($result, new TextDocumentContentResult); + }); + } } diff --git a/src/LanguageClient.php b/src/LanguageClient.php index 4745a4db..d21a9aae 100644 --- a/src/LanguageClient.php +++ b/src/LanguageClient.php @@ -33,7 +33,7 @@ public function __construct(ProtocolReader $reader, ProtocolWriter $writer) $handler = new ClientHandler($reader, $writer); $mapper = new JsonMapper; - $this->textDocument = new Client\TextDocument($handler); + $this->textDocument = new Client\TextDocument($handler, $mapper); $this->window = new Client\Window($handler); $this->workspace = new Client\Workspace($handler, $mapper); } diff --git a/src/LanguageServer.php b/src/LanguageServer.php index ce74c0be..014ae34c 100644 --- a/src/LanguageServer.php +++ b/src/LanguageServer.php @@ -10,16 +10,18 @@ Message, MessageType, InitializeResult, - SymbolInformation + SymbolInformation, + TextDocumentIdentifier }; use AdvancedJsonRpc; -use Sabre\Event\Loop; +use Sabre\Event\{Loop, Promise}; use function Sabre\Event\coroutine; use Exception; +use RuntimeException; use Throwable; use Generator; use Webmozart\Glob\Iterator\GlobIterator; -use Webmozart\PathUril\Path; +use Webmozart\PathUtil\Path; class LanguageServer extends AdvancedJsonRpc\Dispatcher { @@ -96,20 +98,10 @@ public function __construct(ProtocolReader $reader, ProtocolWriter $writer) } $this->protocolWriter->write(new Message($responseBody)); } - })->otherwise(function ($err) { - // Crash process - Loop\nextTick(function () use ($err) { - throw $err; - }); - }); + })->otherwise('\\LanguageServer\\crash'); }); $this->protocolWriter = $writer; $this->client = new LanguageClient($reader, $writer); - - $this->project = new Project($this->client); - - $this->textDocument = new Server\TextDocument($this->project, $this->client); - $this->workspace = new Server\Workspace($this->project, $this->client); } /** @@ -125,15 +117,13 @@ public function initialize(int $processId, ClientCapabilities $capabilities, str $this->rootPath = $rootPath; $this->clientCapabilities = $capabilities; + $this->project = new Project($this->client, $capabilities); + $this->textDocument = new Server\TextDocument($this->project, $this->client); + $this->workspace = new Server\Workspace($this->project, $this->client); // start building project index if ($rootPath !== null) { - $this->indexProject()->otherwise(function ($err) { - // Crash process - Loop\nextTick(function () use ($err) { - throw $err; - }); - }); + $this->indexProject()->otherwise('\\LanguageServer\\crash'); } $serverCapabilities = new ServerCapabilities(); @@ -164,6 +154,7 @@ public function initialize(int $processId, ClientCapabilities $capabilities, str */ public function shutdown() { + unset($this->project); } /** @@ -185,16 +176,16 @@ private function indexProject() { return coroutine(function () { $textDocuments = yield $this->globWorkspace('**/*.php'); - $count = count($uris); + $count = count($textDocuments); $startTime = microtime(true); foreach ($textDocuments as $i => $textDocument) { // Give LS to the chance to handle requests while indexing - Loop\tick(); + yield timeout(); $this->client->window->logMessage(MessageType::INFO, "Parsing file $i/$count: {$textDocument->uri}"); try { - $this->project->loadDocument($uri); + $this->project->loadDocument($textDocument->uri); } catch (Exception $e) { $this->client->window->logMessage(MessageType::ERROR, "Error parsing file $shortName: " . (string)$e); } @@ -220,21 +211,15 @@ private function globWorkspace(string $pattern): Promise return $this->client->workspace->xglob($pattern); } else { // Use the file system - $promise = new Promise; - $textDocuments = []; - $pattern = Path::makeAbsolute($pattern, $this->rootPath); - $iterator = new GlobIterator($pattern); - $next = function () use ($iterator, &$textDocuments, $promise, &$next) { - if (!$iterator->valid()) { - $promise->resolve($textDocuments); - return; + return coroutine(function () use ($pattern) { + $textDocuments = []; + $pattern = Path::makeAbsolute($pattern, $this->rootPath); + foreach (new GlobIterator($pattern) as $path) { + $textDocuments[] = new TextDocumentIdentifier(pathToUri($path)); + yield timeout(); } - $textDocuments[] = new TextDocumentIdentifier(pathToUri($iterator->current())); - $iterator->next(); - Loop\setTimeout($next, 0); - }; - Loop\setTimeout($next, 0); - return $promise; + return $textDocuments; + }); } } } diff --git a/src/Project.php b/src/Project.php index 0a5349f0..361f7b0f 100644 --- a/src/Project.php +++ b/src/Project.php @@ -3,7 +3,7 @@ namespace LanguageServer; -use LanguageServer\Protocol\SymbolInformation; +use LanguageServer\Protocol\{SymbolInformation, TextDocumentIdentifier, ClientCapabilities}; use phpDocumentor\Reflection\DocBlockFactory; class Project @@ -51,10 +51,17 @@ class Project */ private $client; - public function __construct(LanguageClient $client) + /** + * The client's capabilities + * + * @var ClientCapabilities + */ + private $clientCapabilities; + + public function __construct(LanguageClient $client, ClientCapabilities $clientCapabilities) { $this->client = $client; - + $this->clientCapabilities = $clientCapabilities; $this->parser = new Parser; $this->docBlockFactory = DocBlockFactory::createInstance(); } @@ -80,11 +87,16 @@ public function getDocument(string $uri) * The document is NOT added to the list of open documents, but definitions are registered. * * @param string $uri - * @return LanguageServer\PhpDocument + * @return Promise */ public function loadDocument(string $uri) { - $content = file_get_contents(uriToPath($uri)); + if ($this->clientCapabilities->xcontentProvider) { + // TODO: make this whole method async instead of calling wait() + $content = $this->client->textDocument->xcontent(new TextDocumentIdentifier($uri))->wait()->text; + } else { + $content = file_get_contents(uriToPath($uri)); + } if (isset($this->documents[$uri])) { $document = $this->documents[$uri]; $document->updateContent($content); diff --git a/src/Protocol/ClientCapabilities.php b/src/Protocol/ClientCapabilities.php index 45ab02ad..f72a6810 100644 --- a/src/Protocol/ClientCapabilities.php +++ b/src/Protocol/ClientCapabilities.php @@ -5,7 +5,16 @@ class ClientCapabilities { /** + * The client supports workspace/xglob requests + * * @var bool|null */ public $xglobProvider; + + /** + * The client supports textDocument/xcontent requests + * + * @var bool|null + */ + public $xcontentProvider; } diff --git a/src/Protocol/TextDocumentContentResult.php b/src/Protocol/TextDocumentContentResult.php new file mode 100644 index 00000000..758de556 --- /dev/null +++ b/src/Protocol/TextDocumentContentResult.php @@ -0,0 +1,11 @@ + + */ +function timeout($seconds = 0): Promise +{ + $promise = new Promise; + Loop\setTimeout([$promise, 'fulfill'], $seconds); + return $promise; +} diff --git a/tests/NodeVisitor/DefinitionCollectorTest.php b/tests/NodeVisitor/DefinitionCollectorTest.php index 0a8fdb15..46741dd9 100644 --- a/tests/NodeVisitor/DefinitionCollectorTest.php +++ b/tests/NodeVisitor/DefinitionCollectorTest.php @@ -7,6 +7,7 @@ use PhpParser\{NodeTraverser, Node}; use PhpParser\NodeVisitor\NameResolver; use LanguageServer\{LanguageClient, Project, PhpDocument, Parser}; +use LanguageServer\Protocol\ClientCapabilities; use LanguageServer\Tests\MockProtocolStream; use LanguageServer\NodeVisitor\{ReferencesAdder, DefinitionCollector}; use function LanguageServer\pathToUri; @@ -16,7 +17,7 @@ class DefinitionCollectorTest extends TestCase public function testCollectsSymbols() { $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream); - $project = new Project($client); + $project = new Project($client, new ClientCapabilities); $parser = new Parser; $uri = pathToUri(realpath(__DIR__ . '/../../fixtures/symbols.php')); $document = $project->loadDocument($uri); @@ -55,7 +56,7 @@ public function testCollectsSymbols() public function testDoesNotCollectReferences() { $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream); - $project = new Project($client); + $project = new Project($client, new ClientCapabilities); $parser = new Parser; $uri = pathToUri(realpath(__DIR__ . '/../../fixtures/references.php')); $document = $project->loadDocument($uri); diff --git a/tests/PhpDocumentTest.php b/tests/PhpDocumentTest.php index fce5dc4b..5cdc8c82 100644 --- a/tests/PhpDocumentTest.php +++ b/tests/PhpDocumentTest.php @@ -7,7 +7,7 @@ use LanguageServer\Tests\MockProtocolStream; use LanguageServer\{LanguageClient, Project}; use LanguageServer\NodeVisitor\NodeAtPositionFinder; -use LanguageServer\Protocol\{SymbolKind, Position}; +use LanguageServer\Protocol\{SymbolKind, Position, ClientCapabilities}; use PhpParser\Node; class PhpDocumentTest extends TestCase @@ -19,7 +19,8 @@ class PhpDocumentTest extends TestCase public function setUp() { - $this->project = new Project(new LanguageClient(new MockProtocolStream, new MockProtocolStream)); + $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream); + $this->project = new Project($client, new ClientCapabilities); } public function testParsesVariableVariables() diff --git a/tests/ProjectTest.php b/tests/ProjectTest.php index dc030d29..b8721c5d 100644 --- a/tests/ProjectTest.php +++ b/tests/ProjectTest.php @@ -6,7 +6,14 @@ use PHPUnit\Framework\TestCase; use LanguageServer\Tests\MockProtocolStream; use LanguageServer\{Server, Client, LanguageClient, Project, PhpDocument}; -use LanguageServer\Protocol\{TextDocumentItem, TextDocumentIdentifier, SymbolKind, DiagnosticSeverity, FormattingOptions}; +use LanguageServer\Protocol\{ + TextDocumentItem, + TextDocumentIdentifier, + SymbolKind, + DiagnosticSeverity, + FormattingOptions, + ClientCapabilities +}; use AdvancedJsonRpc\{Request as RequestBody, Response as ResponseBody}; use function LanguageServer\pathToUri; @@ -19,7 +26,8 @@ class ProjectTest extends TestCase public function setUp() { - $this->project = new Project(new LanguageClient(new MockProtocolStream, new MockProtocolStream)); + $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream); + $this->project = new Project($client, new ClientCapabilities); } public function testGetDocumentLoadsDocument() diff --git a/tests/Server/ServerTestCase.php b/tests/Server/ServerTestCase.php index 24095a37..10ef6367 100644 --- a/tests/Server/ServerTestCase.php +++ b/tests/Server/ServerTestCase.php @@ -6,7 +6,7 @@ use PHPUnit\Framework\TestCase; use LanguageServer\Tests\MockProtocolStream; use LanguageServer\{Server, LanguageClient, Project}; -use LanguageServer\Protocol\{Position, Location, Range}; +use LanguageServer\Protocol\{Position, Location, Range, ClientCapabilities}; use function LanguageServer\pathToUri; abstract class ServerTestCase extends TestCase @@ -43,7 +43,7 @@ abstract class ServerTestCase extends TestCase public function setUp() { $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream); - $this->project = new Project($client); + $this->project = new Project($client, new ClientCapabilities); $this->textDocument = new Server\TextDocument($this->project, $client); $this->workspace = new Server\Workspace($this->project, $client); diff --git a/tests/Server/TextDocument/Definition/GlobalFallbackTest.php b/tests/Server/TextDocument/Definition/GlobalFallbackTest.php index ea62f704..915b3d96 100644 --- a/tests/Server/TextDocument/Definition/GlobalFallbackTest.php +++ b/tests/Server/TextDocument/Definition/GlobalFallbackTest.php @@ -6,14 +6,14 @@ use LanguageServer\Tests\MockProtocolStream; use LanguageServer\Tests\Server\ServerTestCase; use LanguageServer\{Server, LanguageClient, Project}; -use LanguageServer\Protocol\{TextDocumentIdentifier, Position, Range, Location}; +use LanguageServer\Protocol\{TextDocumentIdentifier, Position, Range, Location, ClientCapabilities}; class GlobalFallbackTest extends ServerTestCase { public function setUp() { $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream); - $project = new Project($client); + $project = new Project($client, new ClientCapabilities); $this->textDocument = new Server\TextDocument($project, $client); $project->openDocument('global_fallback', file_get_contents(__DIR__ . '/../../../../fixtures/global_fallback.php')); $project->openDocument('global_symbols', file_get_contents(__DIR__ . '/../../../../fixtures/global_symbols.php')); diff --git a/tests/Server/TextDocument/DidChangeTest.php b/tests/Server/TextDocument/DidChangeTest.php index 0ee62ef2..1df05057 100644 --- a/tests/Server/TextDocument/DidChangeTest.php +++ b/tests/Server/TextDocument/DidChangeTest.php @@ -12,7 +12,8 @@ VersionedTextDocumentIdentifier, TextDocumentContentChangeEvent, Range, - Position + Position, + ClientCapabilities }; class DidChangeTest extends TestCase @@ -20,7 +21,7 @@ class DidChangeTest extends TestCase public function test() { $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream); - $project = new Project($client); + $project = new Project($client, new ClientCapabilities); $textDocument = new Server\TextDocument($project, $client); $phpDocument = $project->openDocument('whatever', "openDocument('whatever', 'hello world'); diff --git a/tests/Server/TextDocument/FormattingTest.php b/tests/Server/TextDocument/FormattingTest.php index ca6fc2c9..bae4721a 100644 --- a/tests/Server/TextDocument/FormattingTest.php +++ b/tests/Server/TextDocument/FormattingTest.php @@ -6,7 +6,7 @@ use PHPUnit\Framework\TestCase; use LanguageServer\Tests\MockProtocolStream; use LanguageServer\{Server, Client, LanguageClient, Project}; -use LanguageServer\Protocol\{TextDocumentIdentifier, TextDocumentItem, FormattingOptions}; +use LanguageServer\Protocol\{TextDocumentIdentifier, TextDocumentItem, FormattingOptions, ClientCapabilities}; use function LanguageServer\{pathToUri, uriToPath}; class FormattingTest extends TestCase @@ -19,14 +19,14 @@ class FormattingTest extends TestCase public function setUp() { $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream); - $project = new Project($client); + $project = new Project($client, new ClientCapabilities); $this->textDocument = new Server\TextDocument($project, $client); } public function testFormatting() { $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream); - $project = new Project($client); + $project = new Project($client, new ClientCapabilities); $textDocument = new Server\TextDocument($project, $client); $path = realpath(__DIR__ . '/../../../fixtures/format.php'); $uri = pathToUri($path); diff --git a/tests/Server/TextDocument/ParseErrorsTest.php b/tests/Server/TextDocument/ParseErrorsTest.php index 128d58f5..2a02efe3 100644 --- a/tests/Server/TextDocument/ParseErrorsTest.php +++ b/tests/Server/TextDocument/ParseErrorsTest.php @@ -6,8 +6,9 @@ use PHPUnit\Framework\TestCase; use LanguageServer\Tests\MockProtocolStream; use LanguageServer\{Server, Client, LanguageClient, Project, ClientHandler}; -use LanguageServer\Protocol\{TextDocumentIdentifier, TextDocumentItem, DiagnosticSeverity}; +use LanguageServer\Protocol\{TextDocumentIdentifier, TextDocumentItem, DiagnosticSeverity, ClientCapabilities}; use Sabre\Event\Promise; +use JsonMapper; class ParseErrorsTest extends TestCase { @@ -25,7 +26,7 @@ public function setUp() private $args; public function __construct(&$args) { - parent::__construct(new ClientHandler(new MockProtocolStream, new MockProtocolStream)); + parent::__construct(new ClientHandler(new MockProtocolStream, new MockProtocolStream), new JsonMapper); $this->args = &$args; } public function publishDiagnostics(string $uri, array $diagnostics): Promise @@ -34,7 +35,7 @@ public function publishDiagnostics(string $uri, array $diagnostics): Promise return Promise\resolve(null); } }; - $project = new Project($client); + $project = new Project($client, new ClientCapabilities); $this->textDocument = new Server\TextDocument($project, $client); } diff --git a/tests/Server/TextDocument/References/GlobalFallbackTest.php b/tests/Server/TextDocument/References/GlobalFallbackTest.php index 6862d979..0cde34af 100644 --- a/tests/Server/TextDocument/References/GlobalFallbackTest.php +++ b/tests/Server/TextDocument/References/GlobalFallbackTest.php @@ -6,7 +6,7 @@ use PHPUnit\Framework\TestCase; use LanguageServer\Tests\MockProtocolStream; use LanguageServer\{Server, LanguageClient, Project}; -use LanguageServer\Protocol\{TextDocumentIdentifier, Position, ReferenceContext, Location, Range}; +use LanguageServer\Protocol\{TextDocumentIdentifier, Position, ReferenceContext, Location, Range, ClientCapabilities}; use LanguageServer\Tests\Server\ServerTestCase; class GlobalFallbackTest extends ServerTestCase @@ -14,7 +14,7 @@ class GlobalFallbackTest extends ServerTestCase public function setUp() { $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream); - $project = new Project($client); + $project = new Project($client, new ClientCapabilities); $this->textDocument = new Server\TextDocument($project, $client); $project->openDocument('global_fallback', file_get_contents(__DIR__ . '/../../../../fixtures/global_fallback.php')); $project->openDocument('global_symbols', file_get_contents(__DIR__ . '/../../../../fixtures/global_symbols.php')); From 381fd4d59436f307db997191282db40f8b368b12 Mon Sep 17 00:00:00 2001 From: Felix Becker Date: Sun, 6 Nov 2016 15:19:19 +0100 Subject: [PATCH 08/24] Fix workspace/xglob method naming --- src/Client/Workspace.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client/Workspace.php b/src/Client/Workspace.php index 7d85f99f..d2bf2397 100644 --- a/src/Client/Workspace.php +++ b/src/Client/Workspace.php @@ -37,7 +37,7 @@ public function __construct(ClientHandler $handler, JsonMapper $mapper) */ public function xglob(string $pattern): Promise { - return $this->handler->request('workspace/_glob', ['pattern' => $pattern])->then(function ($textDocuments) { + return $this->handler->request('workspace/xglob', ['pattern' => $pattern])->then(function ($textDocuments) { return $this->mapper->mapArray($textDocuments, [], TextDocumentIdentifier::class); }); } From 9a13d641fd9f42dc323c67c2abe76a988678e40b Mon Sep 17 00:00:00 2001 From: Felix Becker Date: Sun, 6 Nov 2016 15:22:32 +0100 Subject: [PATCH 09/24] Remove Generator return value support --- src/LanguageServer.php | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/LanguageServer.php b/src/LanguageServer.php index 014ae34c..0aef291d 100644 --- a/src/LanguageServer.php +++ b/src/LanguageServer.php @@ -75,12 +75,7 @@ public function __construct(ProtocolReader $reader, ProtocolWriter $writer) $error = null; try { // Invoke the method handler to get a result - $result = $this->dispatch($msg->body); - if ($result instanceof Generator) { - $result = yield from $result; - } else if ($result instanceof Promise) { - $result = yield $result; - } + $result = yield $this->dispatch($msg->body); } catch (AdvancedJsonRpc\Error $e) { // If a ResponseError is thrown, send it back in the Response $error = $e; From 1080d63fcfab4a87e72336c82dd9c0828e42006e Mon Sep 17 00:00:00 2001 From: Felix Becker Date: Sun, 6 Nov 2016 18:36:14 +0100 Subject: [PATCH 10/24] Remove wait(), async everything --- src/LanguageServer.php | 30 ++- src/PhpDocument.php | 124 ++++++------ src/Project.php | 72 ++++--- src/Server/TextDocument.php | 189 ++++++++++-------- tests/NodeVisitor/DefinitionCollectorTest.php | 4 +- tests/ProjectTest.php | 4 +- tests/Server/ServerTestCase.php | 13 +- .../Definition/GlobalFallbackTest.php | 16 +- .../TextDocument/Definition/GlobalTest.php | 120 ++++++++--- .../Definition/NamespacedTest.php | 15 +- .../TextDocument/DocumentSymbolTest.php | 2 +- tests/Server/TextDocument/FormattingTest.php | 26 +-- tests/Server/TextDocument/HoverTest.php | 44 +++- .../References/GlobalFallbackTest.php | 18 +- .../TextDocument/References/GlobalTest.php | 60 +++++- 15 files changed, 473 insertions(+), 264 deletions(-) diff --git a/src/LanguageServer.php b/src/LanguageServer.php index 0aef291d..de006c19 100644 --- a/src/LanguageServer.php +++ b/src/LanguageServer.php @@ -167,7 +167,7 @@ public function exit() * * @return Promise */ - private function indexProject() + private function indexProject(): Promise { return coroutine(function () { $textDocuments = yield $this->globWorkspace('**/*.php'); @@ -175,16 +175,24 @@ private function indexProject() $startTime = microtime(true); - foreach ($textDocuments as $i => $textDocument) { - // Give LS to the chance to handle requests while indexing - yield timeout(); - $this->client->window->logMessage(MessageType::INFO, "Parsing file $i/$count: {$textDocument->uri}"); - try { - $this->project->loadDocument($textDocument->uri); - } catch (Exception $e) { - $this->client->window->logMessage(MessageType::ERROR, "Error parsing file $shortName: " . (string)$e); - } - } + yield Promise\all(array_map(function ($textDocument, $i) use ($count) { + return coroutine(function () use ($textDocument, $i, $count) { + // Give LS to the chance to handle requests while indexing + yield timeout(); + $this->client->window->logMessage( + MessageType::INFO, + "Parsing file $i/$count: {$textDocument->uri}" + ); + try { + yield $this->project->loadDocument($textDocument->uri); + } catch (Exception $e) { + $this->client->window->logMessage( + MessageType::ERROR, + "Error parsing file $shortName: " . (string)$e + ); + } + }); + }, $textDocuments, array_keys($textDocuments))); $duration = (int)(microtime(true) - $startTime); $mem = (int)(memory_get_usage(true) / (1024 * 1024)); diff --git a/src/PhpDocument.php b/src/PhpDocument.php index 50339593..75badcae 100644 --- a/src/PhpDocument.php +++ b/src/PhpDocument.php @@ -17,6 +17,8 @@ use PhpParser\NodeVisitor\NameResolver; use phpDocumentor\Reflection\DocBlockFactory; use function LanguageServer\Fqn\{getDefinedFqn, getVariableDefinition, getReferencedFqn}; +use Sabre\Event\Promise; +use function Sabre\Event\coroutine; class PhpDocument { @@ -314,34 +316,36 @@ public function isDefined(string $fqn): bool * The definition node MAY be in another document, check the ownerDocument attribute * * @param Node $node - * @return Node|null + * @return Promise */ - public function getDefinitionByNode(Node $node) + public function getDefinitionByNode(Node $node): Promise { - // Variables always stay in the boundary of the file and need to be searched inside their function scope - // by traversing the AST - if ($node instanceof Node\Expr\Variable) { - return getVariableDefinition($node); - } - $fqn = getReferencedFqn($node); - if (!isset($fqn)) { - return null; - } - $document = $this->project->getDefinitionDocument($fqn); - if (!isset($document)) { - // If the node is a function or constant, it could be namespaced, but PHP falls back to global - // http://php.net/manual/en/language.namespaces.fallback.php - $parent = $node->getAttribute('parentNode'); - if ($parent instanceof Node\Expr\ConstFetch || $parent instanceof Node\Expr\FuncCall) { - $parts = explode('\\', $fqn); - $fqn = end($parts); - $document = $this->project->getDefinitionDocument($fqn); + return coroutine(function () use ($node) { + // Variables always stay in the boundary of the file and need to be searched inside their function scope + // by traversing the AST + if ($node instanceof Node\Expr\Variable) { + return getVariableDefinition($node); } - } - if (!isset($document)) { - return null; - } - return $document->getDefinitionByFqn($fqn); + $fqn = getReferencedFqn($node); + if (!isset($fqn)) { + return null; + } + $document = yield $this->project->getDefinitionDocument($fqn); + if (!isset($document)) { + // If the node is a function or constant, it could be namespaced, but PHP falls back to global + // http://php.net/manual/en/language.namespaces.fallback.php + $parent = $node->getAttribute('parentNode'); + if ($parent instanceof Node\Expr\ConstFetch || $parent instanceof Node\Expr\FuncCall) { + $parts = explode('\\', $fqn); + $fqn = end($parts); + $document = yield $this->project->getDefinitionDocument($fqn); + } + } + if (!isset($document)) { + return null; + } + return $document->getDefinitionByFqn($fqn); + }); } /** @@ -349,45 +353,47 @@ public function getDefinitionByNode(Node $node) * The references node MAY be in other documents, check the ownerDocument attribute * * @param Node $node - * @return Node[] + * @return Promise */ - public function getReferencesByNode(Node $node) + public function getReferencesByNode(Node $node): Promise { - // Variables always stay in the boundary of the file and need to be searched inside their function scope - // by traversing the AST - if ($node instanceof Node\Expr\Variable || $node instanceof Node\Param) { - if ($node->name instanceof Node\Expr) { - return null; - } - // Find function/method/closure scope - $n = $node; - while (isset($n) && !($n instanceof Node\FunctionLike)) { - $n = $n->getAttribute('parentNode'); + return coroutine(function () use ($node) { + // Variables always stay in the boundary of the file and need to be searched inside their function scope + // by traversing the AST + if ($node instanceof Node\Expr\Variable || $node instanceof Node\Param) { + if ($node->name instanceof Node\Expr) { + return null; + } + // Find function/method/closure scope + $n = $node; + while (isset($n) && !($n instanceof Node\FunctionLike)) { + $n = $n->getAttribute('parentNode'); + } + if (!isset($n)) { + $n = $node->getAttribute('ownerDocument'); + } + $traverser = new NodeTraverser; + $refCollector = new VariableReferencesCollector($node->name); + $traverser->addVisitor($refCollector); + $traverser->traverse($n->getStmts()); + return $refCollector->references; } - if (!isset($n)) { - $n = $node->getAttribute('ownerDocument'); + // Definition with a global FQN + $fqn = getDefinedFqn($node); + if ($fqn === null) { + return []; } - $traverser = new NodeTraverser; - $refCollector = new VariableReferencesCollector($node->name); - $traverser->addVisitor($refCollector); - $traverser->traverse($n->getStmts()); - return $refCollector->references; - } - // Definition with a global FQN - $fqn = getDefinedFqn($node); - if ($fqn === null) { - return []; - } - $refDocuments = $this->project->getReferenceDocuments($fqn); - $nodes = []; - foreach ($refDocuments as $document) { - $refs = $document->getReferencesByFqn($fqn); - if ($refs !== null) { - foreach ($refs as $ref) { - $nodes[] = $ref; + $refDocuments = yield $this->project->getReferenceDocuments($fqn); + $nodes = []; + foreach ($refDocuments as $document) { + $refs = $document->getReferencesByFqn($fqn); + if ($refs !== null) { + foreach ($refs as $ref) { + $nodes[] = $ref; + } } } - } - return $nodes; + return $nodes; + }); } } diff --git a/src/Project.php b/src/Project.php index 361f7b0f..a6d17484 100644 --- a/src/Project.php +++ b/src/Project.php @@ -5,6 +5,8 @@ use LanguageServer\Protocol\{SymbolInformation, TextDocumentIdentifier, ClientCapabilities}; use phpDocumentor\Reflection\DocBlockFactory; +use Sabre\Event\Promise; +use function Sabre\Event\coroutine; class Project { @@ -68,18 +70,26 @@ public function __construct(LanguageClient $client, ClientCapabilities $clientCa /** * Returns the document indicated by uri. - * If the document is not open, tries to read it from disk, but the document is not added the list of open documents. + * Returns null if the document if not loaded. * * @param string $uri - * @return LanguageServer\PhpDocument + * @return PhpDocument|null */ public function getDocument(string $uri) { - if (!isset($this->documents[$uri])) { - return $this->loadDocument($uri); - } else { - return $this->documents[$uri]; - } + return $this->documents[$uri] ?? null; + } + + /** + * Returns the document indicated by uri. + * If the document is not open, loads it. + * + * @param string $uri + * @return Promise + */ + public function getOrLoadDocument(string $uri) + { + return isset($this->documents[$uri]) ? Promise\resolve($this->documents[$uri]) : $this->loadDocument($uri); } /** @@ -87,23 +97,24 @@ public function getDocument(string $uri) * The document is NOT added to the list of open documents, but definitions are registered. * * @param string $uri - * @return Promise + * @return Promise */ - public function loadDocument(string $uri) + public function loadDocument(string $uri): Promise { - if ($this->clientCapabilities->xcontentProvider) { - // TODO: make this whole method async instead of calling wait() - $content = $this->client->textDocument->xcontent(new TextDocumentIdentifier($uri))->wait()->text; - } else { - $content = file_get_contents(uriToPath($uri)); - } - if (isset($this->documents[$uri])) { - $document = $this->documents[$uri]; - $document->updateContent($content); - } else { - $document = new PhpDocument($uri, $content, $this, $this->client, $this->parser, $this->docBlockFactory); - } - return $document; + return coroutine(function () use ($uri) { + if ($this->clientCapabilities->xcontentProvider) { + $content = (yield $this->client->textDocument->xcontent(new TextDocumentIdentifier($uri)))->text; + } else { + $content = file_get_contents(uriToPath($uri)); + } + if (isset($this->documents[$uri])) { + $document = $this->documents[$uri]; + $document->updateContent($content); + } else { + $document = new PhpDocument($uri, $content, $this, $this->client, $this->parser, $this->docBlockFactory); + } + return $document; + }); } /** @@ -234,14 +245,14 @@ public function removeReferenceUri(string $fqn, string $uri) * Returns all documents that reference a symbol * * @param string $fqn The fully qualified name of the symbol - * @return PhpDocument[] + * @return Promise */ - public function getReferenceDocuments(string $fqn) + public function getReferenceDocuments(string $fqn): Promise { if (!isset($this->references[$fqn])) { - return []; + return Promise\resolve([]); } - return array_map([$this, 'getDocument'], $this->references[$fqn]); + return Promise\all(array_map([$this, 'getOrLoadDocument'], $this->references[$fqn])); } /** @@ -270,11 +281,14 @@ public function setReferenceUris(array $references) * Returns the document where a symbol is defined * * @param string $fqn The fully qualified name of the symbol - * @return PhpDocument|null + * @return Promise */ - public function getDefinitionDocument(string $fqn) + public function getDefinitionDocument(string $fqn): Promise { - return isset($this->symbols[$fqn]) ? $this->getDocument($this->symbols[$fqn]->location->uri) : null; + if (!isset($this->symbols[$fqn])) { + return Promise\resolve(null); + } + return $this->getOrLoadDocument($this->symbols[$fqn]->location->uri); } /** diff --git a/src/Server/TextDocument.php b/src/Server/TextDocument.php index 188e629c..1fc8f279 100644 --- a/src/Server/TextDocument.php +++ b/src/Server/TextDocument.php @@ -3,7 +3,7 @@ namespace LanguageServer\Server; -use LanguageServer\{LanguageClient, Project}; +use LanguageServer\{LanguageClient, Project, PhpDocument}; use PhpParser\PrettyPrinter\Standard as PrettyPrinter; use PhpParser\Node; use LanguageServer\Protocol\{ @@ -20,6 +20,8 @@ Hover, MarkedString }; +use Sabre\Event\Promise; +use function Sabre\Event\coroutine; /** * Provides method handlers for all textDocument/* methods @@ -55,11 +57,13 @@ public function __construct(Project $project, LanguageClient $client) * document. * * @param \LanguageServer\Protocol\TextDocumentIdentifier $textDocument - * @return SymbolInformation[] + * @return Promise */ - public function documentSymbol(TextDocumentIdentifier $textDocument): array + public function documentSymbol(TextDocumentIdentifier $textDocument): Promise { - return array_values($this->project->getDocument($textDocument->uri)->getSymbols()); + return $this->project->getOrLoadDocument($textDocument->uri)->then(function (PhpDocument $document) { + return array_values($document->getSymbols()); + }); } /** @@ -105,11 +109,13 @@ public function didClose(TextDocumentIdentifier $textDocument) * * @param TextDocumentIdentifier $textDocument The document to format * @param FormattingOptions $options The format options - * @return TextEdit[] + * @return Promise */ public function formatting(TextDocumentIdentifier $textDocument, FormattingOptions $options) { - return $this->project->getDocument($textDocument->uri)->getFormattedText(); + return $this->project->getOrLoadDocument($textDocument->uri)->then(function (PhpDocument $document) { + return $document->getFormattedText(); + }); } /** @@ -117,21 +123,26 @@ public function formatting(TextDocumentIdentifier $textDocument, FormattingOptio * denoted by the given text document position. * * @param ReferenceContext $context - * @return Location[] + * @return Promise */ - public function references(ReferenceContext $context, TextDocumentIdentifier $textDocument, Position $position): array - { - $document = $this->project->getDocument($textDocument->uri); - $node = $document->getNodeAtPosition($position); - if ($node === null) { - return []; - } - $refs = $document->getReferencesByNode($node); - $locations = []; - foreach ($refs as $ref) { - $locations[] = Location::fromNode($ref); - } - return $locations; + public function references( + ReferenceContext $context, + TextDocumentIdentifier $textDocument, + Position $position + ): Promise { + return coroutine(function () use ($textDocument, $position) { + $document = yield $this->project->getOrLoadDocument($textDocument->uri); + $node = $document->getNodeAtPosition($position); + if ($node === null) { + return []; + } + $refs = yield $document->getReferencesByNode($node); + $locations = []; + foreach ($refs as $ref) { + $locations[] = Location::fromNode($ref); + } + return $locations; + }); } /** @@ -140,20 +151,22 @@ public function references(ReferenceContext $context, TextDocumentIdentifier $te * * @param TextDocumentIdentifier $textDocument The text document * @param Position $position The position inside the text document - * @return Location|Location[] + * @return Promise */ - public function definition(TextDocumentIdentifier $textDocument, Position $position) + public function definition(TextDocumentIdentifier $textDocument, Position $position): Promise { - $document = $this->project->getDocument($textDocument->uri); - $node = $document->getNodeAtPosition($position); - if ($node === null) { - return []; - } - $def = $document->getDefinitionByNode($node); - if ($def === null) { - return []; - } - return Location::fromNode($def); + return coroutine(function () use ($textDocument, $position) { + $document = yield $this->project->getOrLoadDocument($textDocument->uri); + $node = $document->getNodeAtPosition($position); + if ($node === null) { + return []; + } + $def = yield $document->getDefinitionByNode($node); + if ($def === null) { + return []; + } + return Location::fromNode($def); + }); } /** @@ -161,66 +174,68 @@ public function definition(TextDocumentIdentifier $textDocument, Position $posit * * @param TextDocumentIdentifier $textDocument The text document * @param Position $position The position inside the text document - * @return Hover + * @return Promise */ - public function hover(TextDocumentIdentifier $textDocument, Position $position): Hover + public function hover(TextDocumentIdentifier $textDocument, Position $position): Promise { - $document = $this->project->getDocument($textDocument->uri); - // Find the node under the cursor - $node = $document->getNodeAtPosition($position); - if ($node === null) { - return new Hover([]); - } - $range = Range::fromNode($node); - // Get the definition node for whatever node is under the cursor - $def = $document->getDefinitionByNode($node); - if ($def === null) { - return new Hover([], $range); - } - $contents = []; - - // Build a declaration string - if ($def instanceof Node\Stmt\PropertyProperty || $def instanceof Node\Const_) { - // Properties and constants can have multiple declarations - // Use the parent node (that includes the modifiers), but only render the requested declaration - $child = $def; - $def = $def->getAttribute('parentNode'); - $defLine = clone $def; - $defLine->props = [$child]; - } else { - $defLine = clone $def; - } - // Don't include the docblock in the declaration string - $defLine->setAttribute('comments', []); - if (isset($defLine->stmts)) { - $defLine->stmts = []; - } - $defText = $this->prettyPrinter->prettyPrint([$defLine]); - $lines = explode("\n", $defText); - if (isset($lines[0])) { - $contents[] = new MarkedString('php', "getAttribute('parentNode'); - $docBlock = $fn->getAttribute('docBlock'); - if ($docBlock !== null) { - $tags = $docBlock->getTagsByName('param'); - foreach ($tags as $tag) { - if ($tag->getVariableName() === $def->name) { - $contents[] = $tag->getDescription()->render(); - break; + return coroutine(function () use ($textDocument, $position) { + $document = yield $this->project->getOrLoadDocument($textDocument->uri); + // Find the node under the cursor + $node = $document->getNodeAtPosition($position); + if ($node === null) { + return new Hover([]); + } + $range = Range::fromNode($node); + // Get the definition node for whatever node is under the cursor + $def = yield $document->getDefinitionByNode($node); + if ($def === null) { + return new Hover([], $range); + } + $contents = []; + + // Build a declaration string + if ($def instanceof Node\Stmt\PropertyProperty || $def instanceof Node\Const_) { + // Properties and constants can have multiple declarations + // Use the parent node (that includes the modifiers), but only render the requested declaration + $child = $def; + $def = $def->getAttribute('parentNode'); + $defLine = clone $def; + $defLine->props = [$child]; + } else { + $defLine = clone $def; + } + // Don't include the docblock in the declaration string + $defLine->setAttribute('comments', []); + if (isset($defLine->stmts)) { + $defLine->stmts = []; + } + $defText = $this->prettyPrinter->prettyPrint([$defLine]); + $lines = explode("\n", $defText); + if (isset($lines[0])) { + $contents[] = new MarkedString('php', "getAttribute('parentNode'); + $docBlock = $fn->getAttribute('docBlock'); + if ($docBlock !== null) { + $tags = $docBlock->getTagsByName('param'); + foreach ($tags as $tag) { + if ($tag->getVariableName() === $def->name) { + $contents[] = $tag->getDescription()->render(); + break; + } } } + } else { + $docBlock = $def->getAttribute('docBlock'); + if ($docBlock !== null) { + $contents[] = $docBlock->getSummary(); + } } - } else { - $docBlock = $def->getAttribute('docBlock'); - if ($docBlock !== null) { - $contents[] = $docBlock->getSummary(); - } - } - return new Hover($contents, $range); + return new Hover($contents, $range); + }); } } diff --git a/tests/NodeVisitor/DefinitionCollectorTest.php b/tests/NodeVisitor/DefinitionCollectorTest.php index 46741dd9..6df59404 100644 --- a/tests/NodeVisitor/DefinitionCollectorTest.php +++ b/tests/NodeVisitor/DefinitionCollectorTest.php @@ -20,7 +20,7 @@ public function testCollectsSymbols() $project = new Project($client, new ClientCapabilities); $parser = new Parser; $uri = pathToUri(realpath(__DIR__ . '/../../fixtures/symbols.php')); - $document = $project->loadDocument($uri); + $document = $project->loadDocument($uri)->wait(); $traverser = new NodeTraverser; $traverser->addVisitor(new NameResolver); $traverser->addVisitor(new ReferencesAdder($document)); @@ -59,7 +59,7 @@ public function testDoesNotCollectReferences() $project = new Project($client, new ClientCapabilities); $parser = new Parser; $uri = pathToUri(realpath(__DIR__ . '/../../fixtures/references.php')); - $document = $project->loadDocument($uri); + $document = $project->loadDocument($uri)->wait(); $traverser = new NodeTraverser; $traverser->addVisitor(new NameResolver); $traverser->addVisitor(new ReferencesAdder($document)); diff --git a/tests/ProjectTest.php b/tests/ProjectTest.php index b8721c5d..161370c8 100644 --- a/tests/ProjectTest.php +++ b/tests/ProjectTest.php @@ -30,9 +30,9 @@ public function setUp() $this->project = new Project($client, new ClientCapabilities); } - public function testGetDocumentLoadsDocument() + public function testGetOrLoadDocumentLoadsDocument() { - $document = $this->project->getDocument(pathToUri(__FILE__)); + $document = $this->project->getOrLoadDocument(pathToUri(__FILE__))->wait(); $this->assertNotNull($document); $this->assertInstanceOf(PhpDocument::class, $document); diff --git a/tests/Server/ServerTestCase.php b/tests/Server/ServerTestCase.php index 10ef6367..c5818a96 100644 --- a/tests/Server/ServerTestCase.php +++ b/tests/Server/ServerTestCase.php @@ -8,6 +8,7 @@ use LanguageServer\{Server, LanguageClient, Project}; use LanguageServer\Protocol\{Position, Location, Range, ClientCapabilities}; use function LanguageServer\pathToUri; +use Sabre\Event\Promise; abstract class ServerTestCase extends TestCase { @@ -53,11 +54,13 @@ public function setUp() $referencesUri = pathToUri(realpath(__DIR__ . '/../../fixtures/references.php')); $useUri = pathToUri(realpath(__DIR__ . '/../../fixtures/use.php')); - $this->project->loadDocument($symbolsUri); - $this->project->loadDocument($referencesUri); - $this->project->loadDocument($globalSymbolsUri); - $this->project->loadDocument($globalReferencesUri); - $this->project->loadDocument($useUri); + Promise\all([ + $this->project->loadDocument($symbolsUri), + $this->project->loadDocument($referencesUri), + $this->project->loadDocument($globalSymbolsUri), + $this->project->loadDocument($globalReferencesUri), + $this->project->loadDocument($useUri) + ])->wait(); // @codingStandardsIgnoreStart $this->definitionLocations = [ diff --git a/tests/Server/TextDocument/Definition/GlobalFallbackTest.php b/tests/Server/TextDocument/Definition/GlobalFallbackTest.php index 915b3d96..c4b021d9 100644 --- a/tests/Server/TextDocument/Definition/GlobalFallbackTest.php +++ b/tests/Server/TextDocument/Definition/GlobalFallbackTest.php @@ -7,6 +7,7 @@ use LanguageServer\Tests\Server\ServerTestCase; use LanguageServer\{Server, LanguageClient, Project}; use LanguageServer\Protocol\{TextDocumentIdentifier, Position, Range, Location, ClientCapabilities}; +use Sabre\Event\Promise; class GlobalFallbackTest extends ServerTestCase { @@ -23,7 +24,10 @@ public function testClassDoesNotFallback() { // $obj = new TestClass(); // Get definition for TestClass should not fall back to global - $result = $this->textDocument->definition(new TextDocumentIdentifier('global_fallback'), new Position(9, 16)); + $result = $this->textDocument->definition( + new TextDocumentIdentifier('global_fallback'), + new Position(9, 16) + )->wait(); $this->assertEquals([], $result); } @@ -31,7 +35,10 @@ public function testFallsBackForConstants() { // echo TEST_CONST; // Get definition for TEST_CONST - $result = $this->textDocument->definition(new TextDocumentIdentifier('global_fallback'), new Position(6, 10)); + $result = $this->textDocument->definition( + new TextDocumentIdentifier('global_fallback'), + new Position(6, 10) + )->wait(); $this->assertEquals(new Location('global_symbols', new Range(new Position(9, 6), new Position(9, 22))), $result); } @@ -39,7 +46,10 @@ public function testFallsBackForFunctions() { // test_function(); // Get definition for test_function - $result = $this->textDocument->definition(new TextDocumentIdentifier('global_fallback'), new Position(5, 6)); + $result = $this->textDocument->definition( + new TextDocumentIdentifier('global_fallback'), + new Position(5, 6) + )->wait(); $this->assertEquals(new Location('global_symbols', new Range(new Position(78, 0), new Position(81, 1))), $result); } } diff --git a/tests/Server/TextDocument/Definition/GlobalTest.php b/tests/Server/TextDocument/Definition/GlobalTest.php index ded6f382..bc5f6e6d 100644 --- a/tests/Server/TextDocument/Definition/GlobalTest.php +++ b/tests/Server/TextDocument/Definition/GlobalTest.php @@ -12,14 +12,20 @@ class GlobalTest extends ServerTestCase public function testDefinitionFileBeginning() { // |textDocument->definition(new TextDocumentIdentifier(pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'))), new Position(0, 0)); + $result = $this->textDocument->definition( + new TextDocumentIdentifier(pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'))), + new Position(0, 0) + )->wait(); $this->assertEquals([], $result); } public function testDefinitionEmptyResult() { // namespace keyword - $result = $this->textDocument->definition(new TextDocumentIdentifier(pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'))), new Position(2, 4)); + $result = $this->textDocument->definition( + new TextDocumentIdentifier(pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'))), + new Position(2, 4) + )->wait(); $this->assertEquals([], $result); } @@ -28,7 +34,10 @@ public function testDefinitionForClassLike() // $obj = new TestClass(); // Get definition for TestClass $reference = $this->getReferenceLocations('TestClass')[0]; - $result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->start); + $result = $this->textDocument->definition( + new TextDocumentIdentifier($reference->uri), + $reference->range->start + )->wait(); $this->assertEquals($this->getDefinitionLocation('TestClass'), $result); } @@ -37,7 +46,10 @@ public function testDefinitionForClassOnStaticMethodCall() // TestClass::staticTestMethod(); // Get definition for TestClass $reference = $this->getReferenceLocations('TestClass')[1]; - $result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->start); + $result = $this->textDocument->definition( + new TextDocumentIdentifier($reference->uri), + $reference->range->start + )->wait(); $this->assertEquals($this->getDefinitionLocation('TestClass'), $result); } @@ -46,7 +58,10 @@ public function testDefinitionForClassOnStaticPropertyFetch() // echo TestClass::$staticTestProperty; // Get definition for TestClass $reference = $this->getReferenceLocations('TestClass')[2]; - $result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->start); + $result = $this->textDocument->definition( + new TextDocumentIdentifier($reference->uri), + $reference->range->start + )->wait(); $this->assertEquals($this->getDefinitionLocation('TestClass'), $result); } @@ -55,7 +70,10 @@ public function testDefinitionForClassOnConstFetch() // TestClass::TEST_CLASS_CONST; // Get definition for TestClass $reference = $this->getReferenceLocations('TestClass')[3]; - $result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->start); + $result = $this->textDocument->definition( + new TextDocumentIdentifier($reference->uri), + $reference->range->start + )->wait(); $this->assertEquals($this->getDefinitionLocation('TestClass'), $result); } @@ -64,7 +82,10 @@ public function testDefinitionForImplements() // class TestClass implements TestInterface // Get definition for TestInterface $reference = $this->getReferenceLocations('TestInterface')[0]; - $result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->start); + $result = $this->textDocument->definition( + new TextDocumentIdentifier($reference->uri), + $reference->range->start + )->wait(); $this->assertEquals($this->getDefinitionLocation('TestInterface'), $result); } @@ -73,7 +94,10 @@ public function testDefinitionForClassConstants() // echo TestClass::TEST_CLASS_CONST; // Get definition for TEST_CLASS_CONST $reference = $this->getReferenceLocations('TestClass::TEST_CLASS_CONST')[1]; - $result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->end); + $result = $this->textDocument->definition( + new TextDocumentIdentifier($reference->uri), + $reference->range->end + )->wait(); $this->assertEquals($this->getDefinitionLocation('TestClass::TEST_CLASS_CONST'), $result); } @@ -82,7 +106,10 @@ public function testDefinitionForClassConstantsOnSelf() // echo self::TEST_CLASS_CONST; // Get definition for TEST_CLASS_CONST $reference = $this->getReferenceLocations('TestClass::TEST_CLASS_CONST')[0]; - $result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->end); + $result = $this->textDocument->definition( + new TextDocumentIdentifier($reference->uri), + $reference->range->end + )->wait(); $this->assertEquals($this->getDefinitionLocation('TestClass::TEST_CLASS_CONST'), $result); } @@ -91,7 +118,10 @@ public function testDefinitionForConstants() // echo TEST_CONST; // Get definition for TEST_CONST $reference = $this->getReferenceLocations('TEST_CONST')[1]; - $result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->start); + $result = $this->textDocument->definition( + new TextDocumentIdentifier($reference->uri), + $reference->range->start + )->wait(); $this->assertEquals($this->getDefinitionLocation('TEST_CONST'), $result); } @@ -100,7 +130,10 @@ public function testDefinitionForStaticMethods() // TestClass::staticTestMethod(); // Get definition for staticTestMethod $reference = $this->getReferenceLocations('TestClass::staticTestMethod()')[0]; - $result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->end); + $result = $this->textDocument->definition( + new TextDocumentIdentifier($reference->uri), + $reference->range->end + )->wait(); $this->assertEquals($this->getDefinitionLocation('TestClass::staticTestMethod()'), $result); } @@ -109,7 +142,10 @@ public function testDefinitionForStaticProperties() // echo TestClass::$staticTestProperty; // Get definition for staticTestProperty $reference = $this->getReferenceLocations('TestClass::staticTestProperty')[0]; - $result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->end); + $result = $this->textDocument->definition( + new TextDocumentIdentifier($reference->uri), + $reference->range->end + )->wait(); $this->assertEquals($this->getDefinitionLocation('TestClass::staticTestProperty'), $result); } @@ -118,7 +154,10 @@ public function testDefinitionForMethods() // $obj->testMethod(); // Get definition for testMethod $reference = $this->getReferenceLocations('TestClass::testMethod()')[0]; - $result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->end); + $result = $this->textDocument->definition( + new TextDocumentIdentifier($reference->uri), + $reference->range->end + )->wait(); $this->assertEquals($this->getDefinitionLocation('TestClass::testMethod()'), $result); } @@ -127,7 +166,10 @@ public function testDefinitionForProperties() // echo $obj->testProperty; // Get definition for testProperty $reference = $this->getReferenceLocations('TestClass::testProperty')[1]; - $result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->end); + $result = $this->textDocument->definition( + new TextDocumentIdentifier($reference->uri), + $reference->range->end + )->wait(); $this->assertEquals($this->getDefinitionLocation('TestClass::testProperty'), $result); } @@ -136,7 +178,10 @@ public function testDefinitionForPropertiesOnThis() // $this->testProperty = $testParameter; // Get definition for testProperty $reference = $this->getReferenceLocations('TestClass::testProperty')[0]; - $result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->end); + $result = $this->textDocument->definition( + new TextDocumentIdentifier($reference->uri), + $reference->range->end + )->wait(); $this->assertEquals($this->getDefinitionLocation('TestClass::testProperty'), $result); } @@ -145,7 +190,10 @@ public function testDefinitionForVariables() // echo $var; // Get definition for $var $uri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php')); - $result = $this->textDocument->definition(new TextDocumentIdentifier($uri), new Position(13, 7)); + $result = $this->textDocument->definition( + new TextDocumentIdentifier($uri), + new Position(13, 7) + )->wait(); $this->assertEquals(new Location($uri, new Range(new Position(12, 0), new Position(12, 10))), $result); } @@ -154,7 +202,10 @@ public function testDefinitionForParamTypeHints() // function whatever(TestClass $param) { // Get definition for TestClass $reference = $this->getReferenceLocations('TestClass')[4]; - $result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->start); + $result = $this->textDocument->definition( + new TextDocumentIdentifier($reference->uri), + $reference->range->start + )->wait(); $this->assertEquals($this->getDefinitionLocation('TestClass'), $result); } @@ -163,7 +214,10 @@ public function testDefinitionForReturnTypeHints() // function whatever(TestClass $param): TestClass { // Get definition for TestClass $reference = $this->getReferenceLocations('TestClass')[5]; - $result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->start); + $result = $this->textDocument->definition( + new TextDocumentIdentifier($reference->uri), + $reference->range->start + )->wait(); $this->assertEquals($this->getDefinitionLocation('TestClass'), $result); } @@ -172,7 +226,10 @@ public function testDefinitionForMethodReturnTypeHints() // public function testMethod($testParameter): TestInterface // Get definition for TestInterface $reference = $this->getReferenceLocations('TestInterface')[1]; - $result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->start); + $result = $this->textDocument->definition( + new TextDocumentIdentifier($reference->uri), + $reference->range->start + )->wait(); $this->assertEquals($this->getDefinitionLocation('TestInterface'), $result); } @@ -181,7 +238,10 @@ public function testDefinitionForParams() // echo $param; // Get definition for $param $uri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php')); - $result = $this->textDocument->definition(new TextDocumentIdentifier($uri), new Position(22, 13)); + $result = $this->textDocument->definition( + new TextDocumentIdentifier($uri), + new Position(22, 13) + )->wait(); $this->assertEquals(new Location($uri, new Range(new Position(21, 18), new Position(21, 34))), $result); } @@ -190,7 +250,10 @@ public function testDefinitionForUsedVariables() // echo $var; // Get definition for $var $uri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php')); - $result = $this->textDocument->definition(new TextDocumentIdentifier($uri), new Position(26, 11)); + $result = $this->textDocument->definition( + new TextDocumentIdentifier($uri), + new Position(26, 11) + )->wait(); $this->assertEquals(new Location($uri, new Range(new Position(25, 22), new Position(25, 26))), $result); } @@ -199,7 +262,10 @@ public function testDefinitionForFunctions() // test_function(); // Get definition for test_function $reference = $this->getReferenceLocations('test_function()')[0]; - $result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->start); + $result = $this->textDocument->definition( + new TextDocumentIdentifier($reference->uri), + $reference->range->start + )->wait(); $this->assertEquals($this->getDefinitionLocation('test_function()'), $result); } @@ -208,7 +274,10 @@ public function testDefinitionForUseFunctions() // use function test_function; // Get definition for test_function $reference = $this->getReferenceLocations('test_function()')[1]; - $result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->start); + $result = $this->textDocument->definition( + new TextDocumentIdentifier($reference->uri), + $reference->range->start + )->wait(); $this->assertEquals($this->getDefinitionLocation('test_function()'), $result); } @@ -217,7 +286,10 @@ public function testDefinitionForInstanceOf() // if ($abc instanceof TestInterface) { // Get definition for TestInterface $reference = $this->getReferenceLocations('TestInterface')[2]; - $result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->start); + $result = $this->textDocument->definition( + new TextDocumentIdentifier($reference->uri), + $reference->range->start + )->wait(); $this->assertEquals($this->getDefinitionLocation('TestInterface'), $result); } } diff --git a/tests/Server/TextDocument/Definition/NamespacedTest.php b/tests/Server/TextDocument/Definition/NamespacedTest.php index e796ef92..ef66624b 100644 --- a/tests/Server/TextDocument/Definition/NamespacedTest.php +++ b/tests/Server/TextDocument/Definition/NamespacedTest.php @@ -23,7 +23,10 @@ public function testDefinitionForConstants() // echo TEST_CONST; // Get definition for TEST_CONST $reference = $this->getReferenceLocations('TEST_CONST')[0]; - $result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->start); + $result = $this->textDocument->definition( + new TextDocumentIdentifier($reference->uri), + $reference->range->start + )->wait(); $this->assertEquals($this->getDefinitionLocation('TEST_CONST'), $result); } @@ -32,7 +35,10 @@ public function testDefinitionForClassLikeUseStatement() // use TestNamespace\TestClass; // Get definition for TestClass $reference = $this->getReferenceLocations('TestClass')[6]; - $result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->start); + $result = $this->textDocument->definition( + new TextDocumentIdentifier($reference->uri), + $reference->range->start + )->wait(); $this->assertEquals($this->getDefinitionLocation('TestClass'), $result); } @@ -41,7 +47,10 @@ public function testDefinitionForClassLikeGroupUseStatement() // use TestNamespace\{TestTrait, TestInterface}; // Get definition for TestInterface $reference = $this->getReferenceLocations('TestClass')[0]; - $result = $this->textDocument->definition(new TextDocumentIdentifier($reference->uri), $reference->range->start); + $result = $this->textDocument->definition( + new TextDocumentIdentifier($reference->uri), + $reference->range->start + )->wait(); $this->assertEquals($this->getDefinitionLocation('TestClass'), $result); } } diff --git a/tests/Server/TextDocument/DocumentSymbolTest.php b/tests/Server/TextDocument/DocumentSymbolTest.php index 7b304d0d..4f09e20d 100644 --- a/tests/Server/TextDocument/DocumentSymbolTest.php +++ b/tests/Server/TextDocument/DocumentSymbolTest.php @@ -15,7 +15,7 @@ public function test() { // Request symbols $uri = pathToUri(realpath(__DIR__ . '/../../../fixtures/symbols.php')); - $result = $this->textDocument->documentSymbol(new TextDocumentIdentifier($uri)); + $result = $this->textDocument->documentSymbol(new TextDocumentIdentifier($uri))->wait(); // @codingStandardsIgnoreStart $this->assertEquals([ new SymbolInformation('TEST_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestNamespace\\TEST_CONST'), 'TestNamespace'), diff --git a/tests/Server/TextDocument/FormattingTest.php b/tests/Server/TextDocument/FormattingTest.php index bae4721a..b7d06093 100644 --- a/tests/Server/TextDocument/FormattingTest.php +++ b/tests/Server/TextDocument/FormattingTest.php @@ -6,7 +6,15 @@ use PHPUnit\Framework\TestCase; use LanguageServer\Tests\MockProtocolStream; use LanguageServer\{Server, Client, LanguageClient, Project}; -use LanguageServer\Protocol\{TextDocumentIdentifier, TextDocumentItem, FormattingOptions, ClientCapabilities}; +use LanguageServer\Protocol\{ + TextDocumentIdentifier, + TextDocumentItem, + FormattingOptions, + ClientCapabilities, + TextEdit, + Range, + Position +}; use function LanguageServer\{pathToUri, uriToPath}; class FormattingTest extends TestCase @@ -42,19 +50,7 @@ public function testFormatting() // how code should look after formatting $expected = file_get_contents(__DIR__ . '/../../../fixtures/format_expected.php'); // Request formatting - $result = $textDocument->formatting(new TextDocumentIdentifier($uri), new FormattingOptions()); - $this->assertEquals([0 => [ - 'range' => [ - 'start' => [ - 'line' => 0, - 'character' => 0 - ], - 'end' => [ - 'line' => 20, - 'character' => 0 - ] - ], - 'newText' => $expected - ]], json_decode(json_encode($result), true)); + $result = $textDocument->formatting(new TextDocumentIdentifier($uri), new FormattingOptions())->wait(); + $this->assertEquals([new TextEdit(new Range(new Position(0, 0), new Position(20, 0)), $expected)], $result); } } diff --git a/tests/Server/TextDocument/HoverTest.php b/tests/Server/TextDocument/HoverTest.php index 20d59d09..80cef64d 100644 --- a/tests/Server/TextDocument/HoverTest.php +++ b/tests/Server/TextDocument/HoverTest.php @@ -16,7 +16,10 @@ public function testHoverForClassLike() // $obj = new TestClass(); // Get hover for TestClass $reference = $this->getReferenceLocations('TestClass')[0]; - $result = $this->textDocument->hover(new TextDocumentIdentifier($reference->uri), $reference->range->start); + $result = $this->textDocument->hover( + new TextDocumentIdentifier($reference->uri), + $reference->range->start + )->wait(); $this->assertEquals(new Hover([ new MarkedString('php', "testMethod(); // Get hover for testMethod $reference = $this->getReferenceLocations('TestClass::testMethod()')[0]; - $result = $this->textDocument->hover(new TextDocumentIdentifier($reference->uri), $reference->range->end); + $result = $this->textDocument->hover( + new TextDocumentIdentifier($reference->uri), + $reference->range->end + )->wait(); $this->assertEquals(new Hover([ new MarkedString('php', "testProperty; // Get hover for testProperty $reference = $this->getReferenceLocations('TestClass::testProperty')[0]; - $result = $this->textDocument->hover(new TextDocumentIdentifier($reference->uri), $reference->range->end); + $result = $this->textDocument->hover( + new TextDocumentIdentifier($reference->uri), + $reference->range->end + )->wait(); $this->assertEquals(new Hover([ new MarkedString('php', "getReferenceLocations('TestClass::staticTestMethod()')[0]; - $result = $this->textDocument->hover(new TextDocumentIdentifier($reference->uri), $reference->range->end); + $result = $this->textDocument->hover( + new TextDocumentIdentifier($reference->uri), + $reference->range->end + )->wait(); $this->assertEquals(new Hover([ new MarkedString('php', "getReferenceLocations('TestClass::staticTestProperty')[0]; - $result = $this->textDocument->hover(new TextDocumentIdentifier($reference->uri), $reference->range->end); + $result = $this->textDocument->hover( + new TextDocumentIdentifier($reference->uri), + $reference->range->end + )->wait(); $this->assertEquals(new Hover([ new MarkedString('php', "getReferenceLocations('TestClass::TEST_CLASS_CONST')[0]; - $result = $this->textDocument->hover(new TextDocumentIdentifier($reference->uri), $reference->range->end); + $result = $this->textDocument->hover( + new TextDocumentIdentifier($reference->uri), + $reference->range->end + )->wait(); $this->assertEquals(new Hover([ new MarkedString('php', "getReferenceLocations('test_function()')[0]; - $result = $this->textDocument->hover(new TextDocumentIdentifier($reference->uri), $reference->range->end); + $result = $this->textDocument->hover( + new TextDocumentIdentifier($reference->uri), + $reference->range->end + )->wait(); $this->assertEquals(new Hover([ new MarkedString('php', "getReferenceLocations('TEST_CONST')[0]; - $result = $this->textDocument->hover(new TextDocumentIdentifier($reference->uri), $reference->range->end); + $result = $this->textDocument->hover( + new TextDocumentIdentifier($reference->uri), + $reference->range->end + )->wait(); $this->assertEquals(new Hover([ new MarkedString('php', "textDocument->hover(new TextDocumentIdentifier($uri), new Position(13, 7)); + $result = $this->textDocument->hover(new TextDocumentIdentifier($uri), new Position(13, 7))->wait(); $this->assertEquals(new Hover( [new MarkedString('php', "textDocument->hover(new TextDocumentIdentifier($uri), new Position(22, 11)); + $result = $this->textDocument->hover(new TextDocumentIdentifier($uri), new Position(22, 11))->wait(); $this->assertEquals(new Hover( [ new MarkedString('php', "textDocument->references(new ReferenceContext, new TextDocumentIdentifier('global_symbols'), new Position(6, 9)); + $result = $this->textDocument->references( + new ReferenceContext, + new TextDocumentIdentifier('global_symbols'), + new Position(6, 9) + )->wait(); $this->assertEquals([], $result); } @@ -32,7 +36,11 @@ public function testFallsBackForConstants() { // const TEST_CONST = 123; // Get references for TEST_CONST - $result = $this->textDocument->references(new ReferenceContext, new TextDocumentIdentifier('global_symbols'), new Position(9, 13)); + $result = $this->textDocument->references( + new ReferenceContext, + new TextDocumentIdentifier('global_symbols'), + new Position(9, 13) + )->wait(); $this->assertEquals([new Location('global_fallback', new Range(new Position(6, 5), new Position(6, 15)))], $result); } @@ -40,7 +48,11 @@ public function testFallsBackForFunctions() { // function test_function() // Get references for test_function - $result = $this->textDocument->references(new ReferenceContext, new TextDocumentIdentifier('global_symbols'), new Position(78, 16)); + $result = $this->textDocument->references( + new ReferenceContext, + new TextDocumentIdentifier('global_symbols'), + new Position(78, 16) + )->wait(); $this->assertEquals([new Location('global_fallback', new Range(new Position(5, 0), new Position(5, 13)))], $result); } } diff --git a/tests/Server/TextDocument/References/GlobalTest.php b/tests/Server/TextDocument/References/GlobalTest.php index 1ecf1f9f..bf6eb977 100644 --- a/tests/Server/TextDocument/References/GlobalTest.php +++ b/tests/Server/TextDocument/References/GlobalTest.php @@ -14,7 +14,11 @@ public function testReferencesForClassLike() // class TestClass implements TestInterface // Get references for TestClass $definition = $this->getDefinitionLocation('TestClass'); - $result = $this->textDocument->references(new ReferenceContext, new TextDocumentIdentifier($definition->uri), $definition->range->start); + $result = $this->textDocument->references( + new ReferenceContext, + new TextDocumentIdentifier($definition->uri), + $definition->range->start + )->wait(); $this->assertEquals($this->getReferenceLocations('TestClass'), $result); } @@ -23,7 +27,11 @@ public function testReferencesForClassConstants() // const TEST_CLASS_CONST = 123; // Get references for TEST_CLASS_CONST $definition = $this->getDefinitionLocation('TestClass::TEST_CLASS_CONST'); - $result = $this->textDocument->references(new ReferenceContext, new TextDocumentIdentifier($definition->uri), $definition->range->start); + $result = $this->textDocument->references( + new ReferenceContext, + new TextDocumentIdentifier($definition->uri), + $definition->range->start + )->wait(); $this->assertEquals($this->getReferenceLocations('TestClass::TEST_CLASS_CONST'), $result); } @@ -32,7 +40,11 @@ public function testReferencesForConstants() // const TEST_CONST = 123; // Get references for TEST_CONST $definition = $this->getDefinitionLocation('TEST_CONST'); - $result = $this->textDocument->references(new ReferenceContext, new TextDocumentIdentifier($definition->uri), $definition->range->start); + $result = $this->textDocument->references( + new ReferenceContext, + new TextDocumentIdentifier($definition->uri), + $definition->range->start + )->wait(); $this->assertEquals($this->getReferenceLocations('TEST_CONST'), $result); } @@ -41,7 +53,11 @@ public function testReferencesForStaticMethods() // public static function staticTestMethod() // Get references for staticTestMethod $definition = $this->getDefinitionLocation('TestClass::staticTestMethod()'); - $result = $this->textDocument->references(new ReferenceContext, new TextDocumentIdentifier($definition->uri), $definition->range->start); + $result = $this->textDocument->references( + new ReferenceContext, + new TextDocumentIdentifier($definition->uri), + $definition->range->start + )->wait(); $this->assertEquals($this->getReferenceLocations('TestClass::staticTestMethod()'), $result); } @@ -50,7 +66,11 @@ public function testReferencesForStaticProperties() // public static $staticTestProperty; // Get references for $staticTestProperty $definition = $this->getDefinitionLocation('TestClass::staticTestProperty'); - $result = $this->textDocument->references(new ReferenceContext, new TextDocumentIdentifier($definition->uri), $definition->range->start); + $result = $this->textDocument->references( + new ReferenceContext, + new TextDocumentIdentifier($definition->uri), + $definition->range->start + )->wait(); $this->assertEquals($this->getReferenceLocations('TestClass::staticTestProperty'), $result); } @@ -59,7 +79,11 @@ public function testReferencesForMethods() // public function testMethod($testParameter) // Get references for testMethod $definition = $this->getDefinitionLocation('TestClass::testMethod()'); - $result = $this->textDocument->references(new ReferenceContext, new TextDocumentIdentifier($definition->uri), $definition->range->start); + $result = $this->textDocument->references( + new ReferenceContext, + new TextDocumentIdentifier($definition->uri), + $definition->range->start + )->wait(); $this->assertEquals($this->getReferenceLocations('TestClass::testMethod()'), $result); } @@ -68,7 +92,11 @@ public function testReferencesForProperties() // public $testProperty; // Get references for testProperty $definition = $this->getDefinitionLocation('TestClass::testProperty'); - $result = $this->textDocument->references(new ReferenceContext, new TextDocumentIdentifier($definition->uri), $definition->range->start); + $result = $this->textDocument->references( + new ReferenceContext, + new TextDocumentIdentifier($definition->uri), + $definition->range->start + )->wait(); $this->assertEquals($this->getReferenceLocations('TestClass::testProperty'), $result); } @@ -77,7 +105,11 @@ public function testReferencesForVariables() // $var = 123; // Get definition for $var $uri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php')); - $result = $this->textDocument->references(new ReferenceContext, new TextDocumentIdentifier($uri), new Position(12, 3)); + $result = $this->textDocument->references( + new ReferenceContext, + new TextDocumentIdentifier($uri), + new Position(12, 3) + )->wait(); $this->assertEquals([ new Location($uri, new Range(new Position(12, 0), new Position(12, 4))), new Location($uri, new Range(new Position(13, 5), new Position(13, 9))), @@ -90,7 +122,11 @@ public function testReferencesForFunctionParams() // function whatever(TestClass $param): TestClass // Get references for $param $uri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php')); - $result = $this->textDocument->references(new ReferenceContext, new TextDocumentIdentifier($uri), new Position(21, 32)); + $result = $this->textDocument->references( + new ReferenceContext, + new TextDocumentIdentifier($uri), + new Position(21, 32) + )->wait(); $this->assertEquals([new Location($uri, new Range(new Position(22, 9), new Position(22, 15)))], $result); } @@ -100,7 +136,11 @@ public function testReferencesForFunctions() // Get references for test_function $referencesUri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php')); $symbolsUri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/symbols.php')); - $result = $this->textDocument->references(new ReferenceContext, new TextDocumentIdentifier($symbolsUri), new Position(78, 16)); + $result = $this->textDocument->references( + new ReferenceContext, + new TextDocumentIdentifier($symbolsUri), + new Position(78, 16) + )->wait(); $this->assertEquals([ new Location($referencesUri, new Range(new Position(10, 0), new Position(10, 13))), new Location($referencesUri, new Range(new Position(31, 13), new Position(31, 40))) From fdb3d4bb6f5aa5cebaf52c4e4765999401fdbf47 Mon Sep 17 00:00:00 2001 From: Felix Becker Date: Sun, 6 Nov 2016 18:41:42 +0100 Subject: [PATCH 11/24] Revert change in logging --- src/LanguageServer.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/LanguageServer.php b/src/LanguageServer.php index de006c19..085302bd 100644 --- a/src/LanguageServer.php +++ b/src/LanguageServer.php @@ -81,7 +81,12 @@ public function __construct(ProtocolReader $reader, ProtocolWriter $writer) $error = $e; } catch (Throwable $e) { // If an unexpected error occured, send back an INTERNAL_ERROR error response - $error = new AdvancedJsonRpc\Error((string)$e, AdvancedJsonRpc\ErrorCode::INTERNAL_ERROR, null, $e); + $error = new AdvancedJsonRpc\Error( + $e->getMessage(), + AdvancedJsonRpc\ErrorCode::INTERNAL_ERROR, + null, + $e + ); } // Only send a Response for a Request // Notifications do not send Responses From 6f6f6f3f4592c0ee830ad52bba38641a0b0ad563 Mon Sep 17 00:00:00 2001 From: Felix Becker Date: Sun, 6 Nov 2016 18:42:01 +0100 Subject: [PATCH 12/24] Remove unneded line --- src/LanguageServer.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/LanguageServer.php b/src/LanguageServer.php index 085302bd..bf2d4c4d 100644 --- a/src/LanguageServer.php +++ b/src/LanguageServer.php @@ -114,7 +114,6 @@ public function __construct(ProtocolReader $reader, ProtocolWriter $writer) */ public function initialize(int $processId, ClientCapabilities $capabilities, string $rootPath = null): InitializeResult { - $this->rootPath = $rootPath; $this->clientCapabilities = $capabilities; $this->project = new Project($this->client, $capabilities); From 545e1fdc2d57bd0513a617f31f7086ce353fc1ff Mon Sep 17 00:00:00 2001 From: Felix Becker Date: Sun, 6 Nov 2016 18:45:02 +0100 Subject: [PATCH 13/24] Improve docblock --- src/Project.php | 3 ++- src/Protocol/TextDocumentContentResult.php | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Project.php b/src/Project.php index a6d17484..e6c1dbd2 100644 --- a/src/Project.php +++ b/src/Project.php @@ -93,7 +93,8 @@ public function getOrLoadDocument(string $uri) } /** - * Reads a document from disk. + * Loads the document by doing a textDocument/xcontent request to the client. + * If the client does not support textDocument/xcontent, tries to read the file from the file system. * The document is NOT added to the list of open documents, but definitions are registered. * * @param string $uri diff --git a/src/Protocol/TextDocumentContentResult.php b/src/Protocol/TextDocumentContentResult.php index 758de556..10b98ada 100644 --- a/src/Protocol/TextDocumentContentResult.php +++ b/src/Protocol/TextDocumentContentResult.php @@ -5,6 +5,8 @@ class TextDocumentContentResult { /** + * The content of the text document + * * @var string */ public $text; From cf432fc1d551afb5ac78054325073e9da47a5c6f Mon Sep 17 00:00:00 2001 From: Felix Becker Date: Sun, 6 Nov 2016 18:48:34 +0100 Subject: [PATCH 14/24] Remove findFilesRecursive fixtures --- fixtures/recursive/a.txt | 1 - fixtures/recursive/search/b.txt | 1 - fixtures/recursive/search/here/c.txt | 1 - 3 files changed, 3 deletions(-) delete mode 100644 fixtures/recursive/a.txt delete mode 100644 fixtures/recursive/search/b.txt delete mode 100644 fixtures/recursive/search/here/c.txt diff --git a/fixtures/recursive/a.txt b/fixtures/recursive/a.txt deleted file mode 100644 index 8c7e5a66..00000000 --- a/fixtures/recursive/a.txt +++ /dev/null @@ -1 +0,0 @@ -A \ No newline at end of file diff --git a/fixtures/recursive/search/b.txt b/fixtures/recursive/search/b.txt deleted file mode 100644 index 7371f47a..00000000 --- a/fixtures/recursive/search/b.txt +++ /dev/null @@ -1 +0,0 @@ -B \ No newline at end of file diff --git a/fixtures/recursive/search/here/c.txt b/fixtures/recursive/search/here/c.txt deleted file mode 100644 index d5274b3d..00000000 --- a/fixtures/recursive/search/here/c.txt +++ /dev/null @@ -1 +0,0 @@ -Peeakboo! \ No newline at end of file From bb9750069c1761034ff1d10f233b590c09142ab8 Mon Sep 17 00:00:00 2001 From: Felix Becker Date: Sun, 6 Nov 2016 18:54:01 +0100 Subject: [PATCH 15/24] Remove use statement --- src/LanguageServer.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/LanguageServer.php b/src/LanguageServer.php index bf2d4c4d..a4e88fb5 100644 --- a/src/LanguageServer.php +++ b/src/LanguageServer.php @@ -19,7 +19,6 @@ use Exception; use RuntimeException; use Throwable; -use Generator; use Webmozart\Glob\Iterator\GlobIterator; use Webmozart\PathUtil\Path; From 232f5c329d46e0ac60bccacf665221d0ae35e5bf Mon Sep 17 00:00:00 2001 From: Felix Becker Date: Sun, 6 Nov 2016 23:27:22 +0100 Subject: [PATCH 16/24] Integrate latest proposal changes --- src/Client/TextDocument.php | 6 +++--- src/Protocol/{TextDocumentContentResult.php => Content.php} | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) rename src/Protocol/{TextDocumentContentResult.php => Content.php} (82%) diff --git a/src/Client/TextDocument.php b/src/Client/TextDocument.php index d96f7c11..a7759c89 100644 --- a/src/Client/TextDocument.php +++ b/src/Client/TextDocument.php @@ -4,7 +4,7 @@ namespace LanguageServer\Client; use LanguageServer\ClientHandler; -use LanguageServer\Protocol\Message; +use LanguageServer\Protocol\{Message, Content}; use Sabre\Event\Promise; use JsonMapper; @@ -49,7 +49,7 @@ public function publishDiagnostics(string $uri, array $diagnostics): Promise * to request the current content of a text document identified by the URI * * @param TextDocumentIdentifier $textDocument The document to get the content for - * @return Promise The document's current content + * @return Promise The document's current content */ public function xcontent(TextDocumentIdentifier $textDocument): Promise { @@ -57,7 +57,7 @@ public function xcontent(TextDocumentIdentifier $textDocument): Promise 'textDocument/xcontent', ['textDocument' => $textDocument] )->then(function ($result) { - return $this->mapper->map($result, new TextDocumentContentResult); + return $this->mapper->map($result, new Content); }); } } diff --git a/src/Protocol/TextDocumentContentResult.php b/src/Protocol/Content.php similarity index 82% rename from src/Protocol/TextDocumentContentResult.php rename to src/Protocol/Content.php index 10b98ada..6f95a8a3 100644 --- a/src/Protocol/TextDocumentContentResult.php +++ b/src/Protocol/Content.php @@ -2,7 +2,7 @@ namespace LanguageServer\Protocol; -class TextDocumentContentResult +class Content { /** * The content of the text document From d83319675716b470441e9fc60dacf331fd288c4b Mon Sep 17 00:00:00 2001 From: Felix Becker Date: Sun, 6 Nov 2016 23:54:52 +0100 Subject: [PATCH 17/24] Array only for xglob $patterns --- src/Client/Workspace.php | 13 ++++++++----- src/LanguageServer.php | 13 ++++++++----- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/Client/Workspace.php b/src/Client/Workspace.php index d2bf2397..8e277e5a 100644 --- a/src/Client/Workspace.php +++ b/src/Client/Workspace.php @@ -30,14 +30,17 @@ public function __construct(ClientHandler $handler, JsonMapper $mapper) } /** - * Returns a list of all files in the workspace that match a glob pattern + * Returns a list of all files in the workspace that match any of the given glob patterns * - * @param string $pattern A glob pattern - * @return Promise Array of documents that match the glob pattern + * @param string[] $patterns Glob patterns + * @return Promise Array of documents that match the glob patterns */ - public function xglob(string $pattern): Promise + public function xglob(array $patterns): Promise { - return $this->handler->request('workspace/xglob', ['pattern' => $pattern])->then(function ($textDocuments) { + return $this->handler->request( + 'workspace/xglob', + ['patterns' => $patterns] + )->then(function (array $textDocuments) { return $this->mapper->mapArray($textDocuments, [], TextDocumentIdentifier::class); }); } diff --git a/src/LanguageServer.php b/src/LanguageServer.php index a4e88fb5..d652fb0d 100644 --- a/src/LanguageServer.php +++ b/src/LanguageServer.php @@ -173,7 +173,7 @@ public function exit() private function indexProject(): Promise { return coroutine(function () { - $textDocuments = yield $this->globWorkspace('**/*.php'); + $textDocuments = yield $this->globWorkspace(['**/*.php']); $count = count($textDocuments); $startTime = microtime(true); @@ -207,23 +207,26 @@ private function indexProject(): Promise * Returns all files matching a glob pattern. * If the client does not support workspace/xglob, it falls back to globbing the file system directly. * - * @param string $pattern + * @param string $patterns * @return Promise */ - private function globWorkspace(string $pattern): Promise + private function globWorkspace(array $patterns): Promise { if ($this->clientCapabilities->xglobProvider) { // Use xglob request - return $this->client->workspace->xglob($pattern); + return $this->client->workspace->xglob($patterns); } else { // Use the file system - return coroutine(function () use ($pattern) { $textDocuments = []; + return Promise\all(array_map(function ($pattern) use (&$textDocuments) { + return coroutine(function () use ($pattern, &$textDocuments) { $pattern = Path::makeAbsolute($pattern, $this->rootPath); foreach (new GlobIterator($pattern) as $path) { $textDocuments[] = new TextDocumentIdentifier(pathToUri($path)); yield timeout(); } + }); + }, $patterns))->then(function () use ($textDocuments) { return $textDocuments; }); } From 4df41956185620a6a22f1c64ce951d74d5ff90f7 Mon Sep 17 00:00:00 2001 From: Felix Becker Date: Mon, 7 Nov 2016 01:23:38 +0100 Subject: [PATCH 18/24] Fix lint errors --- src/LanguageServer.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/LanguageServer.php b/src/LanguageServer.php index d652fb0d..1696ac27 100644 --- a/src/LanguageServer.php +++ b/src/LanguageServer.php @@ -220,11 +220,11 @@ private function globWorkspace(array $patterns): Promise $textDocuments = []; return Promise\all(array_map(function ($pattern) use (&$textDocuments) { return coroutine(function () use ($pattern, &$textDocuments) { - $pattern = Path::makeAbsolute($pattern, $this->rootPath); - foreach (new GlobIterator($pattern) as $path) { - $textDocuments[] = new TextDocumentIdentifier(pathToUri($path)); - yield timeout(); - } + $pattern = Path::makeAbsolute($pattern, $this->rootPath); + foreach (new GlobIterator($pattern) as $path) { + $textDocuments[] = new TextDocumentIdentifier(pathToUri($path)); + yield timeout(); + } }); }, $patterns))->then(function () use ($textDocuments) { return $textDocuments; From 898349b69f04d2a5cf62e62255f568cc784845bc Mon Sep 17 00:00:00 2001 From: Felix Becker Date: Mon, 7 Nov 2016 13:16:14 +0100 Subject: [PATCH 19/24] Add tests for xglob/xcontent mode --- src/Client/TextDocument.php | 2 +- src/LanguageServer.php | 2 +- src/Protocol/Content.php | 8 +++++ tests/LanguageServerTest.php | 63 ++++++++++++++++++++++++++++++++---- tests/MockProtocolStream.php | 6 ++-- 5 files changed, 71 insertions(+), 10 deletions(-) diff --git a/src/Client/TextDocument.php b/src/Client/TextDocument.php index a7759c89..6cf09cd8 100644 --- a/src/Client/TextDocument.php +++ b/src/Client/TextDocument.php @@ -4,7 +4,7 @@ namespace LanguageServer\Client; use LanguageServer\ClientHandler; -use LanguageServer\Protocol\{Message, Content}; +use LanguageServer\Protocol\{Message, Content, TextDocumentIdentifier}; use Sabre\Event\Promise; use JsonMapper; diff --git a/src/LanguageServer.php b/src/LanguageServer.php index 1da67445..0af06aa0 100644 --- a/src/LanguageServer.php +++ b/src/LanguageServer.php @@ -191,7 +191,7 @@ private function indexProject(): Promise } catch (Exception $e) { $this->client->window->logMessage( MessageType::ERROR, - "Error parsing file $shortName: " . (string)$e + "Error parsing file {$textDocument->uri}: " . (string)$e ); } }); diff --git a/src/Protocol/Content.php b/src/Protocol/Content.php index 6f95a8a3..c1b93813 100644 --- a/src/Protocol/Content.php +++ b/src/Protocol/Content.php @@ -10,4 +10,12 @@ class Content * @var string */ public $text; + + /** + * @param string $text The content of the text document + */ + public function __construct(string $text = null) + { + $this->text = $text; + } } diff --git a/tests/LanguageServerTest.php b/tests/LanguageServerTest.php index 3f0c3147..fcab0c33 100644 --- a/tests/LanguageServerTest.php +++ b/tests/LanguageServerTest.php @@ -5,9 +5,12 @@ use PHPUnit\Framework\TestCase; use LanguageServer\LanguageServer; -use LanguageServer\Protocol\{Message, ClientCapabilities, TextDocumentSyncKind, MessageType}; +use LanguageServer\Protocol\{Message, ClientCapabilities, TextDocumentSyncKind, MessageType, Content}; use AdvancedJsonRpc; +use Webmozart\Glob\Glob; +use Webmozart\PathUtil\Path; use Sabre\Event\Promise; +use Exception; use function LanguageServer\pathToUri; class LanguageServerTest extends TestCase @@ -17,15 +20,14 @@ public function testInitialize() $reader = new MockProtocolStream(); $writer = new MockProtocolStream(); $server = new LanguageServer($reader, $writer); - $msg = null; - $writer->on('message', function (Message $message) use (&$msg) { - $msg = $message; - }); + $promise = new Promise; + $writer->once('message', [$promise, 'fulfill']); $reader->write(new Message(new AdvancedJsonRpc\Request(1, 'initialize', [ 'rootPath' => __DIR__, 'processId' => getmypid(), 'capabilities' => new ClientCapabilities() ]))); + $msg = $promise->wait(); $this->assertNotNull($msg, 'message event should be emitted'); $this->assertInstanceOf(AdvancedJsonRpc\SuccessResponse::class, $msg->body); $this->assertEquals((object)[ @@ -49,7 +51,7 @@ public function testInitialize() ], $msg->body->result); } - public function testIndexing() + public function testIndexingWithDirectFileAccess() { $promise = new Promise; $input = new MockProtocolStream; @@ -68,4 +70,53 @@ public function testIndexing() $server->initialize(getmypid(), $capabilities, realpath(__DIR__ . '/../fixtures')); $promise->wait(); } + + public function testIndexingWithGlobAndContentRequests() + { + $promise = new Promise; + $globCalled = false; + $contentCalled = false; + $rootPath = realpath(__DIR__ . '/../fixtures'); + $input = new MockProtocolStream; + $output = new MockProtocolStream; + $output->on('message', function (Message $msg) use ($promise, $input, $rootPath, &$globCalled, &$contentCalled) { + if ($msg->body->method === 'textDocument/xcontent') { + // Document content requested + $contentCalled = true; + $input->write(new Message(new AdvancedJsonRpc\SuccessResponse( + $msg->body->id, + new Content(file_get_contents($msg->body->params->textDocument->uri)) + ))); + } else if ($msg->body->method === 'workspace/xglob') { + // Glob requested + $globCalled = true; + $files = array_map( + '\\LanguageServer\\pathToUri', + array_merge(...array_map(function (string $pattern) use ($rootPath) { + return Glob::glob(Path::makeAbsolute($pattern, $rootPath)); + }, $msg->body->params->patterns)) + ); + $input->write(new Message(new AdvancedJsonRpc\SuccessResponse($msg->body->id, $files))); + } else if ($msg->body->method === 'window/logMessage') { + // Message logged + if ($msg->body->params->type === MessageType::ERROR) { + // Error happened during indexing, fail test + if ($promise->state === Promise::PENDING) { + $promise->reject(new Exception($msg->body->params->message)); + } + } else if (strpos($msg->body->params->message, 'All 10 PHP files parsed') !== false) { + // Indexing finished + $promise->fulfill(); + } + } + }); + $server = new LanguageServer($input, $output); + $capabilities = new ClientCapabilities; + $capabilities->xglobProvider = true; + $capabilities->xcontentProvider = true; + $server->initialize(getmypid(), $capabilities, $rootPath); + $promise->wait(); + $this->assertTrue($globCalled); + $this->assertTrue($contentCalled); + } } diff --git a/tests/MockProtocolStream.php b/tests/MockProtocolStream.php index 5550b3f2..b2a489d0 100644 --- a/tests/MockProtocolStream.php +++ b/tests/MockProtocolStream.php @@ -5,7 +5,7 @@ use LanguageServer\{ProtocolReader, ProtocolWriter}; use LanguageServer\Protocol\Message; -use Sabre\Event\{Emitter, Promise}; +use Sabre\Event\{Loop, Emitter, Promise}; /** * A fake duplex protocol stream @@ -20,7 +20,9 @@ class MockProtocolStream extends Emitter implements ProtocolReader, ProtocolWrit */ public function write(Message $msg): Promise { - $this->emit('message', [Message::parse((string)$msg)]); + Loop\nextTick(function () use ($msg) { + $this->emit('message', [Message::parse((string)$msg)]); + }); return Promise\resolve(null); } } From cdb2c46f6896c76502a819da1f73aea8b1963bc8 Mon Sep 17 00:00:00 2001 From: Felix Becker Date: Sat, 12 Nov 2016 10:15:37 +0100 Subject: [PATCH 20/24] Integrate latest protocol changes * workspace/glob becomes workspace/files * return type of textDocument/files is now TextDocumentItem --- src/Client/TextDocument.php | 4 +-- src/LanguageServer.php | 40 +++++++++++++---------------- src/Protocol/ClientCapabilities.php | 4 +-- src/Protocol/Content.php | 21 --------------- 4 files changed, 22 insertions(+), 47 deletions(-) delete mode 100644 src/Protocol/Content.php diff --git a/src/Client/TextDocument.php b/src/Client/TextDocument.php index 6cf09cd8..4f356564 100644 --- a/src/Client/TextDocument.php +++ b/src/Client/TextDocument.php @@ -4,7 +4,7 @@ namespace LanguageServer\Client; use LanguageServer\ClientHandler; -use LanguageServer\Protocol\{Message, Content, TextDocumentIdentifier}; +use LanguageServer\Protocol\{Message, TextDocumentItem, TextDocumentIdentifier}; use Sabre\Event\Promise; use JsonMapper; @@ -57,7 +57,7 @@ public function xcontent(TextDocumentIdentifier $textDocument): Promise 'textDocument/xcontent', ['textDocument' => $textDocument] )->then(function ($result) { - return $this->mapper->map($result, new Content); + return $this->mapper->map($result, new TextDocumentItem); }); } } diff --git a/src/LanguageServer.php b/src/LanguageServer.php index 0af06aa0..a6789b95 100644 --- a/src/LanguageServer.php +++ b/src/LanguageServer.php @@ -173,7 +173,7 @@ public function exit() private function indexProject(): Promise { return coroutine(function () { - $textDocuments = yield $this->globWorkspace(['**/*.php']); + $textDocuments = yield $this->findPhpFiles(); $count = count($textDocuments); $startTime = microtime(true); @@ -207,31 +207,27 @@ private function indexProject(): Promise } /** - * Returns all files matching a glob pattern. - * If the client does not support workspace/xglob, it falls back to globbing the file system directly. + * Returns all PHP files in the workspace. + * If the client does not support workspace/files, it falls back to searching the file system directly. * - * @param string $patterns * @return Promise */ - private function globWorkspace(array $patterns): Promise + private function findPhpFiles(): Promise { - if ($this->clientCapabilities->xglobProvider) { - // Use xglob request - return $this->client->workspace->xglob($patterns); - } else { - // Use the file system - $textDocuments = []; - return Promise\all(array_map(function ($pattern) use (&$textDocuments) { - return coroutine(function () use ($pattern, &$textDocuments) { - $pattern = Path::makeAbsolute($pattern, $this->rootPath); - foreach (new GlobIterator($pattern) as $path) { - $textDocuments[] = new TextDocumentIdentifier(pathToUri($path)); - yield timeout(); - } - }); - }, $patterns))->then(function () use (&$textDocuments) { + return coroutine(function () { + if ($this->clientCapabilities->xfilesProvider) { + // Use xfiles request + return yield $this->client->workspace->xfiles($patterns); + } else { + // Use the file system + $textDocuments = []; + $pattern = Path::makeAbsolute('**/*.php', $this->rootPath); + foreach (new GlobIterator($pattern) as $path) { + $textDocuments[] = new TextDocumentIdentifier(pathToUri($path)); + yield timeout(); + } return $textDocuments; - }); - } + } + }); } } diff --git a/src/Protocol/ClientCapabilities.php b/src/Protocol/ClientCapabilities.php index f72a6810..42137e91 100644 --- a/src/Protocol/ClientCapabilities.php +++ b/src/Protocol/ClientCapabilities.php @@ -5,11 +5,11 @@ class ClientCapabilities { /** - * The client supports workspace/xglob requests + * The client supports workspace/xfiles requests * * @var bool|null */ - public $xglobProvider; + public $xfilesProvider; /** * The client supports textDocument/xcontent requests diff --git a/src/Protocol/Content.php b/src/Protocol/Content.php deleted file mode 100644 index c1b93813..00000000 --- a/src/Protocol/Content.php +++ /dev/null @@ -1,21 +0,0 @@ -text = $text; - } -} From d9adfea4221a99f0293debbd0c963ca6af0c6374 Mon Sep 17 00:00:00 2001 From: Felix Becker Date: Sat, 12 Nov 2016 14:31:57 +0100 Subject: [PATCH 21/24] Fixes --- composer.json | 3 ++- src/Client/Workspace.php | 12 ++++++------ src/LanguageServer.php | 15 ++++++++++---- tests/LanguageServerTest.php | 38 +++++++++++++++++++----------------- 4 files changed, 39 insertions(+), 29 deletions(-) diff --git a/composer.json b/composer.json index 62a9217b..2afb4686 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,8 @@ "symfony/debug": "^3.1", "netresearch/jsonmapper": "^1.0", "webmozart/path-util": "^2.3", - "webmozart/glob": "^4.1" + "webmozart/glob": "^4.1", + "sabre/uri": "^2.0" }, "minimum-stability": "dev", "prefer-stable": true, diff --git a/src/Client/Workspace.php b/src/Client/Workspace.php index 8e277e5a..901e386a 100644 --- a/src/Client/Workspace.php +++ b/src/Client/Workspace.php @@ -30,16 +30,16 @@ public function __construct(ClientHandler $handler, JsonMapper $mapper) } /** - * Returns a list of all files in the workspace that match any of the given glob patterns + * Returns a list of all files in a directory * - * @param string[] $patterns Glob patterns - * @return Promise Array of documents that match the glob patterns + * @param string $base The base directory (defaults to the workspace) + * @return Promise Array of documents */ - public function xglob(array $patterns): Promise + public function xfiles(string $base = null): Promise { return $this->handler->request( - 'workspace/xglob', - ['patterns' => $patterns] + 'workspace/xfiles', + ['base' => $base] )->then(function (array $textDocuments) { return $this->mapper->mapArray($textDocuments, [], TextDocumentIdentifier::class); }); diff --git a/src/LanguageServer.php b/src/LanguageServer.php index a6789b95..959aa335 100644 --- a/src/LanguageServer.php +++ b/src/LanguageServer.php @@ -20,7 +20,9 @@ use RuntimeException; use Throwable; use Webmozart\Glob\Iterator\GlobIterator; +use Webmozart\Glob\Glob; use Webmozart\PathUtil\Path; +use Sabre\Uri; class LanguageServer extends AdvancedJsonRpc\Dispatcher { @@ -215,19 +217,24 @@ private function indexProject(): Promise private function findPhpFiles(): Promise { return coroutine(function () { + $textDocuments = []; + $pattern = Path::makeAbsolute('**/*.php', $this->rootPath); if ($this->clientCapabilities->xfilesProvider) { // Use xfiles request - return yield $this->client->workspace->xfiles($patterns); + foreach (yield $this->client->workspace->xfiles() as $textDocument) { + $path = Uri\parse($textDocument->uri)['path']; + if (Glob::match($path, $pattern)) { + $textDocuments[] = $textDocument; + } + } } else { // Use the file system - $textDocuments = []; - $pattern = Path::makeAbsolute('**/*.php', $this->rootPath); foreach (new GlobIterator($pattern) as $path) { $textDocuments[] = new TextDocumentIdentifier(pathToUri($path)); yield timeout(); } - return $textDocuments; } + return $textDocuments; }); } } diff --git a/tests/LanguageServerTest.php b/tests/LanguageServerTest.php index fcab0c33..1690a5e1 100644 --- a/tests/LanguageServerTest.php +++ b/tests/LanguageServerTest.php @@ -5,7 +5,8 @@ use PHPUnit\Framework\TestCase; use LanguageServer\LanguageServer; -use LanguageServer\Protocol\{Message, ClientCapabilities, TextDocumentSyncKind, MessageType, Content}; +use LanguageServer\Protocol\{ + Message, ClientCapabilities, TextDocumentSyncKind, MessageType, TextDocumentItem, TextDocumentIdentifier}; use AdvancedJsonRpc; use Webmozart\Glob\Glob; use Webmozart\PathUtil\Path; @@ -71,31 +72,32 @@ public function testIndexingWithDirectFileAccess() $promise->wait(); } - public function testIndexingWithGlobAndContentRequests() + public function testIndexingWithFilesAndContentRequests() { $promise = new Promise; - $globCalled = false; + $filesCalled = false; $contentCalled = false; $rootPath = realpath(__DIR__ . '/../fixtures'); $input = new MockProtocolStream; $output = new MockProtocolStream; - $output->on('message', function (Message $msg) use ($promise, $input, $rootPath, &$globCalled, &$contentCalled) { + $output->on('message', function (Message $msg) use ($promise, $input, $rootPath, &$filesCalled, &$contentCalled) { if ($msg->body->method === 'textDocument/xcontent') { // Document content requested $contentCalled = true; - $input->write(new Message(new AdvancedJsonRpc\SuccessResponse( - $msg->body->id, - new Content(file_get_contents($msg->body->params->textDocument->uri)) - ))); - } else if ($msg->body->method === 'workspace/xglob') { + $textDocumentItem = new TextDocumentItem; + $textDocumentItem->uri = $msg->body->params->textDocument->uri; + $textDocumentItem->version = 1; + $textDocumentItem->languageId = 'php'; + $textDocumentItem->text = file_get_contents($msg->body->params->textDocument->uri); + $input->write(new Message(new AdvancedJsonRpc\SuccessResponse($msg->body->id, $textDocumentItem))); + } else if ($msg->body->method === 'workspace/xfiles') { // Glob requested - $globCalled = true; - $files = array_map( - '\\LanguageServer\\pathToUri', - array_merge(...array_map(function (string $pattern) use ($rootPath) { - return Glob::glob(Path::makeAbsolute($pattern, $rootPath)); - }, $msg->body->params->patterns)) - ); + $filesCalled = true; + $pattern = Path::makeAbsolute('**/*.php', $msg->body->params->base ?? $rootPath); + $files = []; + foreach (Glob::glob($pattern) as $path) { + $files[] = new TextDocumentIdentifier(pathToUri($path)); + } $input->write(new Message(new AdvancedJsonRpc\SuccessResponse($msg->body->id, $files))); } else if ($msg->body->method === 'window/logMessage') { // Message logged @@ -112,11 +114,11 @@ public function testIndexingWithGlobAndContentRequests() }); $server = new LanguageServer($input, $output); $capabilities = new ClientCapabilities; - $capabilities->xglobProvider = true; + $capabilities->xfilesProvider = true; $capabilities->xcontentProvider = true; $server->initialize(getmypid(), $capabilities, $rootPath); $promise->wait(); - $this->assertTrue($globCalled); + $this->assertTrue($filesCalled); $this->assertTrue($contentCalled); } } From 471b88fc79edf1ffeacc8387e46435714c066dd4 Mon Sep 17 00:00:00 2001 From: Felix Becker Date: Sat, 12 Nov 2016 14:58:45 +0100 Subject: [PATCH 22/24] Correct documentation --- tests/LanguageServerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/LanguageServerTest.php b/tests/LanguageServerTest.php index 1690a5e1..84567130 100644 --- a/tests/LanguageServerTest.php +++ b/tests/LanguageServerTest.php @@ -91,7 +91,7 @@ public function testIndexingWithFilesAndContentRequests() $textDocumentItem->text = file_get_contents($msg->body->params->textDocument->uri); $input->write(new Message(new AdvancedJsonRpc\SuccessResponse($msg->body->id, $textDocumentItem))); } else if ($msg->body->method === 'workspace/xfiles') { - // Glob requested + // Files requested $filesCalled = true; $pattern = Path::makeAbsolute('**/*.php', $msg->body->params->base ?? $rootPath); $files = []; From 981cb75422331b2d21262a1ebca3174276b65a55 Mon Sep 17 00:00:00 2001 From: Felix Becker Date: Mon, 14 Nov 2016 10:08:50 +0100 Subject: [PATCH 23/24] Correct docblock return type --- src/Client/TextDocument.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client/TextDocument.php b/src/Client/TextDocument.php index 4f356564..176c4fd7 100644 --- a/src/Client/TextDocument.php +++ b/src/Client/TextDocument.php @@ -49,7 +49,7 @@ public function publishDiagnostics(string $uri, array $diagnostics): Promise * to request the current content of a text document identified by the URI * * @param TextDocumentIdentifier $textDocument The document to get the content for - * @return Promise The document's current content + * @return Promise The document's current content */ public function xcontent(TextDocumentIdentifier $textDocument): Promise { From 7efb2c91c16173fc1295ae50d7b4e2823468ba62 Mon Sep 17 00:00:00 2001 From: Felix Becker Date: Mon, 14 Nov 2016 10:10:51 +0100 Subject: [PATCH 24/24] Remove unused import --- src/LanguageServer.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/LanguageServer.php b/src/LanguageServer.php index 959aa335..9a89066c 100644 --- a/src/LanguageServer.php +++ b/src/LanguageServer.php @@ -17,7 +17,6 @@ use Sabre\Event\{Loop, Promise}; use function Sabre\Event\coroutine; use Exception; -use RuntimeException; use Throwable; use Webmozart\Glob\Iterator\GlobIterator; use Webmozart\Glob\Glob;