very rough first go at signatureHelp
parent
63bf43e40c
commit
f2fdfa5bc3
|
@ -9,7 +9,8 @@ use LanguageServer\Protocol\{
|
||||||
TextDocumentSyncKind,
|
TextDocumentSyncKind,
|
||||||
Message,
|
Message,
|
||||||
InitializeResult,
|
InitializeResult,
|
||||||
CompletionOptions
|
CompletionOptions,
|
||||||
|
SignatureHelpOptions
|
||||||
};
|
};
|
||||||
use LanguageServer\FilesFinder\{FilesFinder, ClientFilesFinder, FileSystemFilesFinder};
|
use LanguageServer\FilesFinder\{FilesFinder, ClientFilesFinder, FileSystemFilesFinder};
|
||||||
use LanguageServer\ContentRetriever\{ContentRetriever, ClientContentRetriever, FileSystemContentRetriever};
|
use LanguageServer\ContentRetriever\{ContentRetriever, ClientContentRetriever, FileSystemContentRetriever};
|
||||||
|
@ -277,6 +278,10 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
||||||
$serverCapabilities->completionProvider = new CompletionOptions;
|
$serverCapabilities->completionProvider = new CompletionOptions;
|
||||||
$serverCapabilities->completionProvider->resolveProvider = false;
|
$serverCapabilities->completionProvider->resolveProvider = false;
|
||||||
$serverCapabilities->completionProvider->triggerCharacters = ['$', '>'];
|
$serverCapabilities->completionProvider->triggerCharacters = ['$', '>'];
|
||||||
|
|
||||||
|
$serverCapabilities->signatureHelpProvider = new SignatureHelpOptions();
|
||||||
|
$serverCapabilities->signatureHelpProvider->triggerCharacters = ['(', ','];
|
||||||
|
|
||||||
// Support global references
|
// Support global references
|
||||||
$serverCapabilities->xworkspaceReferencesProvider = true;
|
$serverCapabilities->xworkspaceReferencesProvider = true;
|
||||||
$serverCapabilities->xdefinitionProvider = true;
|
$serverCapabilities->xdefinitionProvider = true;
|
||||||
|
|
|
@ -4,7 +4,7 @@ declare(strict_types = 1);
|
||||||
namespace LanguageServer\Server;
|
namespace LanguageServer\Server;
|
||||||
|
|
||||||
use LanguageServer\{
|
use LanguageServer\{
|
||||||
CompletionProvider, LanguageClient, PhpDocument, PhpDocumentLoader, DefinitionResolver
|
CompletionProvider, SignatureHelpProvider, LanguageClient, PhpDocument, PhpDocumentLoader, DefinitionResolver
|
||||||
};
|
};
|
||||||
use LanguageServer\Index\ReadableIndex;
|
use LanguageServer\Index\ReadableIndex;
|
||||||
use LanguageServer\Protocol\{
|
use LanguageServer\Protocol\{
|
||||||
|
@ -58,6 +58,8 @@ class TextDocument
|
||||||
*/
|
*/
|
||||||
protected $completionProvider;
|
protected $completionProvider;
|
||||||
|
|
||||||
|
protected $signatureHelpProvider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var ReadableIndex
|
* @var ReadableIndex
|
||||||
*/
|
*/
|
||||||
|
@ -93,6 +95,7 @@ class TextDocument
|
||||||
$this->client = $client;
|
$this->client = $client;
|
||||||
$this->definitionResolver = $definitionResolver;
|
$this->definitionResolver = $definitionResolver;
|
||||||
$this->completionProvider = new CompletionProvider($this->definitionResolver, $index);
|
$this->completionProvider = new CompletionProvider($this->definitionResolver, $index);
|
||||||
|
$this->signatureHelpProvider = new SignatureHelpProvider($this->definitionResolver, $index, $documentLoader);
|
||||||
$this->index = $index;
|
$this->index = $index;
|
||||||
$this->composerJson = $composerJson;
|
$this->composerJson = $composerJson;
|
||||||
$this->composerLock = $composerLock;
|
$this->composerLock = $composerLock;
|
||||||
|
@ -250,6 +253,22 @@ class TextDocument
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The goto definition request is sent from the client to the server to resolve the definition location of a symbol
|
||||||
|
* at a given text document position.
|
||||||
|
*
|
||||||
|
* @param TextDocumentIdentifier $textDocument The text document
|
||||||
|
* @param Position $position The position inside the text document
|
||||||
|
* @return Promise <Location|Location[]>
|
||||||
|
*/
|
||||||
|
public function signatureHelp(TextDocumentIdentifier $textDocument, Position $position): Promise
|
||||||
|
{
|
||||||
|
return coroutine(function () use ($textDocument, $position) {
|
||||||
|
$document = yield $this->documentLoader->getOrLoad($textDocument->uri);
|
||||||
|
return $this->signatureHelpProvider->getSignatureHelp($document, $position);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The goto definition request is sent from the client to the server to resolve the definition location of a symbol
|
* The goto definition request is sent from the client to the server to resolve the definition location of a symbol
|
||||||
* at a given text document position.
|
* at a given text document position.
|
||||||
|
|
|
@ -0,0 +1,194 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace LanguageServer;
|
||||||
|
|
||||||
|
use LanguageServer\Index\ReadableIndex;
|
||||||
|
use LanguageServer\Protocol\{
|
||||||
|
Position,
|
||||||
|
SignatureHelp,
|
||||||
|
SignatureInformation,
|
||||||
|
ParameterInformation
|
||||||
|
};
|
||||||
|
use Microsoft\PhpParser;
|
||||||
|
use Microsoft\PhpParser\Node;
|
||||||
|
|
||||||
|
class SignatureHelpProvider
|
||||||
|
{
|
||||||
|
/** @var DefinitionResolver */
|
||||||
|
private $definitionResolver;
|
||||||
|
|
||||||
|
/** @var ReadableIndex */
|
||||||
|
private $index;
|
||||||
|
|
||||||
|
private $documentLoader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param DefinitionResolver $definitionResolver
|
||||||
|
* @param ReadableIndex $index
|
||||||
|
*/
|
||||||
|
public function __construct(DefinitionResolver $definitionResolver, ReadableIndex $index, PhpDocumentLoader $documentLoader)
|
||||||
|
{
|
||||||
|
$this->definitionResolver = $definitionResolver;
|
||||||
|
$this->index = $index;
|
||||||
|
$this->documentLoader = $documentLoader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSignatureHelp(PhpDocument $doc, Position $position): SignatureHelp
|
||||||
|
{
|
||||||
|
// Find the node under the cursor
|
||||||
|
$node = $doc->getNodeAtPosition($position);
|
||||||
|
|
||||||
|
$fqn = null;
|
||||||
|
|
||||||
|
// First find the node that the call belongs to
|
||||||
|
if ($node instanceof Node\DelimitedList\ArgumentExpressionList) {
|
||||||
|
$argumentExpressionList = $node;
|
||||||
|
if ($node->parent instanceof Node\Expression\ObjectCreationExpression) {
|
||||||
|
$node = $node->parent->classTypeDesignator;
|
||||||
|
if (!$node instanceof Node\QualifiedName) {
|
||||||
|
return new SignatureHelp();
|
||||||
|
}
|
||||||
|
$fqn = $this->definitionResolver->resolveReferenceNodeToFqn($node);
|
||||||
|
$fqn = "{$fqn}->__construct()";
|
||||||
|
} else {
|
||||||
|
$node = $node->parent->getFirstChildNode(
|
||||||
|
Node\Expression\MemberAccessExpression::class,
|
||||||
|
Node\Expression\ScopedPropertyAccessExpression::class,
|
||||||
|
Node\QualifiedName::class
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} elseif ($node instanceof Node\Expression\CallExpression) {
|
||||||
|
$argumentExpressionList = $node->getFirstChildNode(Node\DelimitedList\ArgumentExpressionList::class);
|
||||||
|
$node = $node->getFirstChildNode(
|
||||||
|
Node\Expression\MemberAccessExpression::class,
|
||||||
|
Node\Expression\ScopedPropertyAccessExpression::class,
|
||||||
|
Node\QualifiedName::class
|
||||||
|
);
|
||||||
|
} elseif ($node instanceof Node\Expression\ObjectCreationExpression) {
|
||||||
|
$argumentExpressionList = $node->getFirstChildNode(Node\DelimitedList\ArgumentExpressionList::class);
|
||||||
|
//$node = $node->getFirstChildNode(Node\QualifiedName::class);
|
||||||
|
$node = $node->classTypeDesignator;
|
||||||
|
if (!$node instanceof Node\QualifiedName) {
|
||||||
|
return new SignatureHelp();
|
||||||
|
}
|
||||||
|
$fqn = $this->definitionResolver->resolveReferenceNodeToFqn($node);
|
||||||
|
$fqn = "{$fqn}->__construct()";
|
||||||
|
} else {
|
||||||
|
$node = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$node) {
|
||||||
|
return new SignatureHelp();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now find the definition of the call
|
||||||
|
$fqn = $fqn ?: DefinitionResolver::getDefinedFqn($node);
|
||||||
|
if ($fqn) {
|
||||||
|
$def = $this->index->getDefinition($fqn);
|
||||||
|
} else {
|
||||||
|
$def = $this->definitionResolver->resolveReferenceNodeToDefinition($node);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$def) {
|
||||||
|
return new SignatureHelp();
|
||||||
|
}
|
||||||
|
|
||||||
|
$activeParam = $argumentExpressionList
|
||||||
|
? $this->findActiveParameter($argumentExpressionList, $position, $doc)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
$doc = $this->documentLoader->get($def->symbolInformation->location->uri);
|
||||||
|
if (!$doc) {
|
||||||
|
return new SignatureHelp();
|
||||||
|
}
|
||||||
|
$node = $doc->getNodeAtPosition($def->symbolInformation->location->range->start);
|
||||||
|
$params = $this->getParameters($node, $doc);
|
||||||
|
$label = $this->getLabel($node, $params, $doc);
|
||||||
|
$signatureInformation = new SignatureInformation();
|
||||||
|
$signatureInformation->label = $label;
|
||||||
|
$signatureInformation->parameters = $params;
|
||||||
|
$signatureHelp = new SignatureHelp();
|
||||||
|
$signatureHelp->signatures = [$signatureInformation];
|
||||||
|
$signatureHelp->activeParameter = $activeParam;
|
||||||
|
return $signatureHelp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Node\MethodDeclaration|Node\Statement\FunctionDeclaration $node
|
||||||
|
*/
|
||||||
|
private function getLabel($node, array $params, PhpDocument $doc): string
|
||||||
|
{
|
||||||
|
//$label = $node->getName() . '(';
|
||||||
|
$label = '(';
|
||||||
|
if ($params) {
|
||||||
|
foreach ($params as $param) {
|
||||||
|
$label .= $param->label . ', ';
|
||||||
|
}
|
||||||
|
$label = substr($label, 0, -2);
|
||||||
|
}
|
||||||
|
$label .= ')';
|
||||||
|
/*
|
||||||
|
if ($node->returnType) {
|
||||||
|
$label .= ': ';
|
||||||
|
if ($node->returnType instanceof QualifiedName) {
|
||||||
|
$label .= $node->returnType->getResolvedName();
|
||||||
|
} else {
|
||||||
|
$label .= $node->returnType->getText($doc->getContent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return $label;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Node\MethodDeclaration|Node\Statement\FunctionDeclaration $node
|
||||||
|
*/
|
||||||
|
private function getParameters($node, PhpDocument $doc): array
|
||||||
|
{
|
||||||
|
$params = [];
|
||||||
|
if ($node->parameters) {
|
||||||
|
foreach ($node->parameters->getElements() as $element) {
|
||||||
|
$param = (string) $this->definitionResolver->getTypeFromNode($element);
|
||||||
|
$param .= ' ' . $element->variableName->getText($doc->getContent());
|
||||||
|
if ($element->default) {
|
||||||
|
$param .= ' = ' . $element->default->getText($doc->getContent());
|
||||||
|
}
|
||||||
|
$info = new ParameterInformation();
|
||||||
|
$info->label = $param;
|
||||||
|
$info->documentation = $this->definitionResolver->getDocumentationFromNode($element);
|
||||||
|
$params[] = $info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $params;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function findActiveParameter(
|
||||||
|
Node\DelimitedList\ArgumentExpressionList $argumentExpressionList,
|
||||||
|
Position $position,
|
||||||
|
PhpDocument $doc
|
||||||
|
): int {
|
||||||
|
$args = $argumentExpressionList->children;
|
||||||
|
$i = 0;
|
||||||
|
$found = null;
|
||||||
|
foreach ($args as $arg) {
|
||||||
|
if ($arg instanceof Node) {
|
||||||
|
$start = $arg->getFullStart();
|
||||||
|
$end = $arg->getEndPosition();
|
||||||
|
++$i;
|
||||||
|
} else {
|
||||||
|
$start = $arg->fullStart;
|
||||||
|
$end = $start + $arg->length;
|
||||||
|
}
|
||||||
|
$offset = $position->toOffset($doc->getContent());
|
||||||
|
if ($offset >= $start && $offset <= $end) {
|
||||||
|
$found = $i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is_null($found)) {
|
||||||
|
$found = $i;
|
||||||
|
}
|
||||||
|
return $found;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue