Skip to content

Commit 04ef6c8

Browse files
authored
Handle Client responses (felixfbecker#128)
1 parent ff0a35d commit 04ef6c8

24 files changed

+233
-110
lines changed

src/Client/TextDocument.php

+12-14
Original file line numberDiff line numberDiff line change
@@ -3,39 +3,37 @@
33

44
namespace LanguageServer\Client;
55

6-
use AdvancedJsonRpc\Notification as NotificationBody;
7-
use LanguageServer\ProtocolWriter;
6+
use LanguageServer\ClientHandler;
87
use LanguageServer\Protocol\Message;
8+
use Sabre\Event\Promise;
99

1010
/**
1111
* Provides method handlers for all textDocument/* methods
1212
*/
1313
class TextDocument
1414
{
1515
/**
16-
* @var ProtocolWriter
16+
* @var ClientHandler
1717
*/
18-
private $protocolWriter;
18+
private $handler;
1919

20-
public function __construct(ProtocolWriter $protocolWriter)
20+
public function __construct(ClientHandler $handler)
2121
{
22-
$this->protocolWriter = $protocolWriter;
22+
$this->handler = $handler;
2323
}
2424

2525
/**
2626
* Diagnostics notification are sent from the server to the client to signal results of validation runs.
2727
*
2828
* @param string $uri
2929
* @param Diagnostic[] $diagnostics
30+
* @return Promise <void>
3031
*/
31-
public function publishDiagnostics(string $uri, array $diagnostics)
32+
public function publishDiagnostics(string $uri, array $diagnostics): Promise
3233
{
33-
$this->protocolWriter->write(new Message(new NotificationBody(
34-
'textDocument/publishDiagnostics',
35-
(object)[
36-
'uri' => $uri,
37-
'diagnostics' => $diagnostics
38-
]
39-
)));
34+
return $this->handler->notify('textDocument/publishDiagnostics', [
35+
'uri' => $uri,
36+
'diagnostics' => $diagnostics
37+
]);
4038
}
4139
}

src/Client/Window.php

+14-23
Original file line numberDiff line numberDiff line change
@@ -3,56 +3,47 @@
33

44
namespace LanguageServer\Client;
55

6-
use AdvancedJsonRpc\Notification as NotificationBody;
7-
use LanguageServer\ProtocolWriter;
6+
use LanguageServer\ClientHandler;
87
use LanguageServer\Protocol\Message;
8+
use Sabre\Event\Promise;
99

1010
/**
1111
* Provides method handlers for all window/* methods
1212
*/
1313
class Window
1414
{
1515
/**
16-
* @var ProtocolWriter
16+
* @var ClientHandler
1717
*/
18-
private $protocolWriter;
18+
private $handler;
1919

20-
public function __construct(ProtocolWriter $protocolWriter)
20+
public function __construct(ClientHandler $handler)
2121
{
22-
$this->protocolWriter = $protocolWriter;
22+
$this->handler = $handler;
2323
}
2424

2525
/**
26-
* The show message notification is sent from a server to a client to ask the client to display a particular message in the user interface.
26+
* The show message notification is sent from a server to a client
27+
* to ask the client to display a particular message in the user interface.
2728
*
2829
* @param int $type
2930
* @param string $message
31+
* @return Promise <void>
3032
*/
31-
public function showMessage(int $type, string $message)
33+
public function showMessage(int $type, string $message): Promise
3234
{
33-
$this->protocolWriter->write(new Message(new NotificationBody(
34-
'window/showMessage',
35-
(object)[
36-
'type' => $type,
37-
'message' => $message
38-
]
39-
)));
35+
return $this->handler->notify('window/showMessage', ['type' => $type, 'message' => $message]);
4036
}
4137

4238
/**
4339
* The log message notification is sent from the server to the client to ask the client to log a particular message.
4440
*
4541
* @param int $type
4642
* @param string $message
43+
* @return Promise <void>
4744
*/
48-
public function logMessage(int $type, string $message)
45+
public function logMessage(int $type, string $message): Promise
4946
{
50-
$this->protocolWriter->write(new Message(new NotificationBody(
51-
'window/logMessage',
52-
(object)[
53-
'type' => $type,
54-
'message' => $message
55-
]
56-
)));
47+
return $this->handler->notify('window/logMessage', ['type' => $type, 'message' => $message]);
5748
}
5849
}

src/ClientHandler.php

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
declare(strict_types = 1);
3+
4+
namespace LanguageServer;
5+
6+
use AdvancedJsonRpc;
7+
use Sabre\Event\Promise;
8+
9+
class ClientHandler
10+
{
11+
/**
12+
* @var ProtocolReader
13+
*/
14+
public $protocolReader;
15+
16+
/**
17+
* @var ProtocolWriter
18+
*/
19+
public $protocolWriter;
20+
21+
/**
22+
* @var IdGenerator
23+
*/
24+
public $idGenerator;
25+
26+
public function __construct(ProtocolReader $protocolReader, ProtocolWriter $protocolWriter)
27+
{
28+
$this->protocolReader = $protocolReader;
29+
$this->protocolWriter = $protocolWriter;
30+
$this->idGenerator = new IdGenerator;
31+
}
32+
33+
/**
34+
* Sends a request to the client and returns a promise that is resolved with the result or rejected with the error
35+
*
36+
* @param string $method The method to call
37+
* @param array|object $params The method parameters
38+
* @return Promise <mixed> Resolved with the result of the request or rejected with an error
39+
*/
40+
public function request(string $method, $params): Promise
41+
{
42+
$id = $this->idGenerator->generate();
43+
return $this->protocolWriter->write(
44+
new Protocol\Message(
45+
new AdvancedJsonRpc\Request($id, $method, (object)$params)
46+
)
47+
)->then(function () use ($id) {
48+
$promise = new Promise;
49+
$listener = function (Protocol\Message $msg) use ($id, $promise, &$listener) {
50+
if (AdvancedJsonRpc\Response::isResponse($msg->body) && $msg->body->id === $id) {
51+
// Received a response
52+
$this->protocolReader->removeListener('message', $listener);
53+
if (AdvancedJsonRpc\SuccessResponse::isSuccessResponse($msg->body)) {
54+
$promise->fulfill($msg->body->result);
55+
} else {
56+
$promise->reject($msg->body->error);
57+
}
58+
}
59+
};
60+
$this->protocolReader->on('message', $listener);
61+
return $promise;
62+
});
63+
}
64+
65+
/**
66+
* Sends a notification to the client
67+
*
68+
* @param string $method The method to call
69+
* @param array|object $params The method parameters
70+
* @return Promise <null> Will be resolved as soon as the notification has been sent
71+
*/
72+
public function notify(string $method, $params): Promise
73+
{
74+
$id = $this->idGenerator->generate();
75+
return $this->protocolWriter->write(
76+
new Protocol\Message(
77+
new AdvancedJsonRpc\Notification($method, (object)$params)
78+
)
79+
);
80+
}
81+
}

src/IdGenerator.php

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
declare(strict_types = 1);
3+
4+
namespace LanguageServer;
5+
6+
/**
7+
* Generates unique, incremental IDs for use as request IDs
8+
*/
9+
class IdGenerator
10+
{
11+
/**
12+
* @var int
13+
*/
14+
public $counter = 1;
15+
16+
/**
17+
* Returns a unique ID
18+
*
19+
* @return int
20+
*/
21+
public function generate()
22+
{
23+
return $this->counter++;
24+
}
25+
}

src/LanguageClient.php

+5-9
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@
33

44
namespace LanguageServer;
55

6-
use LanguageServer\Client\TextDocument;
7-
use LanguageServer\Client\Window;
8-
96
class LanguageClient
107
{
118
/**
@@ -21,13 +18,12 @@ class LanguageClient
2118
* @var Client\Window
2219
*/
2320
public $window;
24-
25-
private $protocolWriter;
2621

27-
public function __construct(ProtocolWriter $writer)
22+
public function __construct(ProtocolReader $reader, ProtocolWriter $writer)
2823
{
29-
$this->protocolWriter = $writer;
30-
$this->textDocument = new TextDocument($writer);
31-
$this->window = new Window($writer);
24+
$handler = new ClientHandler($reader, $writer);
25+
26+
$this->textDocument = new Client\TextDocument($handler);
27+
$this->window = new Client\Window($handler);
3228
}
3329
}

src/LanguageServer.php

+6-4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
namespace LanguageServer;
55

6-
use LanguageServer\Server\TextDocument;
76
use LanguageServer\Protocol\{
87
ServerCapabilities,
98
ClientCapabilities,
@@ -15,7 +14,6 @@
1514
};
1615
use AdvancedJsonRpc;
1716
use Sabre\Event\Loop;
18-
use JsonMapper;
1917
use Exception;
2018
use Throwable;
2119

@@ -56,7 +54,11 @@ public function __construct(ProtocolReader $reader, ProtocolWriter $writer)
5654
{
5755
parent::__construct($this, '/');
5856
$this->protocolReader = $reader;
59-
$this->protocolReader->onMessage(function (Message $msg) {
57+
$this->protocolReader->on('message', function (Message $msg) {
58+
// Ignore responses, this is the handler for requests and notifications
59+
if (AdvancedJsonRpc\Response::isResponse($msg->body)) {
60+
return;
61+
}
6062
$result = null;
6163
$error = null;
6264
try {
@@ -81,7 +83,7 @@ public function __construct(ProtocolReader $reader, ProtocolWriter $writer)
8183
}
8284
});
8385
$this->protocolWriter = $writer;
84-
$this->client = new LanguageClient($writer);
86+
$this->client = new LanguageClient($reader, $writer);
8587

8688
$this->project = new Project($this->client);
8789

src/ProtocolReader.php

+8-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@
33

44
namespace LanguageServer;
55

6-
interface ProtocolReader
6+
use Sabre\Event\EmitterInterface;
7+
8+
/**
9+
* Must emit a "message" event with a Protocol\Message object as parameter
10+
* when a message comes in
11+
*/
12+
interface ProtocolReader extends EmitterInterface
713
{
8-
public function onMessage(callable $listener);
14+
915
}

src/ProtocolStreamReader.php

+4-17
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55

66
use LanguageServer\Protocol\Message;
77
use AdvancedJsonRpc\Message as MessageBody;
8-
use Sabre\Event\Loop;
8+
use Sabre\Event\{Loop, Emitter};
99

10-
class ProtocolStreamReader implements ProtocolReader
10+
class ProtocolStreamReader extends Emitter implements ProtocolReader
1111
{
1212
const PARSE_HEADERS = 1;
1313
const PARSE_BODY = 2;
@@ -17,7 +17,6 @@ class ProtocolStreamReader implements ProtocolReader
1717
private $buffer = '';
1818
private $headers = [];
1919
private $contentLength;
20-
private $listener;
2120

2221
/**
2322
* @param resource $input
@@ -43,11 +42,8 @@ public function __construct($input)
4342
break;
4443
case self::PARSE_BODY:
4544
if (strlen($this->buffer) === $this->contentLength) {
46-
if (isset($this->listener)) {
47-
$msg = new Message(MessageBody::parse($this->buffer), $this->headers);
48-
$listener = $this->listener;
49-
$listener($msg);
50-
}
45+
$msg = new Message(MessageBody::parse($this->buffer), $this->headers);
46+
$this->emit('message', [$msg]);
5147
$this->parsingMode = self::PARSE_HEADERS;
5248
$this->headers = [];
5349
$this->buffer = '';
@@ -57,13 +53,4 @@ public function __construct($input)
5753
}
5854
});
5955
}
60-
61-
/**
62-
* @param callable $listener Is called with a Message object
63-
* @return void
64-
*/
65-
public function onMessage(callable $listener)
66-
{
67-
$this->listener = $listener;
68-
}
6956
}

src/ProtocolStreamWriter.php

+2-5
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,9 @@ public function __construct($output)
3131
}
3232

3333
/**
34-
* Sends a Message to the client
35-
*
36-
* @param Message $msg
37-
* @return Promise Resolved when the message has been fully written out to the output stream
34+
* {@inheritdoc}
3835
*/
39-
public function write(Message $msg)
36+
public function write(Message $msg): Promise
4037
{
4138
// if the message queue is currently empty, register a write handler.
4239
if (empty($this->messages)) {

src/ProtocolWriter.php

+8-1
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,15 @@
44
namespace LanguageServer;
55

66
use LanguageServer\Protocol\Message;
7+
use Sabre\Event\Promise;
78

89
interface ProtocolWriter
910
{
10-
public function write(Message $msg);
11+
/**
12+
* Sends a Message to the client
13+
*
14+
* @param Message $msg
15+
* @return Promise Resolved when the message has been fully written out to the output stream
16+
*/
17+
public function write(Message $msg): Promise;
1118
}

0 commit comments

Comments
 (0)