1
0
Fork 0
pull/6/head v1.2.1
Felix Becker 2016-08-23 11:21:37 +02:00
parent 770a7aa90d
commit 138b529df1
10 changed files with 160 additions and 30 deletions

View File

@ -2,17 +2,32 @@
namespace LanguageServer; namespace LanguageServer;
use LanguageServer\Protocol\{ProtocolServer, ServerCapabilities}; use LanguageServer\Protocol\{ProtocolServer, ServerCapabilities, TextDocumentSyncKind};
use LanguageServer\Protocol\Methods\Initialize\{InitializeRequest, InitializeResult, InitializeResponse}; use LanguageServer\Protocol\Methods\{InitializeParams, InitializeResult};
class LanguageServer extends ProtocolServer class LanguageServer extends ProtocolServer
{ {
public function initialize(InitializeRequest $req): InitializeResponse protected $textDocument;
protected $telemetry;
protected $window;
protected $workspace;
protected $completionItem;
protected $codeLens;
public function __construct($input, $output)
{ {
$result = new InitializeResult; parent::__construct($input, $output);
$result->capabilites = new ServerCapabilities; $this->textDocument = new TextDocumentManager();
return new InitializeResponse($result);
} }
public function shutdown protected function initialize(InitializeParams $req): InitializeResult
{
$capabilities = new ServerCapabilites();
// Ask the client to return always full documents (because we need to rebuild the AST from scratch)
$capabilities->textDocumentSync = TextDocumentSyncKind::FULL;
// Support "Find all symbols"
$capabilities->documentSymbolProvider = true;
$result = new InitializeResult($capabilities);
return $result;
}
} }

View File

@ -7,7 +7,7 @@ use LanguageServer\Protocol\Notification;
class CancelRequestNotification extends Notification class CancelRequestNotification extends Notification
{ {
/** /**
* @var CancelParams * @var CancelRequestParams
*/ */
public $params; public $params;
} }

View File

@ -1,6 +1,6 @@
<?php <?php
namespace LanguageServer\Protocol\Methods\Initialize; namespace LanguageServer\Protocol\Methods;
class InitializeParams class InitializeParams
{ {

View File

@ -1,6 +1,6 @@
<?php <?php
namespace LanguageServer\Protocol\Methods\Initialize; namespace LanguageServer\Protocol\Methods;
use LanguageServer\Protocol\Request; use LanguageServer\Protocol\Request;

View File

@ -1,6 +1,6 @@
<?php <?php
namespace LanguageServer\Protocol\Methods\Initialize; namespace LanguageServer\Protocol\Methods;
class InitializeResponse class InitializeResponse
{ {

View File

@ -3,6 +3,7 @@
namespace LanguageServer\Protocol\Methods\Initialize; namespace LanguageServer\Protocol\Methods\Initialize;
use LanguageServer\Protocol\Result; use LanguageServer\Protocol\Result;
use LanguageServer\Protocol\ServerCapabilities;
class InitializeResult extends Result class InitializeResult extends Result
{ {
@ -12,4 +13,12 @@ class InitializeResult extends Result
* @var LanguageServer\Protocol\ServerCapabilities * @var LanguageServer\Protocol\ServerCapabilities
*/ */
public $capabilites; public $capabilites;
/**
* @param LanguageServer\Protocol\ServerCapabilities $capabilites
*/
public function __construct(ServerCapabilities $capabilites = null)
{
$this->capabilities = $capabilites ?? new ServerCapabilities();
}
} }

View File

@ -3,7 +3,7 @@
namespace LanguageServer\Protocol; namespace LanguageServer\Protocol;
use Sabre\Event\Loop; use Sabre\Event\Loop;
use LanguageServer\Protocol\Methods\Initialize\{InitializeRequest, InitializeResponse}; use LanguageServer\Protocol\Methods\{InitializeRequest, InitializeResponse};
abstract class ParsingMode { abstract class ParsingMode {
const HEADERS = 1; const HEADERS = 1;
@ -29,11 +29,16 @@ abstract class ProtocolServer
$this->output = $output; $this->output = $output;
} }
/**
* Starts an event loop and listens on the provided input stream for messages, invoking method handlers and
* responding on the provided output stream
*
* @return void
*/
public function listen() public function listen()
{ {
Loop\addReadStream($this->input, function() { Loop\addReadStream($this->input, function() {
$this->buffer .= fgetc($this->output); $this->buffer .= fgetc($this->input);
switch ($parsingMode) { switch ($parsingMode) {
case ParsingMode::HEADERS: case ParsingMode::HEADERS:
if (substr($buffer, -4) === '\r\n\r\n') { if (substr($buffer, -4) === '\r\n\r\n') {
@ -48,18 +53,28 @@ abstract class ProtocolServer
break; break;
case ParsingMode::BODY: case ParsingMode::BODY:
if (strlen($buffer) === $contentLength) { if (strlen($buffer) === $contentLength) {
$req = Message::parse($body, Request::class); $msg = Message::parse($body, Request::class);
if (!method_exists($this, $req->method)) { $result = null;
$this->sendResponse(new Response(null, new ResponseError("Method {$req->method} is not implemented", ErrorCode::METHOD_NOT_FOUND, $e))); $err = null;
} else { try {
try { // Invoke the method handler to get a result
$result = $this->{$req->method}($req->params); $result = $this->dispatch($msg);
$this->sendResponse(new Response($result)); } catch (ResponseError $e) {
} catch (ResponseError $e) { // If a ResponseError is thrown, send it back in the Response (result will be null)
$this->sendResponse(new Response(null, $e)); $err = $e;
} catch (Throwable $e) { } catch (Throwable $e) {
$this->sendResponse(new Response(null, new ResponseError($e->getMessage(), $e->getCode(), null, $e))); // If an unexpected error occured, send back an INTERNAL_ERROR error response (result will be null)
} $err = new ResponseError(
$e->getMessage(),
$e->getCode() === 0 ? ErrorCode::INTERNAL_ERROR : $e->getCode(),
null,
$e
);
}
// Only send a Response for a Request
// Notifications do not send Responses
if ($msg instanceof Request) {
$this->send(new Response($msg->id, $msg->method, $result, $err));
} }
$this->parsingMode = ParsingMode::HEADERS; $this->parsingMode = ParsingMode::HEADERS;
$this->buffer = ''; $this->buffer = '';
@ -71,10 +86,80 @@ abstract class ProtocolServer
Loop\run(); Loop\run();
} }
public function sendResponse(Response $res) /**
* Calls the appropiate method handler for an incoming Message
*
* @param Message $msg The incoming message
* @return Result|void
*/
private function dispatch(Message $msg)
{ {
fwrite($this->output, json_encode($res)); // Find out the object and function that should be called
$obj = $this;
$parts = explode('/', $msg->method);
// The function to call is always the last part of the method
$fn = array_pop($parts);
// For namespaced methods like textDocument/didOpen, call the didOpen method on the $textDocument property
// For simple methods like initialize, shutdown, exit, this loop will simply not be entered and $obj will be
// the server ($this)
foreach ($parts as $part) {
if (!isset($obj->$part)) {
throw new ResponseError("Method {$msg->method} is not implemented", ErrorCode::METHOD_NOT_FOUND);
}
$obj = $obj->$part;
}
// Check if $fn exists on $obj
if (!method_exists($obj, $fn)) {
throw new ResponseError("Method {$msg->method} is not implemented", ErrorCode::METHOD_NOT_FOUND);
}
// Invoke the method handler and return the result
return $obj->$fn($msg->params);
} }
abstract public function initialize(InitializeRequest $req): InitializeResponse; /**
* Sends a Message to the client (for example a Response)
*
* @param Message $msg
* @return void
*/
private function send(Message $msg)
{
fwrite($this->output, json_encode($msg));
}
/**
* The initialize request is sent as the first request from the client to the server.
* The default implementation returns no capabilities.
*
* @param LanguageServer\Protocol\Methods\InitializeParams $params
* @return LanguageServer\Protocol\Methods\IntializeResult
*/
protected function initialize(InitializeParams $params): InitializeResult
{
return new InitializeResult();
}
/**
* The shutdown request is sent from the client to the server. It asks the server to shut down, but to not exit
* (otherwise the response might not be delivered correctly to the client). There is a separate exit notification that
* asks the server to exit.
* The default implementation does nothing.
*
* @return void
*/
protected function shutdown()
{
}
/**
* A notification to ask the server to exit its process.
* The default implementation does exactly this.
*
* @return void
*/
protected function exit()
{
exit(0);
}
} }

View File

@ -24,7 +24,7 @@ class Response extends Message
*/ */
public $error; public $error;
public function __construct($result, ResponseError $error = null) public function __construct($id, string $method, $result, ResponseError $error = null)
{ {
$this->result = $result; $this->result = $result;
$this->error = $error; $this->error = $error;

View File

@ -0,0 +1,21 @@
<?php
namespace LanguageServer;
/**
* Provides method handlers for all textDocument/* methods
*/
class TextDocumentManager
{
/**
* The document symbol request is sent from the client to the server to list all symbols found in a given text
* document.
*
* @param LanguageServer\Protocol\Methods\TextDocument\DocumentSymbolParams $params
* @return SymbolInformation[]
*/
public function documentSymbol(DocumentSymbolParams $params): array
{
}
}