diff --git a/fixtures/signatureHelp/funcClosed.php b/fixtures/signatureHelp/funcClosed.php index 3efbd9a..6012844 100644 --- a/fixtures/signatureHelp/funcClosed.php +++ b/fixtures/signatureHelp/funcClosed.php @@ -3,4 +3,5 @@ function helpFunc1(int $count = 0) { } + helpFunc1() diff --git a/fixtures/signatureHelp/funcNotClosed.php b/fixtures/signatureHelp/funcNotClosed.php index f469937..d636f42 100644 --- a/fixtures/signatureHelp/funcNotClosed.php +++ b/fixtures/signatureHelp/funcNotClosed.php @@ -3,4 +3,7 @@ function helpFunc2(int $count = 0) { } + +$a = 1; + helpFunc2( diff --git a/fixtures/signatureHelp/methodNotClosed.php b/fixtures/signatureHelp/methodNotClosed.php index af55912..d5ce4cc 100644 --- a/fixtures/signatureHelp/methodNotClosed.php +++ b/fixtures/signatureHelp/methodNotClosed.php @@ -7,9 +7,11 @@ class HelpClass2 } public function test() { - $this->method( + $this->method(1,1); } } - $a = new HelpClass2; -$a->method(1, \ No newline at end of file +$a + ->method( + 1, + array(), diff --git a/fixtures/signatureHelp/staticNotClosed.php b/fixtures/signatureHelp/staticNotClosed.php index b2f76a0..8f0ac96 100644 --- a/fixtures/signatureHelp/staticNotClosed.php +++ b/fixtures/signatureHelp/staticNotClosed.php @@ -7,4 +7,6 @@ class HelpClass4 } } -HelpClass4::method( +HelpClass4::method(1, 2, 3 + +HelpClass4::method(1, 2, 3 diff --git a/src/SignatureHelpProvider.php b/src/SignatureHelpProvider.php index fe850d9..b7575cc 100644 --- a/src/SignatureHelpProvider.php +++ b/src/SignatureHelpProvider.php @@ -3,9 +3,11 @@ declare(strict_types = 1); namespace LanguageServer; +use PhpParser\ErrorHandler\Collecting; use PhpParser\Node; use LanguageServer\Index\ReadableIndex; use LanguageServer\Protocol\{ + Range, Position, SignatureHelp, SignatureInformation, @@ -24,6 +26,16 @@ class SignatureHelpProvider */ private $index; + /** + * @var Parser + */ + private $parser; + + /** + * @var Parser + */ + private $parserErrorHandler; + /** * @param DefinitionResolver $definitionResolver * @param ReadableIndex $index @@ -32,6 +44,8 @@ class SignatureHelpProvider { $this->definitionResolver = $definitionResolver; $this->index = $index; + $this->parser = new Parser; + $this->parserErrorHandler = new Collecting; } /** @@ -41,76 +55,52 @@ class SignatureHelpProvider * @param Position $pos The cursor position * @return SignatureHelp */ - public function provideSignature(PhpDocument $doc, Position $pos): SignatureHelp + public function provideSignature(PhpDocument $doc, Position $pos) : SignatureHelp { $help = new SignatureHelp; $help->signatures = []; - $newPos = clone $pos; - $line = explode("\n", $doc->getContent())[$newPos->line]; + $handle = fopen($doc->getUri(), 'r'); + $lines = []; + for ($i = 0; $i < $pos->line; $i++) { + $lines[] = strlen(fgets($handle)); + } + $filePos = ftell($handle) + $pos->character; + $line = substr(fgets($handle), 0, $pos->character); + fseek($handle, 0); + do { - $newPos->character --; - } while ($newPos->character > 0 && $line[$newPos->character] !== "("); + $node = $doc->getNodeAtPosition($pos); + $pos->character--; + if ($pos->character < 0) { + $pos->line --; + if ($pos->line < 0) { + break; + } + $pos->character = $lines[$pos->line]; + } + } while ($node === null); - if (!$newPos->character) { + if ($node === null) { + fclose($handle); return $help; } - $line = substr($line, 0, $newPos->character); - - $newPos->character --; - - $node = $doc->getNodeAtPosition($newPos); - - if ($node instanceof Node\Expr\Error) { + $i = 0; + while (!( + $node instanceof Node\Expr\PropertyFetch || + $node instanceof Node\Expr\MethodCall || + $node instanceof Node\Expr\FuncCall || + $node instanceof Node\Expr\ClassConstFetch || + $node instanceof Node\Expr\StaticCall + ) && ++$i < 5 && $node !== null) { $node = $node->getAttribute('parentNode'); } - - 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; - $signature->label = str_replace('()', '', $def->fqn) . '('.implode(', ', $def->parameters).')'; - $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\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] . '('.implode(', ', $def->parameters).')'; - $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; - $signature->label = str_replace('()', '', explode('->', $def->fqn)[1]) . '('.implode(', ', $def->parameters).')'; - $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\PropertyFetch) { + $params = ''; + if ($node instanceof Node\Expr\PropertyFetch) { + fseek($handle, $node->name->getAttribute('startFilePos')); + $method = fread($handle, ($node->name->getAttribute('endFilePos') + 1) - $node->name->getAttribute('startFilePos')); + fseek($handle, $node->name->getAttribute('endFilePos') + 1); + $params = fread($handle, ($filePos - 1) - $node->name->getAttribute('endFilePos')); if ($def = $this->definitionResolver->resolveReferenceNodeToDefinition($node->var)) { $fqn = $def->fqn; if (!$fqn) { @@ -121,55 +111,65 @@ class SignatureHelpProvider $fqn = $fqns[0]; } } - $method = trim(substr($line, strrpos($line, ">") + 1)); - if ($method && $fqn) { + if ($fqn) { $fqn = $fqn . '->' . $method . '()'; - if ($def = $this->index->getDefinition($fqn)) { - $signature = new SignatureInformation; - $signature->label = str_replace('()', '', explode('->', $def->fqn)[1]) . '('.implode(', ', $def->parameters).')'; - $signature->documentation = $def->documentation; - $signature->parameters = []; - foreach ($def->parameters as $param) { - $p = new ParameterInformation; - $p->label = $param; - $signature->parameters[] = $p; - } - $help->signatures[] = $signature; - } + $def = $this->index->getDefinition($fqn); } } + } else if ($node instanceof Node\Expr\MethodCall) { + fseek($handle, $node->getAttribute('startFilePos')); + $params = explode('(', fread($handle, $filePos - $node->getAttribute('startFilePos')), 2)[1]; + $def = $this->definitionResolver->resolveReferenceNodeToDefinition($node); + } else if ($node instanceof Node\Expr\FuncCall) { + fseek($handle, $node->getAttribute('startFilePos')); + $params = explode('(', fread($handle, $filePos - $node->getAttribute('startFilePos')), 2)[1]; + $fqn = $this->definitionResolver->resolveReferenceNodeToFqn($node->name); + $def = $this->index->getDefinition($fqn); } else if ($node instanceof Node\Expr\StaticCall) { - if ($def = $this->definitionResolver->resolveReferenceNodeToDefinition($node)) { - $signature = new SignatureInformation; - $signature->label = str_replace('()', '', explode('::', $def->fqn)[1]) . '('.implode(', ', $def->parameters).')'; - $signature->documentation = $def->documentation; - $signature->parameters = []; - foreach ($def->parameters as $param) { - $p = new ParameterInformation; - $p->label = $param; - $signature->parameters[] = $p; - } - $help->signatures[] = $signature; - } + fseek($handle, $node->getAttribute('startFilePos')); + $params = explode('(', fread($handle, $filePos - $node->getAttribute('startFilePos')), 2)[1]; + $def = $this->definitionResolver->resolveReferenceNodeToDefinition($node); } 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]) . '('.implode(', ', $def->parameters).')'; - $signature->documentation = $def->documentation; - $signature->parameters = []; - foreach ($def->parameters as $param) { - $p = new ParameterInformation; - $p->label = $param; - $signature->parameters[] = $p; - } - $help->signatures[] = $signature; - } + fseek($handle, $node->name->getAttribute('endFilePos') + 2); + $params = fread($handle, ($filePos - 1) - $node->name->getAttribute('endFilePos')); + fseek($handle, $node->name->getAttribute('startFilePos')); + $method = fread($handle, ($node->name->getAttribute('endFilePos') + 1) - $node->name->getAttribute('startFilePos')); + $method = explode('::', str_replace('()', '', $method), 2); + $method = $method[1] ?? $method[0]; + $fqn = $this->definitionResolver->resolveReferenceNodeToFqn($node->class); + $def = $this->index->getDefinition($fqn.'::'.$method.'()'); + } else { + if (!preg_match('(([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\s*\((.*)$)', $line, $method)) { + fclose($handle); + return $help; + } + $def = $this->index->getDefinition($method[1] . '()'); + $params = $method[2]; + } + fclose($handle); + + if ($def) { + $method = preg_split('(::|->)', str_replace('()', '', $def->fqn), 2); + $method = $method[1] ?? $method[0]; + $signature = new SignatureInformation; + $signature->label = $method . '('.implode(', ', $def->parameters).')'; + $signature->documentation = $def->documentation; + $signature->parameters = []; + foreach ($def->parameters as $param) { + $p = new ParameterInformation; + $p->label = $param; + $signature->parameters[] = $p; + } + $help->activeSignature = 0; + $help->activeParameter = 0; + if (strlen(trim($params))) { + try { + $params = $this->parser->parse('parserErrorHandler)[0]->expr->items; + $help->activeParameter = count($params); + } catch (\Exception $e) { } } + $help->signatures[] = $signature; } return $help;