parent
770a7aa90d
commit
138b529df1
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ use LanguageServer\Protocol\Notification;
|
||||||
class CancelRequestNotification extends Notification
|
class CancelRequestNotification extends Notification
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var CancelParams
|
* @var CancelRequestParams
|
||||||
*/
|
*/
|
||||||
public $params;
|
public $params;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace LanguageServer\Protocol\Methods\Initialize;
|
namespace LanguageServer\Protocol\Methods;
|
||||||
|
|
||||||
class InitializeParams
|
class InitializeParams
|
||||||
{
|
{
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace LanguageServer\Protocol\Methods\Initialize;
|
namespace LanguageServer\Protocol\Methods;
|
||||||
|
|
||||||
use LanguageServer\Protocol\Request;
|
use LanguageServer\Protocol\Request;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace LanguageServer\Protocol\Methods\Initialize;
|
namespace LanguageServer\Protocol\Methods;
|
||||||
|
|
||||||
class InitializeResponse
|
class InitializeResponse
|
||||||
{
|
{
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue