diff --git a/src/Definition.php b/src/Definition.php index d81594db..d101f9f1 100644 --- a/src/Definition.php +++ b/src/Definition.php @@ -72,6 +72,11 @@ class Definition */ public $symbolInformation; + /** + * @var Location + */ + public $name; + /** * The type a reference to this symbol will resolve to. * For properties and constants, this is the type of the property/constant. diff --git a/src/DefinitionResolver.php b/src/DefinitionResolver.php index adddf774..f425b976 100644 --- a/src/DefinitionResolver.php +++ b/src/DefinitionResolver.php @@ -5,6 +5,7 @@ use LanguageServer\Index\ReadableIndex; use LanguageServer\Factory\SymbolInformationFactory; +use LanguageServer\Factory\LocationFactory; use LanguageServerProtocol\SymbolInformation; use Microsoft\PhpParser; use Microsoft\PhpParser\Node; @@ -236,6 +237,13 @@ public function createDefinitionFromNode(Node $node, string $fqn = null): Defini $def->symbolInformation = SymbolInformationFactory::fromNode($node, $fqn); + if ($node instanceof Node\Statement\ClassDeclaration || + $node instanceof Node\MethodDeclaration) { + $def->name = LocationFactory::fromToken($node, $node->name); + } elseif ($node instanceof Node\Expression\Variable) { + $def->name = LocationFactory::fromToken($node, $node->name); + $def->name->range->start->character++; + } if ($def->symbolInformation !== null) { $def->type = $this->getTypeFromNode($node); $def->declarationLine = $this->getDeclarationLineFromNode($node); diff --git a/src/Factory/LocationFactory.php b/src/Factory/LocationFactory.php index 3ccc80b7..01329d6c 100644 --- a/src/Factory/LocationFactory.php +++ b/src/Factory/LocationFactory.php @@ -6,6 +6,7 @@ use LanguageServerProtocol\Position; use LanguageServerProtocol\Range; use Microsoft\PhpParser\Node; +use Microsoft\PhpParser\Token; use Microsoft\PhpParser\PositionUtilities; class LocationFactory @@ -29,4 +30,24 @@ public static function fromNode(Node $node): Location new Position($range->end->line, $range->end->character) )); } + + /** + * Returns the location of the token + * + * @param Token $node + * @return self + */ + public static function fromToken(Node $node, Token $token): Location + { + $range = PositionUtilities::getRangeFromPosition( + $token->getStartPosition(), + $token->getWidth(), + $node->getFileContents() + ); + + return new Location($node->getUri(), new Range( + new Position($range->start->line, $range->start->character), + new Position($range->end->line, $range->end->character) + )); + } } diff --git a/src/Index/Index.php b/src/Index/Index.php index 0d61f9ca..39e8d70d 100644 --- a/src/Index/Index.php +++ b/src/Index/Index.php @@ -423,6 +423,9 @@ private function indexDefinition(int $level, array $parts, array &$storage, Defi */ private function removeIndexedDefinition(int $level, array $parts, array &$storage, array &$rootStorage) { + if (empty($parts)) { + return; + } $part = $parts[$level]; if ($level + 1 === count($parts)) { diff --git a/src/Server/TextDocument.php b/src/Server/TextDocument.php index 039ff578..56c1942f 100644 --- a/src/Server/TextDocument.php +++ b/src/Server/TextDocument.php @@ -4,7 +4,7 @@ namespace LanguageServer\Server; use LanguageServer\{ - CompletionProvider, SignatureHelpProvider, LanguageClient, PhpDocument, PhpDocumentLoader, DefinitionResolver + CompletionProvider, SignatureHelpProvider, LanguageClient, PhpDocument, PhpDocumentLoader, DefinitionResolver, Definition }; use LanguageServer\Index\ReadableIndex; use LanguageServer\Factory\LocationFactory; @@ -23,6 +23,8 @@ TextDocumentIdentifier, TextDocumentItem, VersionedTextDocumentIdentifier, + WorkspaceEdit, + TextEdit, CompletionContext }; use Microsoft\PhpParser\Node; @@ -179,7 +181,7 @@ public function references( TextDocumentIdentifier $textDocument, Position $position ): Promise { - return coroutine(function () use ($textDocument, $position) { + return coroutine(function () use ($context, $textDocument, $position) { $document = yield $this->documentLoader->getOrLoad($textDocument->uri); $node = $document->getNodeAtPosition($position); if ($node === null) { @@ -190,7 +192,7 @@ public function references( // by traversing the AST if ( - ($node instanceof Node\Expression\Variable && !($node->getParent()->getParent() instanceof Node\PropertyDeclaration)) + ($node instanceof Node\Expression\Variable && !($node->getParent()->getParent() instanceof Node\PropertyDeclaration) && !($node->getParent()->getParent()->getParent() instanceof Node\PropertyDeclaration)) || $node instanceof Node\Parameter || $node instanceof Node\UseVariableName ) { @@ -210,7 +212,14 @@ public function references( if ($descendantNode instanceof Node\Expression\Variable && $descendantNode->getName() === $node->getName() ) { - $locations[] = LocationFactory::fromNode($descendantNode); + $location = LocationFactory::fromNode($descendantNode); + $location->range->start->character++; + $locations[] = $location; + } else if (($descendantNode instanceof Node\Parameter) + && $context->includeDeclaration && $descendantNode->getName() === $node->getName() ) { + $location = LocationFactory::fromToken($descendantNode, $descendantNode->variableName); + $location->range->start->character++; + $locations[] = $location; } } } else { @@ -227,6 +236,10 @@ public function references( return []; } } + $nameParts = preg_split('/[\>\\\:]/', $fqn); + $name = end($nameParts); + $nameParts = explode('(', $name); + $name = $nameParts[0]; $refDocumentPromises = []; foreach ($this->index->getReferenceUris($fqn) as $uri) { $refDocumentPromises[] = $this->documentLoader->getOrLoad($uri); @@ -236,15 +249,75 @@ public function references( $refs = $document->getReferenceNodesByFqn($fqn); if ($refs !== null) { foreach ($refs as $ref) { - $locations[] = LocationFactory::fromNode($ref); + if ($ref instanceof Node\Expression\MemberAccessExpression) { + $locations[] = LocationFactory::fromToken($ref, $ref -> memberName); + } else { + $locations[] = LocationFactory::fromNode($ref); + } } } + + } + if ($context->includeDeclaration) { + $definitionObjects = yield $this->definitionObject($textDocument, $position); + $definitionLocations = $definitionObjects->name; + if (gettype($definitionLocations) == "string") { + throw new \Exception($definitionLocations); + } + if (!is_array($definitionLocations)) { + $definitionLocations = array($definitionLocations); + } + $locations = array_merge($locations, $definitionLocations); } } return $locations; }); } + /** + * The rename request is sent from the client to the server to perform a workspace-wide rename of a symbol. + * + * @param TextDocumentIdentifier $textDocument The document to rename in. + * @param Position $position The position at which this request was sent. + * @param string $newName The new name of the symbol. + * @return Promise + */ + public function rename( + TextDocumentIdentifier $textDocument, + Position $position, + string $newName + ) : Promise { + return coroutine(function () use ($textDocument, $position, $newName) { + $document = yield $this->documentLoader->getOrLoad($textDocument->uri); + $node = $document->getNodeAtPosition($position); + $locations = yield $this->references(new ReferenceContext(true), $textDocument, $position); + $edits = [$textDocument->uri => [] ]; + foreach ($locations as $location) { + $textEdit = new TextEdit($location->range, $newName); + if (!isset($edits[$location->uri])) { + $edits[$location->uri] = []; + } + $edits[$location->uri][] = $textEdit; + } + + foreach ($edits as $uri => $textEdits) { + $document = yield $this->documentLoader->getOrLoad($uri); + $newtext = $document->getContent(); + foreach ($textEdits as $textEdit) { + $startOffset = $textEdit->range->start->toOffset($document->getContent()); + $endOffset = $textEdit->range->end->toOffset($document->getContent()); + $length = $endOffset - $startOffset; + $newtext = substr_replace($newtext, $textEdit->newText, $startOffset, $length); + } + $document->updateContent($newtext); + $this->client->textDocument->publishDiagnostics($uri, $document->getDiagnostics()); + + } + return new WorkspaceEdit($edits); + }); + } + + /** * The signature help request is sent from the client to the server to request signature information at a given * cursor position. @@ -271,6 +344,21 @@ public function signatureHelp(TextDocumentIdentifier $textDocument, Position $po * @return Promise */ public function definition(TextDocumentIdentifier $textDocument, Position $position): Promise + { + return coroutine(function () use ($textDocument, $position) { + $def = yield $this->definitionObject($textDocument, $position); + if ( + $def === null + || $def->symbolInformation === null + || Uri\parse($def->symbolInformation->location->uri)['scheme'] === 'phpstubs' + ) { + return []; + } + return $def->symbolInformation->location; + }); + } + + private function definitionObject(TextDocumentIdentifier $textDocument, Position $position) { return coroutine(function () use ($textDocument, $position) { $document = yield $this->documentLoader->getOrLoad($textDocument->uri); @@ -293,14 +381,7 @@ public function definition(TextDocumentIdentifier $textDocument, Position $posit } yield waitForEvent($this->index, 'definition-added'); } - if ( - $def === null - || $def->symbolInformation === null - || Uri\parse($def->symbolInformation->location->uri)['scheme'] === 'phpstubs' - ) { - return []; - } - return $def->symbolInformation->location; + return $def; }); }