2016-08-23 09:21:37 +00:00
|
|
|
<?php
|
2016-09-30 09:54:49 +00:00
|
|
|
declare(strict_types = 1);
|
2016-08-23 09:21:37 +00:00
|
|
|
|
2016-09-02 19:13:30 +00:00
|
|
|
namespace LanguageServer\Server;
|
2016-08-23 09:21:37 +00:00
|
|
|
|
2016-11-14 09:25:44 +00:00
|
|
|
use LanguageServer\{LanguageClient, Project, PhpDocument};
|
2016-10-19 10:31:32 +00:00
|
|
|
use PhpParser\PrettyPrinter\Standard as PrettyPrinter;
|
|
|
|
use PhpParser\Node;
|
2016-09-02 19:13:30 +00:00
|
|
|
use LanguageServer\Protocol\{
|
|
|
|
TextDocumentItem,
|
|
|
|
TextDocumentIdentifier,
|
|
|
|
VersionedTextDocumentIdentifier,
|
2016-09-06 10:54:34 +00:00
|
|
|
Position,
|
2016-10-19 10:31:32 +00:00
|
|
|
Range,
|
2016-09-06 10:54:34 +00:00
|
|
|
FormattingOptions,
|
2016-10-08 12:59:08 +00:00
|
|
|
TextEdit,
|
2016-10-11 12:42:56 +00:00
|
|
|
Location,
|
2016-10-11 23:45:15 +00:00
|
|
|
SymbolInformation,
|
2016-10-19 10:31:32 +00:00
|
|
|
ReferenceContext,
|
|
|
|
Hover,
|
|
|
|
MarkedString
|
2016-09-02 19:13:30 +00:00
|
|
|
};
|
2016-11-14 09:25:44 +00:00
|
|
|
use Sabre\Event\Promise;
|
|
|
|
use function Sabre\Event\coroutine;
|
2016-08-25 13:27:14 +00:00
|
|
|
|
2016-08-23 09:21:37 +00:00
|
|
|
/**
|
|
|
|
* Provides method handlers for all textDocument/* methods
|
|
|
|
*/
|
2016-09-02 19:13:30 +00:00
|
|
|
class TextDocument
|
2016-08-23 09:21:37 +00:00
|
|
|
{
|
2016-09-02 19:13:30 +00:00
|
|
|
/**
|
|
|
|
* The lanugage client object to call methods on the client
|
|
|
|
*
|
2016-09-04 10:27:56 +00:00
|
|
|
* @var \LanguageServer\LanguageClient
|
2016-09-02 19:13:30 +00:00
|
|
|
*/
|
|
|
|
private $client;
|
|
|
|
|
2016-10-08 10:51:55 +00:00
|
|
|
/**
|
|
|
|
* @var Project
|
|
|
|
*/
|
2016-09-30 09:30:08 +00:00
|
|
|
private $project;
|
|
|
|
|
2016-10-19 10:31:32 +00:00
|
|
|
/**
|
|
|
|
* @var PrettyPrinter
|
|
|
|
*/
|
|
|
|
private $prettyPrinter;
|
|
|
|
|
2016-09-30 09:30:08 +00:00
|
|
|
public function __construct(Project $project, LanguageClient $client)
|
2016-08-25 13:27:14 +00:00
|
|
|
{
|
2016-09-30 09:30:08 +00:00
|
|
|
$this->project = $project;
|
2016-09-02 19:13:30 +00:00
|
|
|
$this->client = $client;
|
2016-10-19 10:31:32 +00:00
|
|
|
$this->prettyPrinter = new PrettyPrinter();
|
2016-08-25 13:27:14 +00:00
|
|
|
}
|
|
|
|
|
2016-08-23 09:21:37 +00:00
|
|
|
/**
|
|
|
|
* The document symbol request is sent from the client to the server to list all symbols found in a given text
|
|
|
|
* document.
|
|
|
|
*
|
2016-09-04 10:27:56 +00:00
|
|
|
* @param \LanguageServer\Protocol\TextDocumentIdentifier $textDocument
|
2016-11-14 09:25:44 +00:00
|
|
|
* @return Promise <SymbolInformation[]>
|
2016-08-23 09:21:37 +00:00
|
|
|
*/
|
2016-11-14 09:25:44 +00:00
|
|
|
public function documentSymbol(TextDocumentIdentifier $textDocument): Promise
|
2016-08-23 09:21:37 +00:00
|
|
|
{
|
2016-11-14 09:25:44 +00:00
|
|
|
return $this->project->getOrLoadDocument($textDocument->uri)->then(function (PhpDocument $document) {
|
|
|
|
return array_values($document->getSymbols());
|
|
|
|
});
|
2016-08-25 13:27:14 +00:00
|
|
|
}
|
2016-08-23 09:21:37 +00:00
|
|
|
|
2016-08-25 13:27:14 +00:00
|
|
|
/**
|
|
|
|
* The document open notification is sent from the client to the server to signal newly opened text documents. The
|
|
|
|
* document's truth is now managed by the client and the server must not try to read the document's truth using the
|
|
|
|
* document's uri.
|
|
|
|
*
|
2016-09-04 10:27:56 +00:00
|
|
|
* @param \LanguageServer\Protocol\TextDocumentItem $textDocument The document that was opened.
|
2016-08-25 13:27:14 +00:00
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function didOpen(TextDocumentItem $textDocument)
|
|
|
|
{
|
2016-10-11 12:42:56 +00:00
|
|
|
$this->project->openDocument($textDocument->uri, $textDocument->text);
|
2016-08-25 13:27:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The document change notification is sent from the client to the server to signal changes to a text document.
|
|
|
|
*
|
2016-09-04 10:27:56 +00:00
|
|
|
* @param \LanguageServer\Protocol\VersionedTextDocumentIdentifier $textDocument
|
|
|
|
* @param \LanguageServer\Protocol\TextDocumentContentChangeEvent[] $contentChanges
|
2016-08-25 13:27:14 +00:00
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function didChange(VersionedTextDocumentIdentifier $textDocument, array $contentChanges)
|
|
|
|
{
|
2016-09-30 09:30:08 +00:00
|
|
|
$this->project->getDocument($textDocument->uri)->updateContent($contentChanges[0]->text);
|
2016-08-25 13:27:14 +00:00
|
|
|
}
|
|
|
|
|
2016-10-11 12:42:56 +00:00
|
|
|
/**
|
|
|
|
* The document close notification is sent from the client to the server when the document got closed in the client.
|
|
|
|
* The document's truth now exists where the document's uri points to (e.g. if the document's uri is a file uri the
|
|
|
|
* truth now exists on disk).
|
|
|
|
*
|
|
|
|
* @param \LanguageServer\Protocol\TextDocumentItem $textDocument The document that was closed
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function didClose(TextDocumentIdentifier $textDocument)
|
|
|
|
{
|
|
|
|
$this->project->closeDocument($textDocument->uri);
|
|
|
|
}
|
2016-09-06 10:54:34 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The document formatting request is sent from the server to the client to format a whole document.
|
|
|
|
*
|
|
|
|
* @param TextDocumentIdentifier $textDocument The document to format
|
|
|
|
* @param FormattingOptions $options The format options
|
2016-11-14 09:25:44 +00:00
|
|
|
* @return Promise <TextEdit[]>
|
2016-09-06 10:54:34 +00:00
|
|
|
*/
|
|
|
|
public function formatting(TextDocumentIdentifier $textDocument, FormattingOptions $options)
|
|
|
|
{
|
2016-11-14 09:25:44 +00:00
|
|
|
return $this->project->getOrLoadDocument($textDocument->uri)->then(function (PhpDocument $document) {
|
|
|
|
return $document->getFormattedText();
|
|
|
|
});
|
2016-09-06 10:54:34 +00:00
|
|
|
}
|
2016-10-08 12:59:08 +00:00
|
|
|
|
2016-10-11 23:45:15 +00:00
|
|
|
/**
|
|
|
|
* The references request is sent from the client to the server to resolve project-wide references for the symbol
|
|
|
|
* denoted by the given text document position.
|
|
|
|
*
|
|
|
|
* @param ReferenceContext $context
|
2016-11-14 09:25:44 +00:00
|
|
|
* @return Promise <Location[]>
|
2016-10-11 23:45:15 +00:00
|
|
|
*/
|
2016-11-14 09:25:44 +00:00
|
|
|
public function references(
|
|
|
|
ReferenceContext $context,
|
|
|
|
TextDocumentIdentifier $textDocument,
|
|
|
|
Position $position
|
|
|
|
): Promise {
|
|
|
|
return coroutine(function () use ($textDocument, $position) {
|
|
|
|
$document = yield $this->project->getOrLoadDocument($textDocument->uri);
|
|
|
|
$node = $document->getNodeAtPosition($position);
|
|
|
|
if ($node === null) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
$refs = yield $document->getReferencesByNode($node);
|
|
|
|
$locations = [];
|
|
|
|
foreach ($refs as $ref) {
|
|
|
|
$locations[] = Location::fromNode($ref);
|
|
|
|
}
|
|
|
|
return $locations;
|
|
|
|
});
|
2016-10-11 23:45:15 +00:00
|
|
|
}
|
|
|
|
|
2016-10-08 12:59:08 +00:00
|
|
|
/**
|
|
|
|
* The goto definition request is sent from the client to the server to resolve the definition location of a symbol
|
|
|
|
* at a given text document position.
|
|
|
|
*
|
|
|
|
* @param TextDocumentIdentifier $textDocument The text document
|
|
|
|
* @param Position $position The position inside the text document
|
2016-11-14 09:25:44 +00:00
|
|
|
* @return Promise <Location|Location[]>
|
2016-10-08 12:59:08 +00:00
|
|
|
*/
|
2016-11-14 09:25:44 +00:00
|
|
|
public function definition(TextDocumentIdentifier $textDocument, Position $position): Promise
|
2016-10-08 12:59:08 +00:00
|
|
|
{
|
2016-11-14 09:25:44 +00:00
|
|
|
return coroutine(function () use ($textDocument, $position) {
|
|
|
|
$document = yield $this->project->getOrLoadDocument($textDocument->uri);
|
|
|
|
$node = $document->getNodeAtPosition($position);
|
|
|
|
if ($node === null) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
$def = yield $document->getDefinitionByNode($node);
|
|
|
|
if ($def === null) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
return Location::fromNode($def);
|
|
|
|
});
|
2016-10-08 12:59:08 +00:00
|
|
|
}
|
2016-10-19 10:31:32 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The hover request is sent from the client to the server to request hover information at a given text document position.
|
|
|
|
*
|
|
|
|
* @param TextDocumentIdentifier $textDocument The text document
|
|
|
|
* @param Position $position The position inside the text document
|
2016-11-14 09:25:44 +00:00
|
|
|
* @return Promise <Hover>
|
2016-10-19 10:31:32 +00:00
|
|
|
*/
|
2016-11-14 09:25:44 +00:00
|
|
|
public function hover(TextDocumentIdentifier $textDocument, Position $position): Promise
|
2016-10-19 10:31:32 +00:00
|
|
|
{
|
2016-11-14 09:25:44 +00:00
|
|
|
return coroutine(function () use ($textDocument, $position) {
|
|
|
|
$document = yield $this->project->getOrLoadDocument($textDocument->uri);
|
|
|
|
// Find the node under the cursor
|
|
|
|
$node = $document->getNodeAtPosition($position);
|
|
|
|
if ($node === null) {
|
|
|
|
return new Hover([]);
|
|
|
|
}
|
|
|
|
$range = Range::fromNode($node);
|
|
|
|
// Get the definition node for whatever node is under the cursor
|
|
|
|
$def = yield $document->getDefinitionByNode($node);
|
|
|
|
if ($def === null) {
|
|
|
|
return new Hover([], $range);
|
|
|
|
}
|
|
|
|
$contents = [];
|
|
|
|
|
|
|
|
// Build a declaration string
|
|
|
|
if ($def instanceof Node\Stmt\PropertyProperty || $def instanceof Node\Const_) {
|
|
|
|
// Properties and constants can have multiple declarations
|
|
|
|
// Use the parent node (that includes the modifiers), but only render the requested declaration
|
|
|
|
$child = $def;
|
|
|
|
$def = $def->getAttribute('parentNode');
|
|
|
|
$defLine = clone $def;
|
|
|
|
$defLine->props = [$child];
|
|
|
|
} else {
|
|
|
|
$defLine = clone $def;
|
|
|
|
}
|
|
|
|
// Don't include the docblock in the declaration string
|
|
|
|
$defLine->setAttribute('comments', []);
|
|
|
|
if (isset($defLine->stmts)) {
|
|
|
|
$defLine->stmts = [];
|
|
|
|
}
|
|
|
|
$defText = $this->prettyPrinter->prettyPrint([$defLine]);
|
|
|
|
$lines = explode("\n", $defText);
|
|
|
|
if (isset($lines[0])) {
|
|
|
|
$contents[] = new MarkedString('php', "<?php\n" . $lines[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the documentation string
|
|
|
|
if ($def instanceof Node\Param) {
|
|
|
|
$fn = $def->getAttribute('parentNode');
|
|
|
|
$docBlock = $fn->getAttribute('docBlock');
|
|
|
|
if ($docBlock !== null) {
|
|
|
|
$tags = $docBlock->getTagsByName('param');
|
|
|
|
foreach ($tags as $tag) {
|
|
|
|
if ($tag->getVariableName() === $def->name) {
|
|
|
|
$contents[] = $tag->getDescription()->render();
|
|
|
|
break;
|
|
|
|
}
|
2016-10-19 10:31:32 +00:00
|
|
|
}
|
|
|
|
}
|
2016-11-14 09:25:44 +00:00
|
|
|
} else {
|
|
|
|
$docBlock = $def->getAttribute('docBlock');
|
|
|
|
if ($docBlock !== null) {
|
|
|
|
$contents[] = $docBlock->getSummary();
|
|
|
|
}
|
2016-10-19 10:31:32 +00:00
|
|
|
}
|
|
|
|
|
2016-11-14 09:25:44 +00:00
|
|
|
return new Hover($contents, $range);
|
|
|
|
});
|
2016-10-19 10:31:32 +00:00
|
|
|
}
|
2016-08-23 09:21:37 +00:00
|
|
|
}
|