-
Notifications
You must be signed in to change notification settings - Fork 185
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
Signature help #250
Conversation
Current coverage is 87.37% (diff: 75.00%)@@ 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
|
There was a problem hiding this 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[] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be ParameterInformation[]
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); | ||
} |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 = ['(']; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 = []; |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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()
?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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')) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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 = []) |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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 = []) |
There was a problem hiding this comment.
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
I am closing this as this solution will now longer work as it depends on the Lexer from nikic's PHP parser. |
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.