From ccaf1c1547188ef4c786d9a40772b96599086215 Mon Sep 17 00:00:00 2001 From: Sara Itani Date: Wed, 1 Mar 2017 00:32:15 -0800 Subject: [PATCH 1/3] Ensure diagnostics are cleared on file deletion Previously, error diagnostics would not be cleared when a file was deleted while it was closed. This would result in lingering errors in the problems view that could only be cleared by reloading the language server. This fix addresses the issue by adding support for workspace/didChangeWatchedFiles and automatically clearing diagnostics for deleted files. --- src/LanguageServer.php | 1 + src/Server/Workspace.php | 33 +++++++++++++- tests/Server/ServerTestCase.php | 2 +- .../Workspace/DidChangeWatchedFilesTest.php | 43 +++++++++++++++++++ 4 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 tests/Server/Workspace/DidChangeWatchedFilesTest.php diff --git a/src/LanguageServer.php b/src/LanguageServer.php index 3c999f22..8fe9ec1c 100644 --- a/src/LanguageServer.php +++ b/src/LanguageServer.php @@ -253,6 +253,7 @@ public function initialize(ClientCapabilities $capabilities, string $rootPath = } if ($this->workspace === null) { $this->workspace = new Server\Workspace( + $this->client, $this->projectIndex, $dependenciesIndex, $sourceIndex, diff --git a/src/Server/Workspace.php b/src/Server/Workspace.php index b94618cb..4d36c0cd 100644 --- a/src/Server/Workspace.php +++ b/src/Server/Workspace.php @@ -5,7 +5,15 @@ use LanguageServer\{LanguageClient, Project, PhpDocumentLoader}; use LanguageServer\Index\{ProjectIndex, DependenciesIndex, Index}; -use LanguageServer\Protocol\{SymbolInformation, SymbolDescriptor, ReferenceInformation, DependencyReference, Location}; +use LanguageServer\Protocol\{ + FileChangeType, + FileEvent, + SymbolInformation, + SymbolDescriptor, + ReferenceInformation, + DependencyReference, + Location +}; use Sabre\Event\Promise; use function Sabre\Event\coroutine; use function LanguageServer\{waitForEvent, getPackageName}; @@ -15,6 +23,11 @@ */ class Workspace { + /** + * @var LanguageClient + */ + public $client; + /** * The symbol index for the workspace * @@ -43,14 +56,16 @@ class Workspace public $documentLoader; /** + * @param LanguageClient $client LanguageClient instance used to signal updated results * @param ProjectIndex $index Index that is searched on a workspace/symbol request * @param DependenciesIndex $dependenciesIndex Index that is used on a workspace/xreferences request * @param DependenciesIndex $sourceIndex Index that is used on a workspace/xreferences request * @param \stdClass $composerLock The parsed composer.lock of the project, if any * @param PhpDocumentLoader $documentLoader PhpDocumentLoader instance to load documents */ - public function __construct(ProjectIndex $index, DependenciesIndex $dependenciesIndex, Index $sourceIndex, \stdClass $composerLock = null, PhpDocumentLoader $documentLoader, \stdClass $composerJson = null) + public function __construct(LanguageClient $client, ProjectIndex $index, DependenciesIndex $dependenciesIndex, Index $sourceIndex, \stdClass $composerLock = null, PhpDocumentLoader $documentLoader, \stdClass $composerJson = null) { + $this->client = $client; $this->sourceIndex = $sourceIndex; $this->index = $index; $this->dependenciesIndex = $dependenciesIndex; @@ -82,6 +97,20 @@ public function symbol(string $query): Promise }); } + /** + * The watched files notification is sent from the client to the server when the client detects changes to files watched by the language client. + * + * @param FileEvent[] $changes + * @return void + */ + public function didChangeWatchedFiles(array $changes) : void { + foreach ($changes as $change) { + if ($change->type === FileChangeType::DELETED) { + $this->client->textDocument->publishDiagnostics($change->uri, []); + } + } + } + /** * The workspace references request is sent from the client to the server to locate project-wide references to a symbol given its description / metadata. * diff --git a/tests/Server/ServerTestCase.php b/tests/Server/ServerTestCase.php index 679191f9..94ba9334 100644 --- a/tests/Server/ServerTestCase.php +++ b/tests/Server/ServerTestCase.php @@ -54,7 +54,7 @@ public function setUp() $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream); $this->documentLoader = new PhpDocumentLoader(new FileSystemContentRetriever, $projectIndex, $definitionResolver); $this->textDocument = new Server\TextDocument($this->documentLoader, $definitionResolver, $client, $projectIndex); - $this->workspace = new Server\Workspace($projectIndex, $dependenciesIndex, $sourceIndex, null, $this->documentLoader); + $this->workspace = new Server\Workspace($client, $projectIndex, $dependenciesIndex, $sourceIndex, null, $this->documentLoader); $globalSymbolsUri = pathToUri(realpath(__DIR__ . '/../../fixtures/global_symbols.php')); $globalReferencesUri = pathToUri(realpath(__DIR__ . '/../../fixtures/global_references.php')); diff --git a/tests/Server/Workspace/DidChangeWatchedFilesTest.php b/tests/Server/Workspace/DidChangeWatchedFilesTest.php new file mode 100644 index 00000000..12089306 --- /dev/null +++ b/tests/Server/Workspace/DidChangeWatchedFilesTest.php @@ -0,0 +1,43 @@ +uri = 'my uri'; + $fileEvent->type = FileChangeType::DELETED; + + $isDiagnosticsCleared = false; + $writer->on('message', function (Message $message) use ($fileEvent, & $isDiagnosticsCleared) { + if ($message->body->method === "textDocument/publishDiagnostics") { + $this->assertEquals($message->body->params->uri, $fileEvent->uri); + $this->assertEquals($message->body->params->diagnostics, []); + $isDiagnosticsCleared = true; + } + }); + + $workspace->didChangeWatchedFiles([$fileEvent]); + Loop\tick(true); + + $this->assertTrue($isDiagnosticsCleared, "Deleting file should clear all diagnostics."); + } +} From 616d74cc3f954baa64675fcc32ccff4aff1fc03a Mon Sep 17 00:00:00 2001 From: Sara Itani Date: Wed, 1 Mar 2017 01:30:17 -0800 Subject: [PATCH 2/3] brace to next line, and remove void return type --- src/Server/Workspace.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Server/Workspace.php b/src/Server/Workspace.php index 4d36c0cd..d2f8cecf 100644 --- a/src/Server/Workspace.php +++ b/src/Server/Workspace.php @@ -103,7 +103,8 @@ public function symbol(string $query): Promise * @param FileEvent[] $changes * @return void */ - public function didChangeWatchedFiles(array $changes) : void { + public function didChangeWatchedFiles(array $changes) + { foreach ($changes as $change) { if ($change->type === FileChangeType::DELETED) { $this->client->textDocument->publishDiagnostics($change->uri, []); From fc19640babfd949cb66c4504715336d468f2b3b3 Mon Sep 17 00:00:00 2001 From: Sara Itani Date: Wed, 1 Mar 2017 01:57:17 -0800 Subject: [PATCH 3/3] add FileEvent constructor, fix formatting --- src/Protocol/FileEvent.php | 10 ++++++++++ tests/Server/Workspace/DidChangeWatchedFilesTest.php | 6 ++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Protocol/FileEvent.php b/src/Protocol/FileEvent.php index b4ed8338..015044d3 100644 --- a/src/Protocol/FileEvent.php +++ b/src/Protocol/FileEvent.php @@ -20,4 +20,14 @@ class FileEvent * @var int */ public $type; + + /** + * @param string $uri + * @param int $type + */ + public function __construct(string $uri, int $type) + { + $this->uri = $uri; + $this->type = $type; + } } diff --git a/tests/Server/Workspace/DidChangeWatchedFilesTest.php b/tests/Server/Workspace/DidChangeWatchedFilesTest.php index 12089306..1074c583 100644 --- a/tests/Server/Workspace/DidChangeWatchedFilesTest.php +++ b/tests/Server/Workspace/DidChangeWatchedFilesTest.php @@ -22,12 +22,10 @@ public function testDeletingFileClearsAllDiagnostics() $loader = new PhpDocumentLoader(new FileSystemContentRetriever(), $projectIndex, $definitionResolver); $workspace = new Server\Workspace($client, $projectIndex, $dependenciesIndex, $sourceIndex, null, $loader, null); - $fileEvent = new FileEvent(); - $fileEvent->uri = 'my uri'; - $fileEvent->type = FileChangeType::DELETED; + $fileEvent = new FileEvent('my uri', FileChangeType::DELETED); $isDiagnosticsCleared = false; - $writer->on('message', function (Message $message) use ($fileEvent, & $isDiagnosticsCleared) { + $writer->on('message', function (Message $message) use ($fileEvent, &$isDiagnosticsCleared) { if ($message->body->method === "textDocument/publishDiagnostics") { $this->assertEquals($message->body->params->uri, $fileEvent->uri); $this->assertEquals($message->body->params->diagnostics, []);