Skip to content

Commit 035a785

Browse files
committed
Add support for file events
1 parent 39cc452 commit 035a785

File tree

9 files changed

+259
-6
lines changed

9 files changed

+259
-6
lines changed

bin/serve.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use Phpactor\LanguageServer\Example\Command\SayHelloCommand;
2424
use Phpactor\LanguageServer\Example\Diagnostics\SayHelloDiagnosticsProvider;
2525
use Phpactor\LanguageServer\Handler\TextDocument\CodeActionHandler;
26+
use Phpactor\LanguageServer\Handler\Workspace\DidChangeWatchedFilesHandler;
2627
use Phpactor\LanguageServer\Listener\ServiceListener;
2728
use Phpactor\LanguageServer\Core\Service\ServiceManager;
2829
use Phpactor\LanguageServer\Core\Service\ServiceProviders;
@@ -38,6 +39,7 @@
3839
use Phpactor\LanguageServer\Middleware\HandlerMiddleware;
3940
use Phpactor\LanguageServer\Middleware\InitializeMiddleware;
4041
use Phpactor\LanguageServer\Core\Command\CommandDispatcher;
42+
use Phpactor\LanguageServer\Middleware\ResponseHandlingMiddleware;
4143
use Phpactor\LanguageServer\Service\DiagnosticsService;
4244
use Psr\Log\AbstractLogger;
4345
use function Safe\fopen;
@@ -107,6 +109,7 @@ function (MessageTransmitter $transmitter, InitializeParams $params) use ($logge
107109
$eventDispatcher = new AggregateEventDispatcher(
108110
new ServiceListener($serviceManager),
109111
new WorkspaceListener($workspace),
112+
new DidChangeWatchedFilesHandler($clientApi, ['**/*.php']),
110113
$diagnosticsService
111114
);
112115

@@ -117,6 +120,7 @@ function (MessageTransmitter $transmitter, InitializeParams $params) use ($logge
117120
new CommandHandler(new CommandDispatcher([
118121
'phpactor.say_hello' => new SayHelloCommand($clientApi)
119122
])),
123+
new DidChangeWatchedFilesHandler($clientApi, ['**/*.php']),
120124
new CodeActionHandler(new AggregateCodeActionProvider(
121125
new SayHelloCodeActionProvider()
122126
), $workspace),
@@ -137,6 +141,7 @@ function (MessageTransmitter $transmitter, InitializeParams $params) use ($logge
137141
'version' => 1,
138142
]),
139143
new CancellationMiddleware($runner),
144+
new ResponseHandlingMiddleware($responseWatcher),
140145
new HandlerMiddleware($runner)
141146
);
142147
}

lib/Core/Server/Client/ClientClient.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Phpactor\LanguageServer\Core\Server\Client;
44

5+
use Amp\Promise;
56
use Phpactor\LanguageServerProtocol\Registration;
67
use Phpactor\LanguageServerProtocol\Unregistration;
78
use Phpactor\LanguageServer\Core\Server\RpcClient;
@@ -18,16 +19,16 @@ public function __construct(RpcClient $client)
1819
$this->client = $client;
1920
}
2021

21-
public function registerCapability(Registration ...$registrations): void
22+
public function registerCapability(Registration ...$registrations): Promise
2223
{
23-
$this->client->notification('client/registerCapability', [
24+
return $this->client->request('client/registerCapability', [
2425
'registrations' => $registrations
2526
]);
2627
}
2728

28-
public function unregisterCapability(Unregistration ...$unregistrations): void
29+
public function unregisterCapability(Unregistration ...$unregistrations): Promise
2930
{
30-
$this->client->notification('client/unregisterCapability', [
31+
return $this->client->request('client/unregisterCapability', [
3132
'unregistrations' => $unregistrations
3233
]);
3334
}

lib/Event/FilesChanged.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace Phpactor\LanguageServer\Event;
4+
5+
use Phpactor\LanguageServerProtocol\FileEvent;
6+
7+
final class FilesChanged
8+
{
9+
/**
10+
* @var array
11+
*/
12+
private $events;
13+
14+
public function __construct(FileEvent ...$events)
15+
{
16+
$this->events = $events;
17+
}
18+
19+
public function events(): array
20+
{
21+
return $this->events;
22+
}
23+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<?php
2+
3+
namespace Phpactor\LanguageServer\Handler\Workspace;
4+
5+
use Phpactor\LanguageServerProtocol\DidChangeTextDocumentNotification;
6+
use Phpactor\LanguageServerProtocol\DidChangeWatchedFilesParams;
7+
use Phpactor\LanguageServerProtocol\DidChangeWatchedFilesRegistrationOptions;
8+
use Phpactor\LanguageServerProtocol\FileSystemWatcher;
9+
use Phpactor\LanguageServerProtocol\Registration;
10+
use Phpactor\LanguageServer\Core\Handler\Handler;
11+
use Phpactor\LanguageServer\Core\Server\ClientApi;
12+
use Phpactor\LanguageServer\Event\FilesChanged;
13+
use Phpactor\LanguageServer\Event\Initialized;
14+
use Psr\EventDispatcher\EventDispatcherInterface;
15+
use Psr\EventDispatcher\ListenerProviderInterface;
16+
use Ramsey\Uuid\Uuid;
17+
use function Amp\asyncCall;
18+
19+
class DidChangeWatchedFilesHandler implements ListenerProviderInterface, Handler
20+
{
21+
/**
22+
* @var ClientApi
23+
*/
24+
private $client;
25+
26+
/**
27+
* @var array
28+
*/
29+
private $globPatterns;
30+
31+
/**
32+
* @var EventDispatcherInterface
33+
*/
34+
private $dispatcher;
35+
36+
public function __construct(ClientApi $client, EventDispatcherInterface $dispatcher, array $globPatterns)
37+
{
38+
$this->client = $client;
39+
$this->globPatterns = $globPatterns;
40+
$this->dispatcher = $dispatcher;
41+
}
42+
43+
/**
44+
* {@inheritDoc}
45+
*/
46+
public function getListenersForEvent(object $event): iterable
47+
{
48+
if ($event instanceof Initialized) {
49+
return [[$this, 'registerCapability']];
50+
}
51+
52+
return [];
53+
}
54+
55+
public function registerCapability(Initialized $initialized): void
56+
{
57+
asyncCall(function () {
58+
yield $this->client->client()->registerCapability(
59+
new Registration(
60+
Uuid::uuid4()->__toString(),
61+
'workspace/didChangeWatchedFiles',
62+
new DidChangeWatchedFilesRegistrationOptions(array_map(function (string $glob) {
63+
return new FileSystemWatcher($glob);
64+
}, $this->globPatterns))
65+
));
66+
});
67+
}
68+
69+
/**
70+
* {@inheritDoc}
71+
*/
72+
public function methods(): array
73+
{
74+
return [
75+
'workspace/didChangeWatchedFiles' => 'didChange'
76+
];
77+
}
78+
79+
public function didChange(DidChangeWatchedFilesParams $params)
80+
{
81+
$this->dispatcher->dispatch(new FilesChanged(...$params->changes));
82+
}
83+
}

lib/LanguageServerTesterBuilder.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
use Phpactor\LanguageServer\Handler\System\ServiceHandler;
2626
use Phpactor\LanguageServer\Handler\TextDocument\TextDocumentHandler;
2727
use Phpactor\LanguageServer\Handler\Workspace\CommandHandler;
28+
use Phpactor\LanguageServer\Handler\Workspace\DidChangeWatchedFilesHandler;
2829
use Phpactor\LanguageServer\Listener\ServiceListener;
2930
use Phpactor\LanguageServer\Listener\WorkspaceListener;
3031
use Phpactor\LanguageServer\Middleware\HandlerMiddleware;
@@ -108,6 +109,11 @@ final class LanguageServerTesterBuilder
108109
*/
109110
private $enableServices = false;
110111

112+
/**
113+
* @var bool
114+
*/
115+
private $enableFileEvents = false;
116+
111117
/**
112118
* @var bool
113119
*/
@@ -128,6 +134,8 @@ final class LanguageServerTesterBuilder
128134
*/
129135
private $diagnosticsProvider = [];
130136

137+
private $fileEventGlobs = ['**/*.php'];
138+
131139
private function __construct()
132140
{
133141
$this->initializeParams = new InitializeParams(new ClientCapabilities());
@@ -224,6 +232,21 @@ public function enableTextDocuments(): self
224232
return $this;
225233
}
226234

235+
/**
236+
* Enable file events
237+
* @param string[] $globs
238+
*/
239+
public function enableFileEvents(?array $globs = null): self
240+
{
241+
$this->enableFileEvents = true;
242+
243+
if (null !==$globs) {
244+
$this->fileEventGlobs = $globs;
245+
}
246+
247+
return $this;
248+
}
249+
227250
/**
228251
* Enable the services (enabled by default with ::create)
229252
*/
@@ -326,6 +349,15 @@ function (MessageTransmitter $transmitter, InitializeParams $params) {
326349
$handlers[] = new ServiceHandler($serviceManager, $this->clientApi);
327350
}
328351

352+
if ($this->enableFileEvents) {
353+
$handlers = (function (array $handlers) use ($eventDispatcher) {
354+
$handler = new DidChangeWatchedFilesHandler($this->clientApi, $eventDispatcher, $this->fileEventGlobs);
355+
$handlers[] = $handler;
356+
$this->listeners[] = $handler;
357+
return $handlers;
358+
})($handlers);
359+
}
360+
329361
if ($this->enableCommands) {
330362
$handlers[] = new CommandHandler(new CommandDispatcher($this->commands));
331363
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
namespace Phpactor\LanguageServer\Test\ListenerProvider;
4+
5+
use Psr\EventDispatcher\ListenerProviderInterface;
6+
use RuntimeException;
7+
8+
class RecordingListenerProvider implements ListenerProviderInterface
9+
{
10+
private $recieved = [];
11+
12+
/**
13+
* {@inheritDoc}
14+
*/
15+
public function getListenersForEvent(object $event): iterable
16+
{
17+
return [
18+
function (object $event): void {
19+
$this->recieved[] = $event;
20+
}
21+
];
22+
}
23+
24+
/**
25+
* @templtae T of class-string
26+
* @param T $type
27+
* @return T
28+
*/
29+
public function shift(string $type): object
30+
{
31+
$next = array_shift($this->recieved);
32+
33+
if (null === $next) {
34+
throw new RuntimeException('No more events');
35+
}
36+
37+
if (!$next instanceof $type) {
38+
throw new RuntimeException(sprintf(
39+
'Expected event of type "%s" but got "%s"',
40+
$type, get_class($next)
41+
));
42+
}
43+
44+
return $next;
45+
}
46+
}

tests/Unit/Core/Server/ClientApiTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ function (ClientApi $api): void {
205205
);
206206
},
207207
function (TestRpcClient $client, $result): void {
208-
$message = $client->transmitter()->shiftNotification();
208+
$message = $client->transmitter()->shiftRequest();
209209
self::assertEquals('client/registerCapability', $message->method);
210210
}
211211
];
@@ -217,7 +217,7 @@ function (ClientApi $api): void {
217217
);
218218
},
219219
function (TestRpcClient $client, $result): void {
220-
$message = $client->transmitter()->shiftNotification();
220+
$message = $client->transmitter()->shiftRequest();
221221
self::assertEquals('client/unregisterCapability', $message->method);
222222
}
223223
];

tests/Unit/Handler/HandlerTestCase.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,11 @@ public function dispatch(string $method, array $params)
1616

1717
return $tester->requestAndWait($method, $params);
1818
}
19+
20+
public function notify(string $method, array $params)
21+
{
22+
$tester = LanguageServerTesterBuilder::createBare()->addHandler($this->handler())->build();
23+
24+
return $tester->notifyAndWait($method, $params);
25+
}
1926
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
namespace Phpactor\LanguageServer\Tests\Unit\Handler\Workspace;
4+
5+
use Amp\Success;
6+
use Phly\EventDispatcher\ListenerProvider\ListenerProviderAggregate;
7+
use Phpactor\LanguageServerProtocol\DidChangeWatchedFilesParams;
8+
use Phpactor\LanguageServerProtocol\FileChangeType;
9+
use Phpactor\LanguageServerProtocol\FileEvent;
10+
use Phpactor\LanguageServerProtocol\ServerCapabilities;
11+
use Phpactor\LanguageServer\Core\Command\Command;
12+
use Phpactor\LanguageServer\Core\Handler\Handler;
13+
use Phpactor\LanguageServer\Core\Server\ClientApi;
14+
use Phpactor\LanguageServer\Core\Server\RpcClient\TestRpcClient;
15+
use Phpactor\LanguageServer\Event\FileCreated;
16+
use Phpactor\LanguageServer\Event\FilesChanged;
17+
use Phpactor\LanguageServer\Event\Initialized;
18+
use Phpactor\LanguageServer\Handler\Workspace\CommandHandler;
19+
use Phpactor\LanguageServer\Handler\Workspace\DidChangeWatchedFilesHandler;
20+
use Phpactor\LanguageServer\LanguageServerTesterBuilder;
21+
use Phpactor\LanguageServer\Test\ListenerProvider\RecordingListenerProvider;
22+
use Phpactor\LanguageServer\Tests\Unit\Handler\HandlerTestCase;
23+
use Phpactor\LanguageServer\Core\Command\CommandDispatcher;
24+
use Phpactor\TestUtils\PHPUnit\TestCase;
25+
use Psr\EventDispatcher\ListenerProviderInterface;
26+
use function Amp\Promise\wait;
27+
use function Amp\delay;
28+
29+
class DidChangeWatchedFilesHandlerTest extends TestCase
30+
{
31+
public function testRegisterCapability(): void
32+
{
33+
$tester = LanguageServerTesterBuilder::createBare()
34+
->enableFileEvents()
35+
->build();
36+
$tester->initialize();
37+
38+
// capability registration happens after the intiialization request has been returned
39+
$this->addToAssertionCount(1);
40+
}
41+
42+
public function testEmitsFileChangedEvents(): void
43+
{
44+
$events = new RecordingListenerProvider();
45+
$tester = LanguageServerTesterBuilder::create()
46+
->enableFileEvents()
47+
->addListenerProvider($events)
48+
->build();
49+
50+
$tester->notifyAndWait('workspace/didChangeWatchedFiles', new DidChangeWatchedFilesParams([
51+
new FileEvent('file://foobar', FileChangeType::CREATED)
52+
]));
53+
$event = $events->shift(FilesChanged::class);
54+
self::assertEquals(new FilesChanged(new FileEvent('file://foobar', FileChangeType::CREATED)), $event);
55+
}
56+
}

0 commit comments

Comments
 (0)