From 207c6a2a682bf87ce8af971233f2574a4dd7a900 Mon Sep 17 00:00:00 2001 From: Florian Schunk Date: Fri, 11 Oct 2019 21:30:14 +0200 Subject: [PATCH 1/8] add rename functionality --- src/Factory/LocationFactory.php | 21 ++++++++++++++++ src/Server/TextDocument.php | 43 ++++++++++++++++++++++++++++++++- 2 files changed, 63 insertions(+), 1 deletion(-) 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/Server/TextDocument.php b/src/Server/TextDocument.php index 039ff578..2273c29d 100644 --- a/src/Server/TextDocument.php +++ b/src/Server/TextDocument.php @@ -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) { @@ -211,6 +213,9 @@ public function references( $descendantNode->getName() === $node->getName() ) { $locations[] = LocationFactory::fromNode($descendantNode); + } else if (($descendantNode instanceof Node\Parameter) + && $context->includeDeclaration && $descendantNode->getName() === $node->getName() ) { + $locations[] = LocationFactory::fromToken($descendantNode, $descendantNode->variableName); } } } else { @@ -245,6 +250,42 @@ public function references( }); } + /** + * 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); + if ( + $node instanceof Node\Expression\Variable || + $node instanceof Node\Parameter + ) { + $newName = '$' . $newName; + } + $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; + } + 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. From 1f157d74244494bafe1656d1a1a3043327cb1276 Mon Sep 17 00:00:00 2001 From: Florian Schunk Date: Fri, 1 Nov 2019 20:28:13 +0100 Subject: [PATCH 2/8] implement rename of class member fields --- src/Server/TextDocument.php | 41 ++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/src/Server/TextDocument.php b/src/Server/TextDocument.php index 2273c29d..e5ddf9a8 100644 --- a/src/Server/TextDocument.php +++ b/src/Server/TextDocument.php @@ -192,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 ) { @@ -212,10 +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() ) { - $locations[] = LocationFactory::fromToken($descendantNode, $descendantNode->variableName); + $location = LocationFactory::fromToken($descendantNode, $descendantNode->variableName); + $location->range->start->character++; + $locations[] = $location; } } } else { @@ -241,9 +245,32 @@ 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) { + $refs = $document->getDefinitionNodeByFqn($fqn); + if ($refs !== null){ + foreach ($refs as $ref) { + if ($ref !== null){ + if ($ref instanceof Node\Expression\AssignmentExpression) { + $location = LocationFactory::fromNode($ref->leftOperand); + $location->range->start->character++; + $locations[] = $location; + }elseif ($ref instanceof Node\DelimitedList\ExpressionList) { + $location = LocationFactory::fromNode($ref); + $location->range->start->character++; + $locations[] = $location; + } + } + } + } + } + } } return $locations; @@ -266,12 +293,6 @@ public function rename( return coroutine( function () use ($textDocument, $position, $newName) { $document = yield $this->documentLoader->getOrLoad($textDocument->uri); $node = $document->getNodeAtPosition($position); - if ( - $node instanceof Node\Expression\Variable || - $node instanceof Node\Parameter - ) { - $newName = '$' . $newName; - } $locations = yield $this->references(new ReferenceContext(true), $textDocument, $position); $edits = [$textDocument->uri => [] ]; foreach ($locations as $location) { From c54b817ba6f253f689257588aae2e918886abbfe Mon Sep 17 00:00:00 2001 From: Florian Schunk Date: Tue, 5 Nov 2019 20:36:10 +0100 Subject: [PATCH 3/8] allow rename of methods --- src/Server/TextDocument.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Server/TextDocument.php b/src/Server/TextDocument.php index e5ddf9a8..988c3d24 100644 --- a/src/Server/TextDocument.php +++ b/src/Server/TextDocument.php @@ -265,6 +265,15 @@ public function references( $location = LocationFactory::fromNode($ref); $location->range->start->character++; $locations[] = $location; + }elseif ($ref instanceof Node\ClassMembersNode) { + foreach ($ref->classMemberDeclarations as $declaration) { + if ($declaration instanceof Node\MethodDeclaration) { + if ($declaration->getName() === $node->getName()) { + $location = LocationFactory::fromToken($declaration, $declaration->name); + $locations[] = $location; + } + } + } } } } From 879c868af151aae611b0b7d8393b4b6ff0c9b817 Mon Sep 17 00:00:00 2001 From: Florian Schunk Date: Tue, 5 Nov 2019 21:05:59 +0100 Subject: [PATCH 4/8] allow rename classes --- src/Server/TextDocument.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Server/TextDocument.php b/src/Server/TextDocument.php index 988c3d24..a18d4a36 100644 --- a/src/Server/TextDocument.php +++ b/src/Server/TextDocument.php @@ -255,6 +255,13 @@ public function references( if ($context->includeDeclaration) { $refs = $document->getDefinitionNodeByFqn($fqn); if ($refs !== null){ + if ($refs instanceof Node\Statement\ClassDeclaration) { + if ($refs->name->getText($refs->getFileContents()) === $node->name->getText($node->getFileContents())) { + $location = LocationFactory::fromToken($refs, $refs->name); + $locations[] = $location; + } + } + foreach ($refs as $ref) { if ($ref !== null){ if ($ref instanceof Node\Expression\AssignmentExpression) { @@ -268,7 +275,7 @@ public function references( }elseif ($ref instanceof Node\ClassMembersNode) { foreach ($ref->classMemberDeclarations as $declaration) { if ($declaration instanceof Node\MethodDeclaration) { - if ($declaration->getName() === $node->getName()) { + if ($declaration->getName() === $node->name->getText($node->getFileContents())) { $location = LocationFactory::fromToken($declaration, $declaration->name); $locations[] = $location; } From 7c4376801875659f72412907b5141b8f2a20ab55 Mon Sep 17 00:00:00 2001 From: Florian Schunk Date: Tue, 12 Nov 2019 20:42:58 +0100 Subject: [PATCH 5/8] Bugfix --- src/Server/TextDocument.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Server/TextDocument.php b/src/Server/TextDocument.php index a18d4a36..dfc09db0 100644 --- a/src/Server/TextDocument.php +++ b/src/Server/TextDocument.php @@ -236,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); @@ -275,7 +279,7 @@ public function references( }elseif ($ref instanceof Node\ClassMembersNode) { foreach ($ref->classMemberDeclarations as $declaration) { if ($declaration instanceof Node\MethodDeclaration) { - if ($declaration->getName() === $node->name->getText($node->getFileContents())) { + if ($declaration->getName() === $name ) { $location = LocationFactory::fromToken($declaration, $declaration->name); $locations[] = $location; } From db926f015702b5f1daba3095469a9957bed5c50a Mon Sep 17 00:00:00 2001 From: Florian Schunk Date: Fri, 15 Nov 2019 17:55:31 +0100 Subject: [PATCH 6/8] follow coding style --- src/Server/TextDocument.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Server/TextDocument.php b/src/Server/TextDocument.php index dfc09db0..a2a1935c 100644 --- a/src/Server/TextDocument.php +++ b/src/Server/TextDocument.php @@ -215,7 +215,7 @@ public function references( $location = LocationFactory::fromNode($descendantNode); $location->range->start->character++; $locations[] = $location; - } else if (($descendantNode instanceof Node\Parameter) + } else if (($descendantNode instanceof Node\Parameter) && $context->includeDeclaration && $descendantNode->getName() === $node->getName() ) { $location = LocationFactory::fromToken($descendantNode, $descendantNode->variableName); $location->range->start->character++; @@ -258,7 +258,7 @@ public function references( } if ($context->includeDeclaration) { $refs = $document->getDefinitionNodeByFqn($fqn); - if ($refs !== null){ + if ($refs !== null) { if ($refs instanceof Node\Statement\ClassDeclaration) { if ($refs->name->getText($refs->getFileContents()) === $node->name->getText($node->getFileContents())) { $location = LocationFactory::fromToken($refs, $refs->name); @@ -267,19 +267,19 @@ public function references( } foreach ($refs as $ref) { - if ($ref !== null){ + if ($ref !== null) { if ($ref instanceof Node\Expression\AssignmentExpression) { $location = LocationFactory::fromNode($ref->leftOperand); $location->range->start->character++; $locations[] = $location; - }elseif ($ref instanceof Node\DelimitedList\ExpressionList) { + } elseif ($ref instanceof Node\DelimitedList\ExpressionList) { $location = LocationFactory::fromNode($ref); $location->range->start->character++; $locations[] = $location; - }elseif ($ref instanceof Node\ClassMembersNode) { + } elseif ($ref instanceof Node\ClassMembersNode) { foreach ($ref->classMemberDeclarations as $declaration) { if ($declaration instanceof Node\MethodDeclaration) { - if ($declaration->getName() === $name ) { + if ($declaration->getName() === $name) { $location = LocationFactory::fromToken($declaration, $declaration->name); $locations[] = $location; } @@ -310,7 +310,7 @@ public function rename( Position $position, string $newName ) : Promise { - return coroutine( function () use ($textDocument, $position, $newName) { + 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); From 80e6f995ae81fb1eb13e075d2056e8eca6a5f9a3 Mon Sep 17 00:00:00 2001 From: Florian Schunk Date: Thu, 28 Nov 2019 01:22:41 +0100 Subject: [PATCH 7/8] fix bugs for rename --- src/Definition.php | 5 +++ src/DefinitionResolver.php | 8 ++++ src/Index/Index.php | 6 +++ src/Server/TextDocument.php | 83 ++++++++++++++++++------------------- 4 files changed, 60 insertions(+), 42 deletions(-) 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/Index/Index.php b/src/Index/Index.php index 0d61f9ca..c72e3e30 100644 --- a/src/Index/Index.php +++ b/src/Index/Index.php @@ -187,6 +187,9 @@ public function setDefinition(string $fqn, Definition $definition) public function removeDefinition(string $fqn) { $parts = $this->splitFqn($fqn); + if (empty($parts)) { + throw new \Exception($fqn); + } $this->removeIndexedDefinition(0, $parts, $this->definitions, $this->definitions); unset($this->references[$fqn]); @@ -423,6 +426,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 a2a1935c..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; @@ -256,41 +256,18 @@ public function references( } } } - if ($context->includeDeclaration) { - $refs = $document->getDefinitionNodeByFqn($fqn); - if ($refs !== null) { - if ($refs instanceof Node\Statement\ClassDeclaration) { - if ($refs->name->getText($refs->getFileContents()) === $node->name->getText($node->getFileContents())) { - $location = LocationFactory::fromToken($refs, $refs->name); - $locations[] = $location; - } - } - foreach ($refs as $ref) { - if ($ref !== null) { - if ($ref instanceof Node\Expression\AssignmentExpression) { - $location = LocationFactory::fromNode($ref->leftOperand); - $location->range->start->character++; - $locations[] = $location; - } elseif ($ref instanceof Node\DelimitedList\ExpressionList) { - $location = LocationFactory::fromNode($ref); - $location->range->start->character++; - $locations[] = $location; - } elseif ($ref instanceof Node\ClassMembersNode) { - foreach ($ref->classMemberDeclarations as $declaration) { - if ($declaration instanceof Node\MethodDeclaration) { - if ($declaration->getName() === $name) { - $location = LocationFactory::fromToken($declaration, $declaration->name); - $locations[] = $location; - } - } - } - } - } - } - } + } + 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; @@ -322,6 +299,20 @@ public function rename( } $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); }); } @@ -353,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); @@ -375,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; }); } From 02057a4baa0d330ee018db8ac41f98de5a3c9d61 Mon Sep 17 00:00:00 2001 From: Florian Schunk Date: Thu, 28 Nov 2019 01:40:00 +0100 Subject: [PATCH 8/8] remove debugging output --- src/Index/Index.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Index/Index.php b/src/Index/Index.php index c72e3e30..39e8d70d 100644 --- a/src/Index/Index.php +++ b/src/Index/Index.php @@ -187,9 +187,6 @@ public function setDefinition(string $fqn, Definition $definition) public function removeDefinition(string $fqn) { $parts = $this->splitFqn($fqn); - if (empty($parts)) { - throw new \Exception($fqn); - } $this->removeIndexedDefinition(0, $parts, $this->definitions, $this->definitions); unset($this->references[$fqn]);