Skip to content

Signature help #250

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 24 commits into from
Closed

Signature help #250

wants to merge 24 commits into from

Conversation

vakata
Copy link
Contributor

@vakata vakata commented Jan 20, 2017

Closes #18
Parameter counting is "hackish" and working with php://temp maybe undesired? I also had to store parsed parameters along with the definition (similar to declarationLine) to avoid parsing again.

@codecov-io
Copy link

codecov-io commented Jan 20, 2017

Current coverage is 87.37% (diff: 75.00%)

Merging #250 into master will decrease coverage by 1.22%

@@             master       #250   diff @@
==========================================
  Files            51         55     +4   
  Lines          1412       1552   +140   
  Methods         152        158     +6   
  Messages          0          0          
  Branches          0          0          
==========================================
+ Hits           1251       1356   +105   
- Misses          161        196    +35   
  Partials          0          0          
Diff Coverage File Path
••••••• 70% new src/SignatureHelpProvider.php
•••••••••• 100% src/DefinitionResolver.php
•••••••••• 100% src/Protocol/ParameterInformation.php
•••••••••• 100% src/Server/TextDocument.php
•••••••••• 100% src/Protocol/SignatureHelp.php
•••••••••• 100% src/Protocol/SignatureInformation.php

Powered by Codecov. Last update 47b5b67...d4d0c47

Copy link
Owner

@felixfbecker felixfbecker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me know if I can help you with anything

/**
* Parameters array (for methods and functions), for use in textDocument/signatureHelp
*
* @var string[]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be ParameterInformation[]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You want me to build the objects here? Won't that use more memory than plain strings, which are converted to ParameterInformation when needed?

Copy link
Owner

@felixfbecker felixfbecker Jan 21, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand your code correctly this string array only contains the pretty-printed parameter node. That means you cannot construct the documentation property from it, which you need to read from the @param tag. Of course as a new feature it is expected to use more memory.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I currently populate only the label (using this string). I will add the documentation and construct ParameterInformation objects - of course that would mean an object for every parameter of every method / function in the project. I will test and let you know how it impacts memory usage.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@felixfbecker the impact was not significant

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI we already keep Hover and SymbolInformation in memory. I thought about changing Definition so that it contains just the data necessary to construct those and then build them from this, but I would prefer to first implement the features and then improve perf.

If memory usage is too high the implementation would not save anything in Definition and load the document on demand.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can leave out the docs first but lets use objects from the start on.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is already done, and the docs are in place as well. I added a comment for you though - Param nodes do not have a parentNode attribute - I could not fix it - the attribute was added in ReferenceAdder but then was not available at a later point.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@felixfbecker anything I can help with here?

// Resolve $this
if ($node->name === 'this' && $fqn = $this->getContainingClassFqn($node)) {
return $this->index->getDefinition($fqn, false);
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please keep this out of this PR

Copy link
Contributor Author

@vakata vakata Jan 21, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is actually needed in order to provide signatureHelp when invoking a method on $this, and those lines are not part of the $this autocomplete PR, as it was not needed for autocomplete - different parts of the code are involved. I could move it to a separate PR?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. If you do it in a separate PR I think I would merge it straight away, change looks good.

@@ -247,6 +248,9 @@ public function initialize(ClientCapabilities $capabilities, string $rootPath =
$serverCapabilities->completionProvider = new CompletionOptions;
$serverCapabilities->completionProvider->resolveProvider = false;
$serverCapabilities->completionProvider->triggerCharacters = ['$', '>'];
// Support "Signature Help"
$serverCapabilities->signatureHelpProvider = new SignatureHelpOptions;
$serverCapabilities->signatureHelpProvider->triggerCharacters = ['('];
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add comma as a trigger character

{
$this->definitionResolver = $definitionResolver;
$this->index = $index;
$this->parser = new Parser;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need a new parser instance here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry - that was leftover - I eventually used Lexer directly - will remove.

public function provideSignature(PhpDocument $doc, Position $pos) : SignatureHelp
{
$help = new SignatureHelp;
$help->signatures = [];
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change the constructor of SignatureHelp to initialize this


$handle = fopen('php://temp', 'r+');
fwrite($handle, $doc->getContent());
fseek($handle, 0);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have the whole content as a string, so opening a stream for this provides no benefit at all.
Did you try solving this just with the PhpParser node offsets you get from getNodeAtPosition()?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but unfortunately most of the time when dealing with an unclosed brace the nearest node was very far from the actual position. As for the buffer - I will rewrite and use string operations, but I doubt I will have the time to do it tonight.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you give an example?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not have the time to prepare an example at the moment. The problem was that the parser errors before reaching the cursor position, which means there is no node at that position - it should be easy to reproduce.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case lets open issues for better error recovery at these places. A lot of improvements were already made in that area.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just checked - the methodNotClosed test requires at least a couple of symbols of backtracking before getNodeAtPosition returns a node.

Copy link
Contributor Author

@vakata vakata Jan 21, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this should remain open until I rewrite it to use getContent instead of a buffer? Or would you rather wait for improvements to PhpParser and then remove backtracking completely?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is weird to use the parser for completion, but the lexer/content for signature help.
If error recovery works for completion for methods, it should work for the parameters too, right?
What is the emitted AST at the point where the parser "fails" for the signature help use case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The node at the requested position is null, after backtracking 31 symbols (each of those positions is still null) we get to an Error node, whose parent is a PropertyFetch node. The $var property of the fetch is populated, but the $name property is not (it is the Error node). This is why I used string operations to get the method being called, and then - the Lexer with some custom logic on top to get the parameter count.
As I said in the Signature Help issue - it is a messy solution, but it is the only one I could come up with. I am sure someone with superior skills can take it a few steps further to something elegant and more robust :)


$help = new SignatureHelp;
$help->signatures = [];
$info = new SignatureInformation;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add constructors to the classes that allow to pass all properties as arguments so we can define the result inline without saving it to a variable

@@ -134,7 +135,13 @@ public function createDefinitionFromNode(Node $node, string $fqn = null): Defini
$def->parameters = [];
if ($node instanceof Node\FunctionLike) {
foreach ($node->getParams() as $param) {
$def->parameters[] = $this->prettyPrinter->prettyPrint([$param]);
if (!$param->getAttribute('parentNode')) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@felixfbecker I could not figure out why those instances do not have a parentNode attribute - this is a temporary patch. Could you have a look?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every node except the root node should have a parentNode
Otherwise there must be a bug in NodeVisitor or ReferencesAdder

* @param bool $activeSignature The active signature.
* @param bool $activeParameter The active parameter of the active signature.
*/
public function __construct(array $signatures = [], int $activeSignature = 0, int $activeParameter = 0)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use null as default value for the last two params because they are optional on the object.

/**
* @param SignatureInformation[] $signatures The signatures.
* @param bool $activeSignature The active signature.
* @param bool $activeParameter The active parameter of the active signature.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

int|null

* @param string|null $documentation The human-readable doc-comment of this signature.
* @param ParameterInformation[] $parameters The parameters of this signature.
*/
public function __construct(string $label = null, string $documentation = null, array $parameters = [])
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use null as default for $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[] $parameters The parameters of this signature.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adapt type for $parameters

* @param ParameterInformation[] $parameters The parameters of this signature.
* @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 = [])
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

default value for $parameters must be null

@vakata
Copy link
Contributor Author

vakata commented Jun 17, 2017

I am closing this as this solution will now longer work as it depends on the Lexer from nikic's PHP parser.

@vakata vakata closed this Jun 17, 2017
@vakata vakata deleted the signatureHelp branch June 17, 2017 21:01
@vakata vakata restored the signatureHelp branch June 17, 2017 21:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants