1
0
Fork 0
php-language-server/src/SignatureHelpProvider.php

178 lines
6.6 KiB
PHP
Raw Normal View History

2017-01-19 18:33:31 +00:00
<?php
declare(strict_types = 1);
namespace LanguageServer;
2017-01-20 13:26:14 +00:00
use PhpParser\ErrorHandler\Collecting;
2017-01-19 18:33:31 +00:00
use PhpParser\Node;
use LanguageServer\Index\ReadableIndex;
use LanguageServer\Protocol\{
2017-01-20 13:26:14 +00:00
Range,
2017-01-19 18:33:31 +00:00
Position,
SignatureHelp,
SignatureInformation,
ParameterInformation
};
class SignatureHelpProvider
{
/**
* @var DefinitionResolver
*/
private $definitionResolver;
/**
* @var ReadableIndex
*/
private $index;
2017-01-20 13:26:14 +00:00
/**
* @var Parser
*/
private $parser;
/**
* @var Parser
*/
private $parserErrorHandler;
2017-01-19 18:33:31 +00:00
/**
* @param DefinitionResolver $definitionResolver
* @param ReadableIndex $index
*/
public function __construct(DefinitionResolver $definitionResolver, ReadableIndex $index)
{
$this->definitionResolver = $definitionResolver;
$this->index = $index;
2017-01-20 13:26:14 +00:00
$this->parser = new Parser;
$this->parserErrorHandler = new Collecting;
2017-01-19 18:33:31 +00:00
}
/**
* Returns signature help for a specific cursor position in a document
*
* @param PhpDocument $doc The opened document
* @param Position $pos The cursor position
* @return SignatureHelp
*/
2017-01-20 13:26:14 +00:00
public function provideSignature(PhpDocument $doc, Position $pos) : SignatureHelp
2017-01-19 18:33:31 +00:00
{
$help = new SignatureHelp;
$help->signatures = [];
2017-01-20 13:26:14 +00:00
$handle = fopen($doc->getUri(), 'r');
$lines = [];
for ($i = 0; $i < $pos->line; $i++) {
$lines[] = strlen(fgets($handle));
2017-01-19 20:18:14 +00:00
}
2017-01-20 13:26:14 +00:00
$filePos = ftell($handle) + $pos->character;
$line = substr(fgets($handle), 0, $pos->character);
fseek($handle, 0);
2017-01-19 20:18:14 +00:00
2017-01-20 13:26:14 +00:00
do {
$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);
2017-01-19 20:18:14 +00:00
2017-01-20 13:26:14 +00:00
if ($node === null) {
fclose($handle);
return $help;
2017-01-19 20:18:14 +00:00
}
2017-01-20 13:26:14 +00:00
$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) {
2017-01-19 20:18:14 +00:00
$node = $node->getAttribute('parentNode');
}
2017-01-20 13:26:14 +00:00
$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'));
2017-01-19 20:18:14 +00:00
if ($def = $this->definitionResolver->resolveReferenceNodeToDefinition($node->var)) {
2017-01-19 21:54:05 +00:00
$fqn = $def->fqn;
if (!$fqn) {
$fqns = DefinitionResolver::getFqnsFromType(
$this->definitionResolver->resolveExpressionNodeToType($node->var)
);
if (count($fqns)) {
$fqn = $fqns[0];
}
}
2017-01-20 13:26:14 +00:00
if ($fqn) {
2017-01-19 21:54:05 +00:00
$fqn = $fqn . '->' . $method . '()';
2017-01-20 13:26:14 +00:00
$def = $this->index->getDefinition($fqn);
2017-01-19 20:18:14 +00:00
}
}
2017-01-20 13:26:14 +00:00
} 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);
2017-01-19 18:33:31 +00:00
} else if ($node instanceof Node\Expr\StaticCall) {
2017-01-20 13:26:14 +00:00
fseek($handle, $node->getAttribute('startFilePos'));
$params = explode('(', fread($handle, $filePos - $node->getAttribute('startFilePos')), 2)[1];
$def = $this->definitionResolver->resolveReferenceNodeToDefinition($node);
2017-01-19 20:18:14 +00:00
} else if ($node instanceof Node\Expr\ClassConstFetch) {
2017-01-20 13:26:14 +00:00
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;
2017-01-20 13:36:10 +00:00
$params = ltrim($params, "( ");
2017-01-20 13:26:14 +00:00
if (strlen(trim($params))) {
try {
$params = $this->parser->parse('<?php $a = [' . $params . '];', $this->parserErrorHandler)[0]->expr->items;
2017-01-20 13:36:10 +00:00
$help->activeParameter = count($params) - 1;
} catch (\Exception $ignore) { }
2017-01-19 18:33:31 +00:00
}
2017-01-20 13:26:14 +00:00
$help->signatures[] = $signature;
2017-01-19 18:33:31 +00:00
}
return $help;
}
}