forked from felixfbecker/php-language-server
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathTextDocument.php
150 lines (138 loc) Β· 5.17 KB
/
TextDocument.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
<?php
namespace LanguageServer\Server;
use PhpParser\{Error, Comment, Node, ParserFactory, NodeTraverser, Lexer, PrettyPrinter\Standard as PrettyPrinterStandard};
use PhpParser\NodeVisitor\NameResolver;
use LanguageServer\{LanguageClient, ColumnCalculator, SymbolFinder};
use LanguageServer\Protocol\{
TextDocumentItem,
TextDocumentIdentifier,
VersionedTextDocumentIdentifier,
Diagnostic,
DiagnosticSeverity,
Range,
Position,
FormattingOptions,
TextEdit
};
/**
* Provides method handlers for all textDocument/* methods
*/
class TextDocument
{
/**
* @var \PhpParser\Parser
*/
private $parser;
/**
* A map from file URIs to ASTs
*
* @var \PhpParser\Stmt[][]
*/
private $asts;
/**
* The lanugage client object to call methods on the client
*
* @var \LanguageServer\LanguageClient
*/
private $client;
public function __construct(LanguageClient $client)
{
$this->client = $client;
$lexer = new Lexer(['usedAttributes' => ['comments', 'startLine', 'endLine', 'startFilePos', 'endFilePos']]);
$this->parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7, $lexer, ['throwOnError' => false]);
}
/**
* 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\TextDocumentIdentifier $textDocument
* @return SymbolInformation[]
*/
public function documentSymbol(TextDocumentIdentifier $textDocument): array
{
$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;
}
/**
* 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.
*
* @param \LanguageServer\Protocol\TextDocumentItem $textDocument The document that was opened.
* @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.
*
* @param \LanguageServer\Protocol\VersionedTextDocumentIdentifier $textDocument
* @param \LanguageServer\Protocol\TextDocumentContentChangeEvent[] $contentChanges
* @return void
*/
public function didChange(VersionedTextDocumentIdentifier $textDocument, array $contentChanges)
{
$this->updateAst($textDocument->uri, $contentChanges[0]->text);
}
/**
* 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
*/
private function updateAst(string $uri, string $content)
{
$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),
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;
}
$this->client->textDocument->publishDiagnostics($uri, $diagnostics);
// $stmts can be null in case of a fatal parsing error
if ($stmts) {
$traverser = new NodeTraverser;
$traverser->addVisitor(new NameResolver);
$traverser->addVisitor(new ColumnCalculator($content));
$traverser->traverse($stmts);
$this->asts[$uri] = $stmts;
}
}
/**
* 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 PrettyPrinterStandard();
$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];
}
}