1
0
Fork 0

initial signature help commit

pull/438/head
Ivan Bozhanov 2017-07-15 02:12:48 +03:00
parent 35f33c8c91
commit 95a82dcdfe
16 changed files with 455 additions and 3 deletions

View File

@ -0,0 +1,7 @@
<?php
function helpFunc1(int $count = 0)
{
}
helpFunc1()

View File

@ -0,0 +1,7 @@
<?php
function helpFunc2(int $count = 0)
{
}
helpFunc2(

View File

@ -0,0 +1,15 @@
<?php
class HelpClass1
{
public function method(string $param = "")
{
}
public function test()
{
$this->method();
}
}
$a = new HelpClass1;
$a->method();

View File

@ -0,0 +1,17 @@
<?php
class HelpClass2
{
protected function method(string $param = "")
{
}
public function test()
{
$this->method(1,1);
}
}
$a = new HelpClass2;
$a
->method(
1,
array(),

View File

@ -0,0 +1,10 @@
<?php
class HelpClass3
{
public static function method(string $param = "")
{
}
}
HelpClass3::method()

View File

@ -0,0 +1,10 @@
<?php
class HelpClass4
{
public static function method(string $param = "")
{
}
}
HelpClass4::method(1

View File

@ -6,6 +6,7 @@ namespace LanguageServer;
use LanguageServer\Index\ReadableIndex;
use phpDocumentor\Reflection\{Types, Type, Fqsen, TypeResolver};
use LanguageServer\Protocol\SymbolInformation;
use LanguageServer\Protocol\ParameterInformation;
use Exception;
use Generator;
@ -97,6 +98,12 @@ class Definition
* @var string
*/
public $documentation;
/**
* Parameters array (for methods and functions), for use in textDocument/signatureHelp
*
* @var ParameterInformation[]
*/
public $parameters;
/**
* Yields the definitons of all ancestor classes (the Definition fqn is yielded as key)

View File

@ -5,6 +5,7 @@ namespace LanguageServer;
use LanguageServer\Index\ReadableIndex;
use LanguageServer\Protocol\SymbolInformation;
use LanguageServer\Protocol\ParameterInformation;
use Microsoft\PhpParser;
use Microsoft\PhpParser\Node;
use phpDocumentor\Reflection\{
@ -234,6 +235,18 @@ class DefinitionResolver
$def->documentation = $this->getDocumentationFromNode($node);
}
$def->parameters = [];
if (property_exists($node, 'parameters') && $node->parameters) {
foreach ($node->parameters->getElements() as $param) {
//var_dump($param); die();
$def->parameters[] = new ParameterInformation(
$this->getDeclarationLineFromNode($param),
//$param->getName(), // TODO: rebuild this
$this->getDocumentationFromNode($param)
);
}
}
return $def;
}

View File

@ -9,7 +9,8 @@ use LanguageServer\Protocol\{
TextDocumentSyncKind,
Message,
InitializeResult,
CompletionOptions
CompletionOptions,
SignatureHelpOptions
};
use LanguageServer\FilesFinder\{FilesFinder, ClientFilesFinder, FileSystemFilesFinder};
use LanguageServer\ContentRetriever\{ContentRetriever, ClientContentRetriever, FileSystemContentRetriever};
@ -277,6 +278,9 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
$serverCapabilities->completionProvider = new CompletionOptions;
$serverCapabilities->completionProvider->resolveProvider = false;
$serverCapabilities->completionProvider->triggerCharacters = ['$', '>'];
// support signature help
$serverCapabilities->signatureHelpProvider = new SignatureHelpOptions;
$serverCapabilities->signatureHelpProvider->triggerCharacters = ['(',','];
// Support global references
$serverCapabilities->xworkspaceReferencesProvider = true;
$serverCapabilities->xdefinitionProvider = true;

View File

@ -23,4 +23,13 @@ class ParameterInformation
* @var string|null
*/
public $documentation;
/**
* @param string $label The label of this signature. Will be shown in the UI.
* @param string|null $documentation The human-readable doc-comment of this signature.
*/
public function __construct(string $label = null, string $documentation = null)
{
$this->label = $label;
$this->documentation = $documentation;
}
}

View File

@ -29,4 +29,15 @@ class SignatureHelp
* @var int|null
*/
public $activeParameter;
/**
* @param SignatureInformation[] $signatures The signatures.
* @param int|null $activeSignature The active signature.
* @param int|null $activeParameter The active parameter of the active signature.
*/
public function __construct(array $signatures = [], int $activeSignature = null, int $activeParameter = null)
{
$this->signatures = $signatures;
$this->activeSignature = $activeSignature;
$this->activeParameter = $activeParameter;
}
}

View File

@ -31,4 +31,16 @@ class SignatureInformation
* @var ParameterInformation[]|null
*/
public $parameters;
/**
* @param string $label The label of this signature. Will be shown in the UI.
* @param string|null $documentation The human-readable doc-comment of this signature.
* @param ParameterInformation[]|null $parameters The parameters of this signature.
*/
public function __construct(string $label = null, string $documentation = null, array $parameters = null)
{
$this->label = $label;
$this->documentation = $documentation;
$this->parameters = $parameters;
}
}

View File

@ -4,7 +4,7 @@ declare(strict_types = 1);
namespace LanguageServer\Server;
use LanguageServer\{
CompletionProvider, LanguageClient, PhpDocument, PhpDocumentLoader, DefinitionResolver
CompletionProvider, LanguageClient, PhpDocument, PhpDocumentLoader, DefinitionResolver, SignatureHelpProvider
};
use LanguageServer\Index\ReadableIndex;
use LanguageServer\Protocol\{
@ -72,6 +72,10 @@ class TextDocument
* @var \stdClass|null
*/
protected $composerLock;
/**
* @var SignatureHelpProvider
*/
protected $signatureHelpProvider;
/**
* @param PhpDocumentLoader $documentLoader
@ -93,6 +97,7 @@ class TextDocument
$this->client = $client;
$this->definitionResolver = $definitionResolver;
$this->completionProvider = new CompletionProvider($this->definitionResolver, $index);
$this->signatureHelpProvider = new SignatureHelpProvider($this->definitionResolver, $index);
$this->index = $index;
$this->composerJson = $composerJson;
$this->composerLock = $composerLock;
@ -411,4 +416,12 @@ class TextDocument
return [new SymbolLocationInformation($descriptor, $def->symbolInformation->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->provideSignature($document, $position);
});
}
}

View File

@ -0,0 +1,92 @@
<?php
declare(strict_types = 1);
namespace LanguageServer;
use Microsoft\PhpParser\Node\DelimitedList\ArgumentExpressionList;
use Microsoft\PhpParser\Node\Expression\CallExpression;
use LanguageServer\Index\ReadableIndex;
use LanguageServer\Protocol\{
Range,
Position,
SignatureHelp,
SignatureInformation,
ParameterInformation
};
class SignatureHelpProvider
{
/**
* @var DefinitionResolver
*/
private $definitionResolver;
/**
* @var ReadableIndex
*/
private $index;
/**
* @param DefinitionResolver $definitionResolver
* @param ReadableIndex $index
*/
public function __construct(DefinitionResolver $definitionResolver, ReadableIndex $index)
{
$this->definitionResolver = $definitionResolver;
$this->index = $index;
}
/**
* 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
*/
public function provideSignature(PhpDocument $doc, Position $pos) : SignatureHelp
{
$node = $doc->getNodeAtPosition($pos);
while ($node &&
!($node instanceof ArgumentExpressionList) &&
!($node instanceof CallExpression) &&
$node->parent
) {
$node = $node->parent;
}
if (!($node instanceof ArgumentExpressionList) &&
!($node instanceof CallExpression)
) {
return new SignatureHelp;
}
$count = 0;
if ($node instanceof ArgumentExpressionList) {
foreach ($node->getElements() as $param) {
$count ++;
}
while ($node && !($node instanceof CallExpression) && $node->parent) {
$node = $node->parent;
}
if (!($node instanceof CallExpression)) {
return new SignatureHelp;
}
}
$def = $this->definitionResolver->resolveReferenceNodeToDefinition($node->callableExpression);
if (!$def) {
return new SignatureHelp;
}
$params = array_map(function ($v) {
return $v->label;
}, $def->parameters);
return new SignatureHelp(
[
new SignatureInformation(
trim(str_replace(['public', 'protected', 'private', 'function', 'static'], '', $def->declarationLine)),
$def->documentation,
$def->parameters
)
],
0,
$count < count($def->parameters) ? $count : null
);
}
}

View File

@ -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;

View File

@ -0,0 +1,222 @@
<?php
declare(strict_types = 1);
namespace LanguageServer\Tests\Server\TextDocument;
use PHPUnit\Framework\TestCase;
use LanguageServer\Tests\MockProtocolStream;
use LanguageServer\{Server, LanguageClient, PhpDocumentLoader, CompletionProvider, DefinitionResolver};
use LanguageServer\Index\{Index, ProjectIndex, DependenciesIndex, GlobalIndex, StubsIndex};
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
use LanguageServer\Protocol\{
TextDocumentIdentifier,
TextEdit,
Range,
Position,
ClientCapabilities,
SignatureHelp,
SignatureInformation,
ParameterInformation
};
use function LanguageServer\pathToUri;
class SignatureHelpTest extends TestCase
{
/**
* @var Server\TextDocument
*/
private $textDocument;
/**
* @var PhpDocumentLoader
*/
private $loader;
public function setUp()
{
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
$projectIndex = new ProjectIndex(new Index, new DependenciesIndex);
$definitionResolver = new DefinitionResolver($projectIndex);
$contentRetriever = new FileSystemContentRetriever;
$this->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/signature/methodClosed.php');
$this->loader->open($completionUri, file_get_contents($completionUri));
$result = $this->textDocument->signatureHelp(
new TextDocumentIdentifier($completionUri),
new Position(9, 22)
)->wait();
$this->assertEquals(new SignatureHelp(
[
new SignatureInformation(
'method(string $param = "")',
null,
[
new ParameterInformation('string $param = ""')
]
)
]
), $result);
}
public function testMethodClosedReference()
{
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/signature/methodClosed.php');
$this->loader->open($completionUri, file_get_contents($completionUri));
$result = $this->textDocument->signatureHelp(
new TextDocumentIdentifier($completionUri),
new Position(14, 11)
)->wait();
$this->assertEquals(new SignatureHelp(
[
new SignatureInformation(
'method(string $param = "")',
null,
[
new ParameterInformation('string $param = ""')
]
)
]
), $result);
}
public function testMethodNotClosed()
{
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/signature/methodNotClosed.php');
$this->loader->open($completionUri, file_get_contents($completionUri));
$result = $this->textDocument->signatureHelp(
new TextDocumentIdentifier($completionUri),
new Position(9, 22)
)->wait();
$this->assertEquals(new SignatureHelp(
[
new SignatureInformation(
'method(string $param = "")',
null,
[
new ParameterInformation('string $param = ""')
]
)
]
), $result);
}
public function testMethodNotClosedReference()
{
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/signature/methodNotClosed.php');
$this->loader->open($completionUri, file_get_contents($completionUri));
$result = $this->textDocument->signatureHelp(
new TextDocumentIdentifier($completionUri),
new Position(14, 14)
)->wait();
$this->assertEquals(new SignatureHelp(
[
new SignatureInformation(
'method(string $param = "")',
null,
[
new ParameterInformation('string $param = ""')
]
)
]
), $result);
}
public function testFuncClosed()
{
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/signature/funcClosed.php');
$this->loader->open($completionUri, file_get_contents($completionUri));
$result = $this->textDocument->signatureHelp(
new TextDocumentIdentifier($completionUri),
new Position(6, 10)
)->wait();
$this->assertEquals(new SignatureHelp(
[
new SignatureInformation(
'helpFunc1(int $count = 0)',
null,
[
new ParameterInformation('int $count = 0')
]
)
]
), $result);
}
public function testFuncNotClosed()
{
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/signature/funcNotClosed.php');
$this->loader->open($completionUri, file_get_contents($completionUri));
$result = $this->textDocument->signatureHelp(
new TextDocumentIdentifier($completionUri),
new Position(6, 10)
)->wait();
$this->assertEquals(new SignatureHelp(
[
new SignatureInformation(
'helpFunc2(int $count = 0)',
null,
[
new ParameterInformation('int $count = 0')
]
)
]
), $result);
}
public function testStaticClosed()
{
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/signature/staticClosed.php');
$this->loader->open($completionUri, file_get_contents($completionUri));
$result = $this->textDocument->signatureHelp(
new TextDocumentIdentifier($completionUri),
new Position(9, 19)
)->wait();
$this->assertEquals(new SignatureHelp(
[
new SignatureInformation(
'method(string $param = "")',
null,
[
new ParameterInformation('string $param = ""')
]
)
]
), $result);
}
public function testStaticNotClosed()
{
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/signature/staticNotClosed.php');
$this->loader->open($completionUri, file_get_contents($completionUri));
$result = $this->textDocument->signatureHelp(
new TextDocumentIdentifier($completionUri),
new Position(9, 19)
)->wait();
$this->assertEquals(new SignatureHelp(
[
new SignatureInformation(
'method(string $param = "")',
null,
[
new ParameterInformation('string $param = ""')
]
)
]
), $result);
}
}