1
0
Fork 0
php-language-server/src/Server/TextDocument.php

152 lines
5.2 KiB
PHP
Raw Normal View History

2016-08-23 09:21:37 +00:00
<?php
namespace LanguageServer\Server;
2016-08-23 09:21:37 +00:00
2016-08-25 13:27:14 +00:00
use PhpParser\{Error, Comment, Node, ParserFactory, NodeTraverser, Lexer};
2016-09-06 10:54:34 +00:00
use PhpParser\PrettyPrinter\Standard as PrettyPrinter;
2016-08-25 13:27:14 +00:00
use PhpParser\NodeVisitor\NameResolver;
use LanguageServer\{LanguageClient, ColumnCalculator, SymbolFinder};
use LanguageServer\Protocol\{
TextDocumentItem,
TextDocumentIdentifier,
VersionedTextDocumentIdentifier,
Diagnostic,
DiagnosticSeverity,
Range,
2016-09-06 10:54:34 +00:00
Position,
FormattingOptions,
TextEdit
};
2016-08-25 13:27:14 +00:00
2016-08-23 09:21:37 +00:00
/**
* Provides method handlers for all textDocument/* methods
*/
class TextDocument
2016-08-23 09:21:37 +00:00
{
2016-08-25 13:27:14 +00:00
/**
2016-09-04 10:27:56 +00:00
* @var \PhpParser\Parser
2016-08-25 13:27:14 +00:00
*/
private $parser;
/**
* A map from file URIs to ASTs
*
2016-09-04 10:27:56 +00:00
* @var \PhpParser\Stmt[][]
2016-08-25 13:27:14 +00:00
*/
private $asts;
/**
* The lanugage client object to call methods on the client
*
2016-09-04 10:27:56 +00:00
* @var \LanguageServer\LanguageClient
*/
private $client;
public function __construct(LanguageClient $client)
2016-08-25 13:27:14 +00:00
{
$this->client = $client;
2016-08-25 13:27:14 +00:00
$lexer = new Lexer(['usedAttributes' => ['comments', 'startLine', 'endLine', 'startFilePos', 'endFilePos']]);
$this->parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7, $lexer, ['throwOnError' => false]);
}
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-08-23 09:21:37 +00:00
* @return SymbolInformation[]
*/
2016-08-25 13:27:14 +00:00
public function documentSymbol(TextDocumentIdentifier $textDocument): array
2016-08-23 09:21:37 +00:00
{
2016-08-25 13:27:14 +00:00
$stmts = $this->asts[$textDocument->uri];
if (!$stmts) {
return [];
}
$finder = new SymbolFinder($textDocument->uri);
$traverser = new NodeTraverser;
$traverser->addVisitor($finder);
$traverser->traverse($stmts);
return $finder->symbols;
}
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)
{
$this->updateAst($textDocument->uri, $textDocument->text);
}
/**
* 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-02 00:56:45 +00:00
$this->updateAst($textDocument->uri, $contentChanges[0]->text);
2016-08-25 13:27:14 +00:00
}
/**
* Re-parses a source file, updates the AST and reports parsing errors that may occured as diagnostics
*
* @param string $uri The URI of the source file
* @param string $content The new content of the source file
* @return void
*/
2016-08-25 13:27:14 +00:00
private function updateAst(string $uri, string $content)
{
2016-09-02 00:56:45 +00:00
$stmts = $this->parser->parse($content);
$diagnostics = [];
foreach ($this->parser->getErrors() as $error) {
$diagnostic = new Diagnostic();
$diagnostic->range = new Range(
new Position($error->getStartLine() - 1, $error->hasColumnInfo() ? $error->getStartColumn($content) - 1 : 0),
2016-09-04 10:43:58 +00:00
new Position($error->getEndLine() - 1, $error->hasColumnInfo() ? $error->getEndColumn($content) : 0)
);
$diagnostic->severity = DiagnosticSeverity::ERROR;
$diagnostic->source = 'php';
// Do not include "on line ..." in the error message
$diagnostic->message = $error->getRawMessage();
$diagnostics[] = $diagnostic;
}
2016-09-04 10:43:58 +00:00
$this->client->textDocument->publishDiagnostics($uri, $diagnostics);
2016-08-25 13:27:14 +00:00
// $stmts can be null in case of a fatal parsing error
if ($stmts) {
$traverser = new NodeTraverser;
$traverser->addVisitor(new NameResolver);
2016-09-02 00:56:45 +00:00
$traverser->addVisitor(new ColumnCalculator($content));
2016-08-25 13:27:14 +00:00
$traverser->traverse($stmts);
$this->asts[$uri] = $stmts;
2016-08-25 13:27:14 +00:00
}
2016-08-23 09:21:37 +00:00
}
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
* @return TextEdit[]
*/
public function formatting(TextDocumentIdentifier $textDocument, FormattingOptions $options)
{
$nodes = $this->asts[$textDocument->uri];
if (empty($nodes)) {
return [];
}
$prettyPrinter = new PrettyPrinter();
$edit = new TextEdit();
$edit->range = new Range(new Position(0, 0), new Position(PHP_INT_MAX, PHP_INT_MAX));
$edit->newText = $prettyPrinter->prettyPrintFile($nodes);
return [$edit];
}
2016-08-23 09:21:37 +00:00
}