From 279e2fb996fdedc3c6b8dc7753ef970c288be618 Mon Sep 17 00:00:00 2001 From: Felix Becker Date: Thu, 17 Nov 2016 23:25:01 +0100 Subject: [PATCH] Cache hover --- src/Definition.php | 14 ++++++ src/DefinitionResolver.php | 60 +++++++++++++++++++++++ src/NodeVisitor/DefinitionCollector.php | 5 +- src/Server/TextDocument.php | 65 +++---------------------- 4 files changed, 86 insertions(+), 58 deletions(-) diff --git a/src/Definition.php b/src/Definition.php index 021c66e..cba69ab 100644 --- a/src/Definition.php +++ b/src/Definition.php @@ -45,4 +45,18 @@ class Definition * @var \phpDocumentor\Type|null */ public $type; + + /** + * The first line of the declaration, for use in textDocument/hover + * + * @var string + */ + public $declarationLine; + + /** + * A documentation string, for use in textDocument/hover + * + * @var string + */ + public $documentation; } diff --git a/src/DefinitionResolver.php b/src/DefinitionResolver.php index 87e8837..2bfd4ad 100644 --- a/src/DefinitionResolver.php +++ b/src/DefinitionResolver.php @@ -4,6 +4,7 @@ declare(strict_types = 1); namespace LanguageServer; use PhpParser\Node; +use PhpParser\PrettyPrinter\Standard as PrettyPrinter; use phpDocumentor\Reflection\{Types, Type, Fqsen, TypeResolver}; use LanguageServer\Protocol\SymbolInformation; use Sabre\Event\Promise; @@ -18,6 +19,61 @@ class DefinitionResolver { $this->project = $project; $this->typeResolver = new TypeResolver; + $this->prettyPrinter = new PrettyPrinter; + } + + /** + * Builds the declaration line for a given node + * + * @param Node $node + * @return string + */ + public function getDeclarationLineFromNode(Node $node): string + { + if ($node instanceof Node\Stmt\PropertyProperty || $node instanceof Node\Const_) { + // Properties and constants can have multiple declarations + // Use the parent node (that includes the modifiers), but only render the requested declaration + $child = $node; + $node = $node->getAttribute('parentNode'); + $defLine = clone $node; + $defLine->props = [$child]; + } else { + $defLine = clone $node; + } + // Don't include the docblock in the declaration string + $defLine->setAttribute('comments', []); + if (isset($defLine->stmts)) { + $defLine->stmts = []; + } + $defText = $this->prettyPrinter->prettyPrint([$defLine]); + return strstr($defText, "\n", true) ?: $defText; + } + + /** + * Gets the documentation string for a node, if it has one + * + * @param Node $node + * @return string|null + */ + public function getDocumentationFromNode(Node $node) + { + if ($node instanceof Node\Param) { + $fn = $node->getAttribute('parentNode'); + $docBlock = $fn->getAttribute('docBlock'); + if ($docBlock !== null) { + $tags = $docBlock->getTagsByName('param'); + foreach ($tags as $tag) { + if ($tag->getVariableName() === $node->name) { + return $tag->getDescription()->render(); + } + } + } + } else { + $docBlock = $node->getAttribute('docBlock'); + if ($docBlock !== null) { + return $docBlock->getSummary(); + } + } } /** @@ -35,6 +91,10 @@ class DefinitionResolver $def = new Definition; // Get symbol information from node (range, symbol kind) $def->symbolInformation = SymbolInformation::fromNode($defNode); + // Declaration line + $def->declarationLine = $this->getDeclarationLineFromNode($node); + // Documentation + $def->documentation = $this->getDocumentationFromNode($node); if ($defNode instanceof Node\Param) { // Get parameter type $def->type = $this->getTypeFromNode($defNode); diff --git a/src/NodeVisitor/DefinitionCollector.php b/src/NodeVisitor/DefinitionCollector.php index 0cf5ea9..162f670 100644 --- a/src/NodeVisitor/DefinitionCollector.php +++ b/src/NodeVisitor/DefinitionCollector.php @@ -27,7 +27,7 @@ class DefinitionCollector extends NodeVisitorAbstract */ public $nodes = []; - public $definitionResolver; + private $definitionResolver; public function __construct(DefinitionResolver $definitionResolver) { @@ -46,6 +46,9 @@ class DefinitionCollector extends NodeVisitorAbstract $def->fqn = $fqn; $def->symbolInformation = SymbolInformation::fromNode($node, $fqn); $def->type = $this->definitionResolver->getTypeFromNode($node); + $def->declarationLine = $this->definitionResolver->getDeclarationLineFromNode($node); + $def->documentation = $this->definitionResolver->getDocumentationFromNode($node); + $this->definitions[$fqn] = $def; } } diff --git a/src/Server/TextDocument.php b/src/Server/TextDocument.php index a928e8b..6c67388 100644 --- a/src/Server/TextDocument.php +++ b/src/Server/TextDocument.php @@ -196,66 +196,17 @@ class TextDocument return new Hover([]); } $range = Range::fromNode($node); - if ($node instanceof Node\Expr\Variable) { - $defNode = DefinitionResolver::resolveVariableToNode($node); - } else { - // Get the definition for whatever node is under the cursor - $def = $this->definitionResolver->resolveReferenceNodeToDefinition($node); - if ($def === null) { - return new Hover([], $range); - } - // TODO inefficient. Add documentation and declaration line to Definition class - // so document doesnt have to be loaded - $document = yield $this->project->getOrLoadDocument($def->symbolInformation->location->uri); - if ($document === null) { - return new Hover([], $range); - } - $defNode = $document->getDefinitionNodeByFqn($def->fqn); + // Get the definition for whatever node is under the cursor + $def = $this->definitionResolver->resolveReferenceNodeToDefinition($node); + if ($def === null) { + return new Hover([], $range); } - $contents = []; - - // Build a declaration string - if ($defNode instanceof Node\Stmt\PropertyProperty || $defNode instanceof Node\Const_) { - // Properties and constants can have multiple declarations - // Use the parent node (that includes the modifiers), but only render the requested declaration - $child = $defNode; - $defNode = $defNode->getAttribute('parentNode'); - $defLine = clone $defNode; - $defLine->props = [$child]; - } else { - $defLine = clone $defNode; + if ($def->declarationLine) { + $contents[] = new MarkedString('php', "declarationLine); } - // Don't include the docblock in the declaration string - $defLine->setAttribute('comments', []); - if (isset($defLine->stmts)) { - $defLine->stmts = []; + if ($def->documentation) { + $contents[] = $def->documentation; } - $defText = $this->prettyPrinter->prettyPrint([$defLine]); - $lines = explode("\n", $defText); - if (isset($lines[0])) { - $contents[] = new MarkedString('php', "getAttribute('parentNode'); - $docBlock = $fn->getAttribute('docBlock'); - if ($docBlock !== null) { - $tags = $docBlock->getTagsByName('param'); - foreach ($tags as $tag) { - if ($tag->getVariableName() === $defNode->name) { - $contents[] = $tag->getDescription()->render(); - break; - } - } - } - } else { - $docBlock = $defNode->getAttribute('docBlock'); - if ($docBlock !== null) { - $contents[] = $docBlock->getSummary(); - } - } - return new Hover($contents, $range); }); }