Handle Client responses (#128)
							parent
							
								
									ff0a35d833
								
							
						
					
					
						commit
						04ef6c8adf
					
				| 
						 | 
					@ -3,9 +3,9 @@ declare(strict_types = 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace LanguageServer\Client;
 | 
					namespace LanguageServer\Client;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use AdvancedJsonRpc\Notification as NotificationBody;
 | 
					use LanguageServer\ClientHandler;
 | 
				
			||||||
use LanguageServer\ProtocolWriter;
 | 
					 | 
				
			||||||
use LanguageServer\Protocol\Message;
 | 
					use LanguageServer\Protocol\Message;
 | 
				
			||||||
 | 
					use Sabre\Event\Promise;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Provides method handlers for all textDocument/* methods
 | 
					 * Provides method handlers for all textDocument/* methods
 | 
				
			||||||
| 
						 | 
					@ -13,13 +13,13 @@ use LanguageServer\Protocol\Message;
 | 
				
			||||||
class TextDocument
 | 
					class TextDocument
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * @var ProtocolWriter
 | 
					     * @var ClientHandler
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    private $protocolWriter;
 | 
					    private $handler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function __construct(ProtocolWriter $protocolWriter)
 | 
					    public function __construct(ClientHandler $handler)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $this->protocolWriter = $protocolWriter;
 | 
					        $this->handler = $handler;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
| 
						 | 
					@ -27,15 +27,13 @@ class TextDocument
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param string $uri
 | 
					     * @param string $uri
 | 
				
			||||||
     * @param Diagnostic[] $diagnostics
 | 
					     * @param Diagnostic[] $diagnostics
 | 
				
			||||||
 | 
					     * @return Promise <void>
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function publishDiagnostics(string $uri, array $diagnostics)
 | 
					    public function publishDiagnostics(string $uri, array $diagnostics): Promise
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $this->protocolWriter->write(new Message(new NotificationBody(
 | 
					        return $this->handler->notify('textDocument/publishDiagnostics', [
 | 
				
			||||||
            'textDocument/publishDiagnostics',
 | 
					            'uri' => $uri,
 | 
				
			||||||
            (object)[
 | 
					            'diagnostics' => $diagnostics
 | 
				
			||||||
                'uri' => $uri,
 | 
					        ]);
 | 
				
			||||||
                'diagnostics' => $diagnostics
 | 
					 | 
				
			||||||
            ]
 | 
					 | 
				
			||||||
        )));
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,9 +3,9 @@ declare(strict_types = 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace LanguageServer\Client;
 | 
					namespace LanguageServer\Client;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use AdvancedJsonRpc\Notification as NotificationBody;
 | 
					use LanguageServer\ClientHandler;
 | 
				
			||||||
use LanguageServer\ProtocolWriter;
 | 
					 | 
				
			||||||
use LanguageServer\Protocol\Message;
 | 
					use LanguageServer\Protocol\Message;
 | 
				
			||||||
 | 
					use Sabre\Event\Promise;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Provides method handlers for all window/* methods
 | 
					 * Provides method handlers for all window/* methods
 | 
				
			||||||
| 
						 | 
					@ -13,30 +13,26 @@ use LanguageServer\Protocol\Message;
 | 
				
			||||||
class Window
 | 
					class Window
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * @var ProtocolWriter
 | 
					     * @var ClientHandler
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    private $protocolWriter;
 | 
					    private $handler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function __construct(ProtocolWriter $protocolWriter)
 | 
					    public function __construct(ClientHandler $handler)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $this->protocolWriter = $protocolWriter;
 | 
					        $this->handler = $handler;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 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.
 | 
					     * 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.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param int $type
 | 
					     * @param int $type
 | 
				
			||||||
     * @param string $message
 | 
					     * @param string $message
 | 
				
			||||||
 | 
					     * @return Promise <void>
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function showMessage(int $type, string $message)
 | 
					    public function showMessage(int $type, string $message): Promise
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $this->protocolWriter->write(new Message(new NotificationBody(
 | 
					        return $this->handler->notify('window/showMessage', ['type' => $type, 'message' => $message]);
 | 
				
			||||||
            'window/showMessage',
 | 
					 | 
				
			||||||
            (object)[
 | 
					 | 
				
			||||||
                'type' => $type,
 | 
					 | 
				
			||||||
                'message' => $message
 | 
					 | 
				
			||||||
            ]
 | 
					 | 
				
			||||||
        )));
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
| 
						 | 
					@ -44,15 +40,10 @@ class Window
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param int $type
 | 
					     * @param int $type
 | 
				
			||||||
     * @param string $message
 | 
					     * @param string $message
 | 
				
			||||||
 | 
					     * @return Promise <void>
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function logMessage(int $type, string $message)
 | 
					    public function logMessage(int $type, string $message): Promise
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $this->protocolWriter->write(new Message(new NotificationBody(
 | 
					        return $this->handler->notify('window/logMessage', ['type' => $type, 'message' => $message]);
 | 
				
			||||||
            'window/logMessage',
 | 
					 | 
				
			||||||
            (object)[
 | 
					 | 
				
			||||||
                'type' => $type,
 | 
					 | 
				
			||||||
                'message' => $message
 | 
					 | 
				
			||||||
            ]
 | 
					 | 
				
			||||||
        )));
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,81 @@
 | 
				
			||||||
 | 
					<?php
 | 
				
			||||||
 | 
					declare(strict_types = 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace LanguageServer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use AdvancedJsonRpc;
 | 
				
			||||||
 | 
					use Sabre\Event\Promise;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ClientHandler
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var ProtocolReader
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public $protocolReader;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var ProtocolWriter
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public $protocolWriter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var IdGenerator
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public $idGenerator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function __construct(ProtocolReader $protocolReader, ProtocolWriter $protocolWriter)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->protocolReader = $protocolReader;
 | 
				
			||||||
 | 
					        $this->protocolWriter = $protocolWriter;
 | 
				
			||||||
 | 
					        $this->idGenerator = new IdGenerator;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Sends a request to the client and returns a promise that is resolved with the result or rejected with the error
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param string $method The method to call
 | 
				
			||||||
 | 
					     * @param array|object $params The method parameters
 | 
				
			||||||
 | 
					     * @return Promise <mixed> Resolved with the result of the request or rejected with an error
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function request(string $method, $params): Promise
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $id = $this->idGenerator->generate();
 | 
				
			||||||
 | 
					        return $this->protocolWriter->write(
 | 
				
			||||||
 | 
					            new Protocol\Message(
 | 
				
			||||||
 | 
					                new AdvancedJsonRpc\Request($id, $method, (object)$params)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        )->then(function () use ($id) {
 | 
				
			||||||
 | 
					            $promise = new Promise;
 | 
				
			||||||
 | 
					            $listener = function (Protocol\Message $msg) use ($id, $promise, &$listener) {
 | 
				
			||||||
 | 
					                if (AdvancedJsonRpc\Response::isResponse($msg->body) && $msg->body->id === $id) {
 | 
				
			||||||
 | 
					                    // Received a response
 | 
				
			||||||
 | 
					                    $this->protocolReader->removeListener('message', $listener);
 | 
				
			||||||
 | 
					                    if (AdvancedJsonRpc\SuccessResponse::isSuccessResponse($msg->body)) {
 | 
				
			||||||
 | 
					                        $promise->fulfill($msg->body->result);
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        $promise->reject($msg->body->error);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            $this->protocolReader->on('message', $listener);
 | 
				
			||||||
 | 
					            return $promise;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Sends a notification to the client
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param string $method The method to call
 | 
				
			||||||
 | 
					     * @param array|object $params The method parameters
 | 
				
			||||||
 | 
					     * @return Promise <null> Will be resolved as soon as the notification has been sent
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function notify(string $method, $params): Promise
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $id = $this->idGenerator->generate();
 | 
				
			||||||
 | 
					        return $this->protocolWriter->write(
 | 
				
			||||||
 | 
					            new Protocol\Message(
 | 
				
			||||||
 | 
					                new AdvancedJsonRpc\Notification($method, (object)$params)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,25 @@
 | 
				
			||||||
 | 
					<?php
 | 
				
			||||||
 | 
					declare(strict_types = 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace LanguageServer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Generates unique, incremental IDs for use as request IDs
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					class IdGenerator
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var int
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public $counter = 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Returns a unique ID
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return int
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function generate()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->counter++;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -3,9 +3,6 @@ declare(strict_types = 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace LanguageServer;
 | 
					namespace LanguageServer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use LanguageServer\Client\TextDocument;
 | 
					 | 
				
			||||||
use LanguageServer\Client\Window;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class LanguageClient
 | 
					class LanguageClient
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
| 
						 | 
					@ -22,12 +19,11 @@ class LanguageClient
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public $window;
 | 
					    public $window;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private $protocolWriter;
 | 
					    public function __construct(ProtocolReader $reader, ProtocolWriter $writer)
 | 
				
			||||||
 | 
					 | 
				
			||||||
    public function __construct(ProtocolWriter $writer)
 | 
					 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $this->protocolWriter = $writer;
 | 
					        $handler = new ClientHandler($reader, $writer);
 | 
				
			||||||
        $this->textDocument = new TextDocument($writer);
 | 
					
 | 
				
			||||||
        $this->window = new Window($writer);
 | 
					        $this->textDocument = new Client\TextDocument($handler);
 | 
				
			||||||
 | 
					        $this->window = new Client\Window($handler);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,6 @@ declare(strict_types = 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace LanguageServer;
 | 
					namespace LanguageServer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use LanguageServer\Server\TextDocument;
 | 
					 | 
				
			||||||
use LanguageServer\Protocol\{
 | 
					use LanguageServer\Protocol\{
 | 
				
			||||||
    ServerCapabilities,
 | 
					    ServerCapabilities,
 | 
				
			||||||
    ClientCapabilities,
 | 
					    ClientCapabilities,
 | 
				
			||||||
| 
						 | 
					@ -15,7 +14,6 @@ use LanguageServer\Protocol\{
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
use AdvancedJsonRpc;
 | 
					use AdvancedJsonRpc;
 | 
				
			||||||
use Sabre\Event\Loop;
 | 
					use Sabre\Event\Loop;
 | 
				
			||||||
use JsonMapper;
 | 
					 | 
				
			||||||
use Exception;
 | 
					use Exception;
 | 
				
			||||||
use Throwable;
 | 
					use Throwable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -56,7 +54,11 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        parent::__construct($this, '/');
 | 
					        parent::__construct($this, '/');
 | 
				
			||||||
        $this->protocolReader = $reader;
 | 
					        $this->protocolReader = $reader;
 | 
				
			||||||
        $this->protocolReader->onMessage(function (Message $msg) {
 | 
					        $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;
 | 
					            $result = null;
 | 
				
			||||||
            $error = null;
 | 
					            $error = null;
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
| 
						 | 
					@ -81,7 +83,7 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        $this->protocolWriter = $writer;
 | 
					        $this->protocolWriter = $writer;
 | 
				
			||||||
        $this->client = new LanguageClient($writer);
 | 
					        $this->client = new LanguageClient($reader, $writer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $this->project = new Project($this->client);
 | 
					        $this->project = new Project($this->client);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,13 @@ declare(strict_types = 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace LanguageServer;
 | 
					namespace LanguageServer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface ProtocolReader
 | 
					use Sabre\Event\EmitterInterface;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Must emit a "message" event with a Protocol\Message object as parameter
 | 
				
			||||||
 | 
					 * when a message comes in
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					interface ProtocolReader extends EmitterInterface
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public function onMessage(callable $listener);
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,9 +5,9 @@ namespace LanguageServer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use LanguageServer\Protocol\Message;
 | 
					use LanguageServer\Protocol\Message;
 | 
				
			||||||
use AdvancedJsonRpc\Message as MessageBody;
 | 
					use AdvancedJsonRpc\Message as MessageBody;
 | 
				
			||||||
use Sabre\Event\Loop;
 | 
					use Sabre\Event\{Loop, Emitter};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ProtocolStreamReader implements ProtocolReader
 | 
					class ProtocolStreamReader extends Emitter implements ProtocolReader
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    const PARSE_HEADERS = 1;
 | 
					    const PARSE_HEADERS = 1;
 | 
				
			||||||
    const PARSE_BODY = 2;
 | 
					    const PARSE_BODY = 2;
 | 
				
			||||||
| 
						 | 
					@ -17,7 +17,6 @@ class ProtocolStreamReader implements ProtocolReader
 | 
				
			||||||
    private $buffer = '';
 | 
					    private $buffer = '';
 | 
				
			||||||
    private $headers = [];
 | 
					    private $headers = [];
 | 
				
			||||||
    private $contentLength;
 | 
					    private $contentLength;
 | 
				
			||||||
    private $listener;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * @param resource $input
 | 
					     * @param resource $input
 | 
				
			||||||
| 
						 | 
					@ -43,11 +42,8 @@ class ProtocolStreamReader implements ProtocolReader
 | 
				
			||||||
                        break;
 | 
					                        break;
 | 
				
			||||||
                    case self::PARSE_BODY:
 | 
					                    case self::PARSE_BODY:
 | 
				
			||||||
                        if (strlen($this->buffer) === $this->contentLength) {
 | 
					                        if (strlen($this->buffer) === $this->contentLength) {
 | 
				
			||||||
                            if (isset($this->listener)) {
 | 
					                            $msg = new Message(MessageBody::parse($this->buffer), $this->headers);
 | 
				
			||||||
                                $msg = new Message(MessageBody::parse($this->buffer), $this->headers);
 | 
					                            $this->emit('message', [$msg]);
 | 
				
			||||||
                                $listener = $this->listener;
 | 
					 | 
				
			||||||
                                $listener($msg);
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                            $this->parsingMode = self::PARSE_HEADERS;
 | 
					                            $this->parsingMode = self::PARSE_HEADERS;
 | 
				
			||||||
                            $this->headers = [];
 | 
					                            $this->headers = [];
 | 
				
			||||||
                            $this->buffer = '';
 | 
					                            $this->buffer = '';
 | 
				
			||||||
| 
						 | 
					@ -57,13 +53,4 @@ class ProtocolStreamReader implements ProtocolReader
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * @param callable $listener Is called with a Message object
 | 
					 | 
				
			||||||
     * @return void
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public function onMessage(callable $listener)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        $this->listener = $listener;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,12 +31,9 @@ class ProtocolStreamWriter implements ProtocolWriter
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Sends a Message to the client
 | 
					     * {@inheritdoc}
 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param Message $msg
 | 
					 | 
				
			||||||
     * @return Promise Resolved when the message has been fully written out to the output stream
 | 
					 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function write(Message $msg)
 | 
					    public function write(Message $msg): Promise
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        // if the message queue is currently empty, register a write handler.
 | 
					        // if the message queue is currently empty, register a write handler.
 | 
				
			||||||
        if (empty($this->messages)) {
 | 
					        if (empty($this->messages)) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,8 +4,15 @@ declare(strict_types = 1);
 | 
				
			||||||
namespace LanguageServer;
 | 
					namespace LanguageServer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use LanguageServer\Protocol\Message;
 | 
					use LanguageServer\Protocol\Message;
 | 
				
			||||||
 | 
					use Sabre\Event\Promise;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface ProtocolWriter
 | 
					interface ProtocolWriter
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public function write(Message $msg);
 | 
					    /**
 | 
				
			||||||
 | 
					     * Sends a Message to the client
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param Message $msg
 | 
				
			||||||
 | 
					     * @return Promise Resolved when the message has been fully written out to the output stream
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function write(Message $msg): Promise;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,43 @@
 | 
				
			||||||
 | 
					<?php
 | 
				
			||||||
 | 
					declare(strict_types = 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace LanguageServer\Tests;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use PHPUnit\Framework\TestCase;
 | 
				
			||||||
 | 
					use LanguageServer\ClientHandler;
 | 
				
			||||||
 | 
					use LanguageServer\Protocol\Message;
 | 
				
			||||||
 | 
					use AdvancedJsonRpc;
 | 
				
			||||||
 | 
					use Sabre\Event\Loop;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ClientHandlerTest extends TestCase
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public function testRequest()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $reader = new MockProtocolStream;
 | 
				
			||||||
 | 
					        $writer = new MockProtocolStream;
 | 
				
			||||||
 | 
					        $handler = new ClientHandler($reader, $writer);
 | 
				
			||||||
 | 
					        $writer->once('message', function (Message $msg) use ($reader) {
 | 
				
			||||||
 | 
					            // Respond to request
 | 
				
			||||||
 | 
					            Loop\setTimeout(function () use ($reader, $msg) {
 | 
				
			||||||
 | 
					                $reader->write(new Message(new AdvancedJsonRpc\SuccessResponse($msg->body->id, 'pong')));
 | 
				
			||||||
 | 
					            }, 0);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        $handler->request('testMethod', ['ping'])->then(function ($result) {
 | 
				
			||||||
 | 
					            $this->assertEquals('pong', $result);
 | 
				
			||||||
 | 
					        })->wait();
 | 
				
			||||||
 | 
					        // No event listeners
 | 
				
			||||||
 | 
					        $this->assertEquals([], $reader->listeners('message'));
 | 
				
			||||||
 | 
					        $this->assertEquals([], $writer->listeners('message'));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function testNotify()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $reader = new MockProtocolStream;
 | 
				
			||||||
 | 
					        $writer = new MockProtocolStream;
 | 
				
			||||||
 | 
					        $handler = new ClientHandler($reader, $writer);
 | 
				
			||||||
 | 
					        $handler->notify('testMethod', ['ping'])->wait();
 | 
				
			||||||
 | 
					        // No event listeners
 | 
				
			||||||
 | 
					        $this->assertEquals([], $reader->listeners('message'));
 | 
				
			||||||
 | 
					        $this->assertEquals([], $writer->listeners('message'));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -16,7 +16,7 @@ class LanguageServerTest extends TestCase
 | 
				
			||||||
        $writer = new MockProtocolStream();
 | 
					        $writer = new MockProtocolStream();
 | 
				
			||||||
        $server = new LanguageServer($reader, $writer);
 | 
					        $server = new LanguageServer($reader, $writer);
 | 
				
			||||||
        $msg = null;
 | 
					        $msg = null;
 | 
				
			||||||
        $writer->onMessage(function (Message $message) use (&$msg) {
 | 
					        $writer->on('message', function (Message $message) use (&$msg) {
 | 
				
			||||||
            $msg = $message;
 | 
					            $msg = $message;
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        $reader->write(new Message(new AdvancedJsonRpc\Request(1, 'initialize', [
 | 
					        $reader->write(new Message(new AdvancedJsonRpc\Request(1, 'initialize', [
 | 
				
			||||||
| 
						 | 
					@ -24,7 +24,7 @@ class LanguageServerTest extends TestCase
 | 
				
			||||||
            'processId' => getmypid(),
 | 
					            'processId' => getmypid(),
 | 
				
			||||||
            'capabilities' => new ClientCapabilities()
 | 
					            'capabilities' => new ClientCapabilities()
 | 
				
			||||||
        ])));
 | 
					        ])));
 | 
				
			||||||
        $this->assertNotNull($msg, 'onMessage callback should be called');
 | 
					        $this->assertNotNull($msg, 'message event should be emitted');
 | 
				
			||||||
        $this->assertInstanceOf(AdvancedJsonRpc\SuccessResponse::class, $msg->body);
 | 
					        $this->assertInstanceOf(AdvancedJsonRpc\SuccessResponse::class, $msg->body);
 | 
				
			||||||
        $this->assertEquals((object)[
 | 
					        $this->assertEquals((object)[
 | 
				
			||||||
            'capabilities' => (object)[
 | 
					            'capabilities' => (object)[
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,34 +5,22 @@ namespace LanguageServer\Tests;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use LanguageServer\{ProtocolReader, ProtocolWriter};
 | 
					use LanguageServer\{ProtocolReader, ProtocolWriter};
 | 
				
			||||||
use LanguageServer\Protocol\Message;
 | 
					use LanguageServer\Protocol\Message;
 | 
				
			||||||
 | 
					use Sabre\Event\{Emitter, Promise};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * A fake duplex protocol stream
 | 
					 * A fake duplex protocol stream
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
class MockProtocolStream implements ProtocolReader, ProtocolWriter
 | 
					class MockProtocolStream extends Emitter implements ProtocolReader, ProtocolWriter
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    private $listener;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Sends a Message to the client
 | 
					     * Sends a Message to the client
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param Message $msg
 | 
					     * @param Message $msg
 | 
				
			||||||
     * @return void
 | 
					     * @return void
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function write(Message $msg)
 | 
					    public function write(Message $msg): Promise
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        if (isset($this->listener)) {
 | 
					        $this->emit('message', [Message::parse((string)$msg)]);
 | 
				
			||||||
            $listener = $this->listener;
 | 
					        return Promise\resolve(null);
 | 
				
			||||||
            $listener(Message::parse((string)$msg));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * @param callable $listener Is called with a Message object
 | 
					 | 
				
			||||||
     * @return void
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public function onMessage(callable $listener)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        $this->listener = $listener;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,7 +15,7 @@ class DefinitionCollectorTest extends TestCase
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public function testCollectsSymbols()
 | 
					    public function testCollectsSymbols()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $client = new LanguageClient(new MockProtocolStream());
 | 
					        $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
 | 
				
			||||||
        $project = new Project($client);
 | 
					        $project = new Project($client);
 | 
				
			||||||
        $parser = new Parser;
 | 
					        $parser = new Parser;
 | 
				
			||||||
        $uri = pathToUri(realpath(__DIR__ . '/../../fixtures/symbols.php'));
 | 
					        $uri = pathToUri(realpath(__DIR__ . '/../../fixtures/symbols.php'));
 | 
				
			||||||
| 
						 | 
					@ -54,7 +54,7 @@ class DefinitionCollectorTest extends TestCase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function testDoesNotCollectReferences()
 | 
					    public function testDoesNotCollectReferences()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $client = new LanguageClient(new MockProtocolStream());
 | 
					        $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
 | 
				
			||||||
        $project = new Project($client);
 | 
					        $project = new Project($client);
 | 
				
			||||||
        $parser = new Parser;
 | 
					        $parser = new Parser;
 | 
				
			||||||
        $uri = pathToUri(realpath(__DIR__ . '/../../fixtures/references.php'));
 | 
					        $uri = pathToUri(realpath(__DIR__ . '/../../fixtures/references.php'));
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19,7 +19,7 @@ class PhpDocumentTest extends TestCase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function setUp()
 | 
					    public function setUp()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $this->project = new Project(new LanguageClient(new MockProtocolStream()));
 | 
					        $this->project = new Project(new LanguageClient(new MockProtocolStream, new MockProtocolStream));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function testParsesVariableVariables()
 | 
					    public function testParsesVariableVariables()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19,7 +19,7 @@ class ProjectTest extends TestCase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function setUp()
 | 
					    public function setUp()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $this->project = new Project(new LanguageClient(new MockProtocolStream()));
 | 
					        $this->project = new Project(new LanguageClient(new MockProtocolStream, new MockProtocolStream));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function testGetDocumentLoadsDocument()
 | 
					    public function testGetDocumentLoadsDocument()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,7 +18,7 @@ class ProtocolStreamReaderTest extends TestCase
 | 
				
			||||||
        $writeHandle = fopen($tmpfile, 'w');
 | 
					        $writeHandle = fopen($tmpfile, 'w');
 | 
				
			||||||
        $reader = new ProtocolStreamReader(fopen($tmpfile, 'r'));
 | 
					        $reader = new ProtocolStreamReader(fopen($tmpfile, 'r'));
 | 
				
			||||||
        $msg = null;
 | 
					        $msg = null;
 | 
				
			||||||
        $reader->onMessage(function (Message $message) use (&$msg) {
 | 
					        $reader->on('message', function (Message $message) use (&$msg) {
 | 
				
			||||||
            $msg = $message;
 | 
					            $msg = $message;
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        $ret = fwrite($writeHandle, (string)new Message(new RequestBody(1, 'aMethod', ['arg' => 'Hello World'])));
 | 
					        $ret = fwrite($writeHandle, (string)new Message(new RequestBody(1, 'aMethod', ['arg' => 'Hello World'])));
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -42,7 +42,7 @@ abstract class ServerTestCase extends TestCase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function setUp()
 | 
					    public function setUp()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $client             = new LanguageClient(new MockProtocolStream());
 | 
					        $client             = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
 | 
				
			||||||
        $this->project      = new Project($client);
 | 
					        $this->project      = new Project($client);
 | 
				
			||||||
        $this->textDocument = new Server\TextDocument($this->project, $client);
 | 
					        $this->textDocument = new Server\TextDocument($this->project, $client);
 | 
				
			||||||
        $this->workspace    = new Server\Workspace($this->project, $client);
 | 
					        $this->workspace    = new Server\Workspace($this->project, $client);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,7 +12,7 @@ class GlobalFallbackTest extends ServerTestCase
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public function setUp()
 | 
					    public function setUp()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $client = new LanguageClient(new MockProtocolStream());
 | 
					        $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
 | 
				
			||||||
        $project = new Project($client);
 | 
					        $project = new Project($client);
 | 
				
			||||||
        $this->textDocument = new Server\TextDocument($project, $client);
 | 
					        $this->textDocument = new Server\TextDocument($project, $client);
 | 
				
			||||||
        $project->openDocument('global_fallback', file_get_contents(__DIR__ . '/../../../../fixtures/global_fallback.php'));
 | 
					        $project->openDocument('global_fallback', file_get_contents(__DIR__ . '/../../../../fixtures/global_fallback.php'));
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19,7 +19,7 @@ class DidChangeTest extends TestCase
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public function test()
 | 
					    public function test()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $client = new LanguageClient(new MockProtocolStream());
 | 
					        $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
 | 
				
			||||||
        $project = new Project($client);
 | 
					        $project = new Project($client);
 | 
				
			||||||
        $textDocument = new Server\TextDocument($project, $client);
 | 
					        $textDocument = new Server\TextDocument($project, $client);
 | 
				
			||||||
        $phpDocument = $project->openDocument('whatever', "<?php\necho 'Hello, World'\n");
 | 
					        $phpDocument = $project->openDocument('whatever', "<?php\necho 'Hello, World'\n");
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,7 +13,7 @@ class DidCloseTest extends TestCase
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public function test()
 | 
					    public function test()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $client = new LanguageClient(new MockProtocolStream());
 | 
					        $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
 | 
				
			||||||
        $project = new Project($client);
 | 
					        $project = new Project($client);
 | 
				
			||||||
        $textDocument = new Server\TextDocument($project, $client);
 | 
					        $textDocument = new Server\TextDocument($project, $client);
 | 
				
			||||||
        $phpDocument = $project->openDocument('whatever', 'hello world');
 | 
					        $phpDocument = $project->openDocument('whatever', 'hello world');
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,14 +18,14 @@ class FormattingTest extends TestCase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function setUp()
 | 
					    public function setUp()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $client = new LanguageClient(new MockProtocolStream());
 | 
					        $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
 | 
				
			||||||
        $project = new Project($client);
 | 
					        $project = new Project($client);
 | 
				
			||||||
        $this->textDocument = new Server\TextDocument($project, $client);
 | 
					        $this->textDocument = new Server\TextDocument($project, $client);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function testFormatting()
 | 
					    public function testFormatting()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $client = new LanguageClient(new MockProtocolStream());
 | 
					        $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
 | 
				
			||||||
        $project = new Project($client);
 | 
					        $project = new Project($client);
 | 
				
			||||||
        $textDocument = new Server\TextDocument($project, $client);
 | 
					        $textDocument = new Server\TextDocument($project, $client);
 | 
				
			||||||
        $path = realpath(__DIR__ . '/../../../fixtures/format.php');
 | 
					        $path = realpath(__DIR__ . '/../../../fixtures/format.php');
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,8 +5,9 @@ namespace LanguageServer\Tests\Server\TextDocument;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use PHPUnit\Framework\TestCase;
 | 
					use PHPUnit\Framework\TestCase;
 | 
				
			||||||
use LanguageServer\Tests\MockProtocolStream;
 | 
					use LanguageServer\Tests\MockProtocolStream;
 | 
				
			||||||
use LanguageServer\{Server, Client, LanguageClient, Project};
 | 
					use LanguageServer\{Server, Client, LanguageClient, Project, ClientHandler};
 | 
				
			||||||
use LanguageServer\Protocol\{TextDocumentIdentifier, TextDocumentItem, DiagnosticSeverity};
 | 
					use LanguageServer\Protocol\{TextDocumentIdentifier, TextDocumentItem, DiagnosticSeverity};
 | 
				
			||||||
 | 
					use Sabre\Event\Promise;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ParseErrorsTest extends TestCase
 | 
					class ParseErrorsTest extends TestCase
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
| 
						 | 
					@ -19,17 +20,18 @@ class ParseErrorsTest extends TestCase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function setUp()
 | 
					    public function setUp()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $client = new LanguageClient(new MockProtocolStream());
 | 
					        $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
 | 
				
			||||||
        $client->textDocument = new class($this->args) extends Client\TextDocument {
 | 
					        $client->textDocument = new class($this->args) extends Client\TextDocument {
 | 
				
			||||||
            private $args;
 | 
					            private $args;
 | 
				
			||||||
            public function __construct(&$args)
 | 
					            public function __construct(&$args)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                parent::__construct(new MockProtocolStream());
 | 
					                parent::__construct(new ClientHandler(new MockProtocolStream, new MockProtocolStream));
 | 
				
			||||||
                $this->args = &$args;
 | 
					                $this->args = &$args;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            public function publishDiagnostics(string $uri, array $diagnostics)
 | 
					            public function publishDiagnostics(string $uri, array $diagnostics): Promise
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                $this->args = func_get_args();
 | 
					                $this->args = func_get_args();
 | 
				
			||||||
 | 
					                return Promise\resolve(null);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        $project = new Project($client);
 | 
					        $project = new Project($client);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,7 +13,7 @@ class GlobalFallbackTest extends ServerTestCase
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public function setUp()
 | 
					    public function setUp()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $client = new LanguageClient(new MockProtocolStream());
 | 
					        $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
 | 
				
			||||||
        $project = new Project($client);
 | 
					        $project = new Project($client);
 | 
				
			||||||
        $this->textDocument = new Server\TextDocument($project, $client);
 | 
					        $this->textDocument = new Server\TextDocument($project, $client);
 | 
				
			||||||
        $project->openDocument('global_fallback', file_get_contents(__DIR__ . '/../../../../fixtures/global_fallback.php'));
 | 
					        $project->openDocument('global_fallback', file_get_contents(__DIR__ . '/../../../../fixtures/global_fallback.php'));
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue