parent
770a7aa90d
commit
138b529df1
|
@ -2,17 +2,32 @@
|
|||
|
||||
namespace LanguageServer;
|
||||
|
||||
use LanguageServer\Protocol\{ProtocolServer, ServerCapabilities};
|
||||
use LanguageServer\Protocol\Methods\Initialize\{InitializeRequest, InitializeResult, InitializeResponse};
|
||||
use LanguageServer\Protocol\{ProtocolServer, ServerCapabilities, TextDocumentSyncKind};
|
||||
use LanguageServer\Protocol\Methods\{InitializeParams, InitializeResult};
|
||||
|
||||
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;
|
||||
$result->capabilites = new ServerCapabilities;
|
||||
return new InitializeResponse($result);
|
||||
parent::__construct($input, $output);
|
||||
$this->textDocument = new TextDocumentManager();
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
/**
|
||||
* @var CancelParams
|
||||
* @var CancelRequestParams
|
||||
*/
|
||||
public $params;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace LanguageServer\Protocol\Methods\Initialize;
|
||||
namespace LanguageServer\Protocol\Methods;
|
||||
|
||||
class InitializeParams
|
||||
{
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace LanguageServer\Protocol\Methods\Initialize;
|
||||
namespace LanguageServer\Protocol\Methods;
|
||||
|
||||
use LanguageServer\Protocol\Request;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace LanguageServer\Protocol\Methods\Initialize;
|
||||
namespace LanguageServer\Protocol\Methods;
|
||||
|
||||
class InitializeResponse
|
||||
{
|
|
@ -3,6 +3,7 @@
|
|||
namespace LanguageServer\Protocol\Methods\Initialize;
|
||||
|
||||
use LanguageServer\Protocol\Result;
|
||||
use LanguageServer\Protocol\ServerCapabilities;
|
||||
|
||||
class InitializeResult extends Result
|
||||
{
|
||||
|
@ -12,4 +13,12 @@ class InitializeResult extends Result
|
|||
* @var LanguageServer\Protocol\ServerCapabilities
|
||||
*/
|
||||
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;
|
||||
|
||||
use Sabre\Event\Loop;
|
||||
use LanguageServer\Protocol\Methods\Initialize\{InitializeRequest, InitializeResponse};
|
||||
use LanguageServer\Protocol\Methods\{InitializeRequest, InitializeResponse};
|
||||
|
||||
abstract class ParsingMode {
|
||||
const HEADERS = 1;
|
||||
|
@ -29,11 +29,16 @@ abstract class ProtocolServer
|
|||
$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()
|
||||
{
|
||||
|
||||
Loop\addReadStream($this->input, function() {
|
||||
$this->buffer .= fgetc($this->output);
|
||||
$this->buffer .= fgetc($this->input);
|
||||
switch ($parsingMode) {
|
||||
case ParsingMode::HEADERS:
|
||||
if (substr($buffer, -4) === '\r\n\r\n') {
|
||||
|
@ -48,18 +53,28 @@ abstract class ProtocolServer
|
|||
break;
|
||||
case ParsingMode::BODY:
|
||||
if (strlen($buffer) === $contentLength) {
|
||||
$req = Message::parse($body, Request::class);
|
||||
if (!method_exists($this, $req->method)) {
|
||||
$this->sendResponse(new Response(null, new ResponseError("Method {$req->method} is not implemented", ErrorCode::METHOD_NOT_FOUND, $e)));
|
||||
} else {
|
||||
$msg = Message::parse($body, Request::class);
|
||||
$result = null;
|
||||
$err = null;
|
||||
try {
|
||||
$result = $this->{$req->method}($req->params);
|
||||
$this->sendResponse(new Response($result));
|
||||
// Invoke the method handler to get a result
|
||||
$result = $this->dispatch($msg);
|
||||
} catch (ResponseError $e) {
|
||||
$this->sendResponse(new Response(null, $e));
|
||||
// If a ResponseError is thrown, send it back in the Response (result will be null)
|
||||
$err = $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->buffer = '';
|
||||
|
@ -71,10 +86,80 @@ abstract class ProtocolServer
|
|||
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 function __construct($result, ResponseError $error = null)
|
||||
public function __construct($id, string $method, $result, ResponseError $error = null)
|
||||
{
|
||||
$this->result = $result;
|
||||
$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