diff --git a/fixtures/signatureHelp/funcClosed.php b/fixtures/signatureHelp/funcClosed.php new file mode 100644 index 0000000..3efbd9a --- /dev/null +++ b/fixtures/signatureHelp/funcClosed.php @@ -0,0 +1,6 @@ +method(); + } +} diff --git a/fixtures/signatureHelp/methodNotClosed.php b/fixtures/signatureHelp/methodNotClosed.php new file mode 100644 index 0000000..5c1fa9c --- /dev/null +++ b/fixtures/signatureHelp/methodNotClosed.php @@ -0,0 +1,12 @@ +method( + } +} diff --git a/fixtures/signatureHelp/staticClosed.php b/fixtures/signatureHelp/staticClosed.php new file mode 100644 index 0000000..301009f --- /dev/null +++ b/fixtures/signatureHelp/staticClosed.php @@ -0,0 +1,10 @@ +getNodeAtPosition($pos); $help = new SignatureHelp; $help->signatures = []; + $newPos = clone $pos; + $line = explode("\n", $doc->getContent())[$newPos->line]; + do { + $newPos->character --; + } while ($newPos->character > 0 && $line[$newPos->character] !== "("); + + if (!$newPos->character) { + return $help; + } + $line = substr($line, 0, $newPos->character); + + //echo $line . "\n"; + //die(); + $newPos->character --; + + $node = $doc->getNodeAtPosition($newPos); + + if ($node instanceof Node\Expr\Error) { + $node = $node->getAttribute('parentNode'); + } + + //echo get_class($node); + //die(); + //$def = $this->definitionResolver->resolveReferenceNodeToDefinition($node->var); + //var_dump($def); + //echo $def->fqn; + + //echo $node->name; + + + //die(); + + if ($node instanceof Node\Expr\Error) { + $node = $node->getAttribute('parentNode'); + } if ($node instanceof Node\Expr\FuncCall) { if ($def = $this->definitionResolver->resolveReferenceNodeToDefinition($node)) { $signature = new SignatureInformation; @@ -60,6 +94,22 @@ class SignatureHelpProvider } $help->signatures[] = $signature; } + } else if ($node instanceof Node\Name\FullyQualified || $node === null) { + if (preg_match('([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$)', $line, $method)) { + $fqn = $method[0] . '()'; + if ($def = $this->index->getDefinition($fqn)) { + $signature = new SignatureInformation; + $signature->label = $method[0]; + $signature->documentation = $def->documentation; + $signature->parameters = []; + foreach ($def->parameters as $param) { + $p = new ParameterInformation; + $p->label = $param; + $signature->parameters[] = $p; + } + $help->signatures[] = $signature; + } + } } else if ($node instanceof Node\Expr\MethodCall) { if ($def = $this->definitionResolver->resolveReferenceNodeToDefinition($node)) { $signature = new SignatureInformation; @@ -73,17 +123,57 @@ class SignatureHelpProvider } $help->signatures[] = $signature; } - } else if ($node instanceof Node\Expr\StaticCall) { - $signature = new SignatureInformation; - $signature->label = str_replace('()', '', explode('::', $def->fqn)[1]); - $signature->documentation = $def->documentation; - $signature->parameters = []; - foreach ($def->parameters as $param) { - $p = new ParameterInformation; - $p->label = $param; - $signature->parameters[] = $p; + } else if ($node instanceof Node\Expr\PropertyFetch) { + if ($def = $this->definitionResolver->resolveReferenceNodeToDefinition($node->var)) { + $method = trim(substr($line, strrpos($line, ">") + 1)); + if ($method) { + $fqn = $def->fqn . '->' . $method . '()'; + if ($def = $this->index->getDefinition($fqn)) { + $signature = new SignatureInformation; + $signature->label = str_replace('()', '', explode('->', $def->fqn)[1]); + $signature->documentation = $def->documentation; + $signature->parameters = []; + foreach ($def->parameters as $param) { + $p = new ParameterInformation; + $p->label = $param; + $signature->parameters[] = $p; + } + $help->signatures[] = $signature; + } + } + } + } else if ($node instanceof Node\Expr\StaticCall) { + if ($def = $this->definitionResolver->resolveReferenceNodeToDefinition($node)) { + $signature = new SignatureInformation; + $signature->label = str_replace('()', '', explode('::', $def->fqn)[1]); + $signature->documentation = $def->documentation; + $signature->parameters = []; + foreach ($def->parameters as $param) { + $p = new ParameterInformation; + $p->label = $param; + $signature->parameters[] = $p; + } + $help->signatures[] = $signature; + } + } else if ($node instanceof Node\Expr\ClassConstFetch) { + if ($def = $this->definitionResolver->resolveReferenceNodeToDefinition($node->class)) { + $method = trim(substr($line, strrpos($line, ":") + 1)); + if ($method) { + $fqn = $def->fqn . '::' . $method . '()'; + if ($def = $this->index->getDefinition($fqn)) { + $signature = new SignatureInformation; + $signature->label = str_replace('()', '', explode('::', $def->fqn)[1]); + $signature->documentation = $def->documentation; + $signature->parameters = []; + foreach ($def->parameters as $param) { + $p = new ParameterInformation; + $p->label = $param; + $signature->parameters[] = $p; + } + $help->signatures[] = $signature; + } + } } - $help->signatures[] = $signature; } return $help; diff --git a/tests/LanguageServerTest.php b/tests/LanguageServerTest.php index 6bfa3c9..7b1c008 100644 --- a/tests/LanguageServerTest.php +++ b/tests/LanguageServerTest.php @@ -14,7 +14,8 @@ use LanguageServer\Protocol\{ TextDocumentIdentifier, InitializeResult, ServerCapabilities, - CompletionOptions + CompletionOptions, + SignatureHelpOptions }; use AdvancedJsonRpc; use Webmozart\Glob\Glob; @@ -41,6 +42,8 @@ class LanguageServerTest extends TestCase $serverCapabilities->completionProvider = new CompletionOptions; $serverCapabilities->completionProvider->resolveProvider = false; $serverCapabilities->completionProvider->triggerCharacters = ['$', '>']; + $serverCapabilities->signatureHelpProvider = new SignatureHelpOptions; + $serverCapabilities->signatureHelpProvider->triggerCharacters = ['(']; $serverCapabilities->xworkspaceReferencesProvider = true; $serverCapabilities->xdefinitionProvider = true; $serverCapabilities->xdependenciesProvider = true; @@ -57,7 +60,7 @@ class LanguageServerTest extends TestCase if ($msg->body->method === 'window/logMessage' && $promise->state === Promise::PENDING) { if ($msg->body->params->type === MessageType::ERROR) { $promise->reject(new Exception($msg->body->params->message)); - } else if (strpos($msg->body->params->message, 'All 25 PHP files parsed') !== false) { + } else if (strpos($msg->body->params->message, 'All 33 PHP files parsed') !== false) { $promise->fulfill(); } } @@ -103,7 +106,7 @@ class LanguageServerTest extends TestCase if ($promise->state === Promise::PENDING) { $promise->reject(new Exception($msg->body->params->message)); } - } else if (strpos($msg->body->params->message, 'All 25 PHP files parsed') !== false) { + } else if (strpos($msg->body->params->message, 'All 33 PHP files parsed') !== false) { if ($run === 1) { $run++; } else { diff --git a/tests/Server/TextDocument/SignatureHelpTest.php b/tests/Server/TextDocument/SignatureHelpTest.php new file mode 100644 index 0000000..bc9c46f --- /dev/null +++ b/tests/Server/TextDocument/SignatureHelpTest.php @@ -0,0 +1,178 @@ +loader = new PhpDocumentLoader($contentRetriever, $projectIndex, $definitionResolver); + $this->loader->load(pathToUri(__DIR__ . '/../../../fixtures/global_symbols.php'))->wait(); + $this->loader->load(pathToUri(__DIR__ . '/../../../fixtures/symbols.php'))->wait(); + $this->textDocument = new Server\TextDocument($this->loader, $definitionResolver, $client, $projectIndex); + } + + public function testMethodClosed() + { + $completionUri = pathToUri(__DIR__ . '/../../../fixtures/signatureHelp/methodClosed.php'); + $this->loader->open($completionUri, file_get_contents($completionUri)); + $result = $this->textDocument->signatureHelp( + new TextDocumentIdentifier($completionUri), + new Position(9, 22) + )->wait(); + + $help = new SignatureHelp; + $help->signatures = []; + $info = new SignatureInformation; + $help->signatures[] = $info; + $info->label = 'method'; + $info->parameters = []; + $param = new ParameterInformation; + $info->parameters[] = $param; + $param->label = 'string $param = ""'; + + $this->assertEquals($help, $result); + } + + public function testMethodNotClosed() + { + $completionUri = pathToUri(__DIR__ . '/../../../fixtures/signatureHelp/methodNotClosed.php'); + $this->loader->open($completionUri, file_get_contents($completionUri)); + $result = $this->textDocument->signatureHelp( + new TextDocumentIdentifier($completionUri), + new Position(9, 22) + )->wait(); + + $help = new SignatureHelp; + $help->signatures = []; + $info = new SignatureInformation; + $help->signatures[] = $info; + $info->label = 'method'; + $info->parameters = []; + $param = new ParameterInformation; + $info->parameters[] = $param; + $param->label = 'string $param = ""'; + + $this->assertEquals($help, $result); + } + + public function funcClosed() + { + $completionUri = pathToUri(__DIR__ . '/../../../fixtures/signatureHelp/funcClosed.php'); + $this->loader->open($completionUri, file_get_contents($completionUri)); + $result = $this->textDocument->signatureHelp( + new TextDocumentIdentifier($completionUri), + new Position(5, 10) + )->wait(); + + $help = new SignatureHelp; + $help->signatures = []; + $info = new SignatureInformation; + $help->signatures[] = $info; + $info->label = 'helpFunc1'; + $info->parameters = []; + $param = new ParameterInformation; + $info->parameters[] = $param; + $param->label = 'int $count = 0'; + + $this->assertEquals($help, $result); + } + + public function funcNotClosed() + { + $completionUri = pathToUri(__DIR__ . '/../../../fixtures/signatureHelp/funcNotClosed.php'); + $this->loader->open($completionUri, file_get_contents($completionUri)); + $result = $this->textDocument->signatureHelp( + new TextDocumentIdentifier($completionUri), + new Position(5, 10) + )->wait(); + + $help = new SignatureHelp; + $help->signatures = []; + $info = new SignatureInformation; + $help->signatures[] = $info; + $info->label = 'helpFunc2'; + $info->parameters = []; + $param = new ParameterInformation; + $info->parameters[] = $param; + $param->label = 'int $count = 0'; + + $this->assertEquals($help, $result); + } + + public function staticClosed() + { + $completionUri = pathToUri(__DIR__ . '/../../../fixtures/signatureHelp/staticClosed.php'); + $this->loader->open($completionUri, file_get_contents($completionUri)); + $result = $this->textDocument->signatureHelp( + new TextDocumentIdentifier($completionUri), + new Position(9, 19) + )->wait(); + + $help = new SignatureHelp; + $help->signatures = []; + $info = new SignatureInformation; + $help->signatures[] = $info; + $info->label = 'method'; + $info->parameters = []; + $param = new ParameterInformation; + $info->parameters[] = $param; + $param->label = 'string $param = ""'; + + $this->assertEquals($help, $result); + } + + public function staticNotClosed() + { + $completionUri = pathToUri(__DIR__ . '/../../../fixtures/signatureHelp/staticNotClosed.php'); + $this->loader->open($completionUri, file_get_contents($completionUri)); + $result = $this->textDocument->signatureHelp( + new TextDocumentIdentifier($completionUri), + new Position(9, 19) + )->wait(); + + $help = new SignatureHelp; + $help->signatures = []; + $info = new SignatureInformation; + $help->signatures[] = $info; + $info->label = 'method'; + $info->parameters = []; + $param = new ParameterInformation; + $info->parameters[] = $param; + $param->label = 'string $param = ""'; + + $this->assertEquals($help, $result); + } +}