diff --git a/src/LanguageServer.php b/src/LanguageServer.php index 1a1c53d..c88cda6 100644 --- a/src/LanguageServer.php +++ b/src/LanguageServer.php @@ -75,12 +75,12 @@ class LanguageServer extends \AdvancedJsonRpc\Dispatcher /** * The initialize request is sent as the first request from the client to the server. * - * @param string $rootPath The rootPath of the workspace. Is null if no folder is open. * @param int $processId The process Id of the parent process that started the server. * @param ClientCapabilities $capabilities The capabilities provided by the client (editor) + * @param string $rootPath The rootPath of the workspace. Is null if no folder is open. * @return InitializeResult */ - public function initialize(string $rootPath, int $processId, ClientCapabilities $capabilities): InitializeResult + public function initialize(int $processId, ClientCapabilities $capabilities, string $rootPath = null): InitializeResult { // start building project index if ($rootPath) { @@ -124,7 +124,7 @@ class LanguageServer extends \AdvancedJsonRpc\Dispatcher /** * Parses workspace files, one at a time. * - * @param string $rootPath The rootPath of the workspace. Is null if no folder is open. + * @param string $rootPath The rootPath of the workspace. * @return void */ private function indexProject(string $rootPath) @@ -136,26 +136,29 @@ class LanguageServer extends \AdvancedJsonRpc\Dispatcher foreach($files as $file) { $fileList = array_merge($fileList, $file); } + $numTotalFiles = count($fileList); + + $startTime = microtime(true); - $processFile = function() use (&$fileList, &$processFile, &$rootPath){ + $processFile = function() use (&$fileList, &$processFile, $rootPath, $numTotalFiles, $startTime) { if ($file = array_pop($fileList)) { $uri = 'file://'.($file[0] == '/' || $file[0] == '\\' ? '' : '/').str_replace('\\', '/', $file); - $numFiles = count($fileList); - if (($numFiles % 100) == 0) { - $this->client->window->logMessage(3, $numFiles.' PHP files remaining.'); - } + $fileNum = $numTotalFiles - count($fileList); + $shortName = substr($file, strlen($rootPath)+1); + $this->client->window->logMessage(3, "Parsing file $fileNum/$numTotalFiles: $shortName."); $this->project->getDocument($uri)->updateAst(file_get_contents($file)); - Loop\nextTick($processFile); + Loop\setTimeout($processFile, 0); } else { - $this->client->window->logMessage(3, 'All PHP files parsed.'); + $duration = (int)(microtime(true) - $startTime); + $this->client->window->logMessage(3, "All PHP files parsed in $duration seconds."); } }; - Loop\nextTick($processFile); + Loop\setTimeout($processFile, 0); } } diff --git a/src/Project.php b/src/Project.php index c7792c2..27e84ea 100644 --- a/src/Project.php +++ b/src/Project.php @@ -8,8 +8,26 @@ use PhpParser\NodeVisitor\NameResolver; class Project { + /** + * An associative array [string => PhpDocument] + * that maps URIs to loaded PhpDocuments + * + * @var array + */ private $documents; + + /** + * Instance of the PHP parser + * + * @var ParserAbstract + */ private $parser; + + /** + * Reference to the language server client interface + * + * @var LanguageClient + */ private $client; public function __construct(LanguageClient $client) diff --git a/src/ProtocolStreamReader.php b/src/ProtocolStreamReader.php index 36b1111..32d95ea 100644 --- a/src/ProtocolStreamReader.php +++ b/src/ProtocolStreamReader.php @@ -30,32 +30,36 @@ class ProtocolStreamReader implements ProtocolReader { $this->input = $input; Loop\addReadStream($this->input, function() { - $c = fgetc($this->input); - $this->buffer .= $c; - switch ($this->parsingMode) { - case ParsingMode::HEADERS: - if ($this->buffer === "\r\n") { - $this->parsingMode = ParsingMode::BODY; - $this->contentLength = (int)$this->headers['Content-Length']; - $this->buffer = ''; - } else if (substr($this->buffer, -2) === "\r\n") { - $parts = explode(':', $this->buffer); - $this->headers[$parts[0]] = trim($parts[1]); - $this->buffer = ''; - } - break; - case ParsingMode::BODY: - if (strlen($this->buffer) === $this->contentLength) { - if (isset($this->listener)) { - $msg = new Message(MessageBody::parse($this->buffer), $this->headers); - $listener = $this->listener; - $listener($msg); + while(($c = fgetc($this->input)) !== false) { + $this->buffer .= $c; + switch ($this->parsingMode) { + case ParsingMode::HEADERS: + if ($this->buffer === "\r\n") { + $this->parsingMode = ParsingMode::BODY; + $this->contentLength = (int)$this->headers['Content-Length']; + $this->buffer = ''; + } else if (substr($this->buffer, -2) === "\r\n") { + $parts = explode(':', $this->buffer); + $this->headers[$parts[0]] = trim($parts[1]); + $this->buffer = ''; } - $this->parsingMode = ParsingMode::HEADERS; - $this->headers = []; - $this->buffer = ''; - } - break; + break; + case ParsingMode::BODY: + if (strlen($this->buffer) === $this->contentLength) { + if (isset($this->listener)) { + $msg = new Message(MessageBody::parse($this->buffer), $this->headers); + $listener = $this->listener; + $listener($msg); + } + $this->parsingMode = ParsingMode::HEADERS; + $this->headers = []; + $this->buffer = ''; + + // after reading a full message, leave to allow different tasks to run + return; + } + break; + } } }); } diff --git a/src/Server/Workspace.php b/src/Server/Workspace.php index 98de35f..ef3e50b 100644 --- a/src/Server/Workspace.php +++ b/src/Server/Workspace.php @@ -31,6 +31,11 @@ class Workspace */ private $client; + /** + * The current project database + * + * @var Project + */ private $project; public function __construct(Project $project, LanguageClient $client) @@ -50,24 +55,4 @@ class Workspace { return $this->project->findSymbols($query); } - - /** - * A notification sent from the client to the server to signal the change of configuration settings. - * - * @param The actual changed settings - * @return void - */ - public function didChangeConfiguration($settings) - { - } - - /** - * The document change notification is sent from the client to the server to signal changes to a text document. - * - * @param \LanguageServer\Protocol\FileEvent[] $textDocument - * @return void - */ - public function didChangeWatchedFiles(array $changes) - { - } } diff --git a/src/SymbolFinder.php b/src/SymbolFinder.php index e54fa77..e79a1d1 100644 --- a/src/SymbolFinder.php +++ b/src/SymbolFinder.php @@ -4,6 +4,9 @@ declare(strict_types = 1); namespace LanguageServer; use PhpParser\{NodeVisitorAbstract, Node}; +use PhpParser\Builder\Function_; +use PhpParser\Node\Stmt\ClassMethod; + use LanguageServer\Protocol\{SymbolInformation, SymbolKind, Range, Position, Location}; class SymbolFinder extends NodeVisitorAbstract @@ -75,7 +78,7 @@ class SymbolFinder extends NodeVisitorAbstract } // if we enter a method or function, increase the function counter - if ($class === Node\Stmt\Function_::class || $class === Node\Stmt\ClassMethod::class) { + if ($node instanceof Function_ || $node instanceof ClassMethod) { $this->functionCount++; } @@ -107,8 +110,7 @@ class SymbolFinder extends NodeVisitorAbstract array_pop($this->nameStack); // if we leave a method or function, decrease the function counter - $class = get_class($node); - if ($class === Node\Stmt\Function_::class || $class === Node\Stmt\ClassMethod::class) { + if ($node instanceof Function_ || $node instanceof ClassMethod) { $this->functionCount--; } } diff --git a/tests/Server/TextDocumentTest.php b/tests/Server/TextDocumentTest.php index ec9c3a3..f227fe8 100644 --- a/tests/Server/TextDocumentTest.php +++ b/tests/Server/TextDocumentTest.php @@ -5,7 +5,7 @@ namespace LanguageServer\Tests\Server; use PHPUnit\Framework\TestCase; use LanguageServer\Tests\MockProtocolStream; -use LanguageServer\{Server, Client, LanguageClient}; +use LanguageServer\{Server, Client, LanguageClient, Project, PhpDocument}; use LanguageServer\Protocol\{TextDocumentItem, TextDocumentIdentifier, SymbolKind, DiagnosticSeverity, FormattingOptions}; use AdvancedJsonRpc\{Request as RequestBody, Response as ResponseBody}; @@ -13,7 +13,9 @@ class TextDocumentTest extends TestCase { public function testDocumentSymbol() { - $textDocument = new Server\TextDocument(new LanguageClient(new MockProtocolStream())); + $client = new LanguageClient(new MockProtocolStream()); + $project = new Project($client); + $textDocument = new Server\TextDocument($project, $client); // Trigger parsing of source $textDocumentItem = new TextDocumentItem(); $textDocumentItem->uri = 'whatever'; @@ -94,7 +96,7 @@ class TextDocumentTest extends TestCase ] ] ], - 'containerName' => null + 'containerName' => 'TestClass' ], [ 'name' => 'TestTrait', @@ -151,7 +153,11 @@ class TextDocumentTest extends TestCase $this->args = func_get_args(); } }; - $textDocument = new Server\TextDocument($client); + + $project = new Project($client); + + $textDocument = new Server\TextDocument($project, $client); + // Trigger parsing of source $textDocumentItem = new TextDocumentItem(); $textDocumentItem->uri = 'whatever'; @@ -182,7 +188,10 @@ class TextDocumentTest extends TestCase public function testFormatting() { - $textDocument = new Server\TextDocument(new LanguageClient(new MockProtocolStream())); + $client = new LanguageClient(new MockProtocolStream()); + $project = new Project($client); + $textDocument = new Server\TextDocument($project, $client); + // Trigger parsing of source $textDocumentItem = new TextDocumentItem(); $textDocumentItem->uri = 'whatever';