Skip to content
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

Rename support #768

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/Definition.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
8 changes: 8 additions & 0 deletions src/DefinitionResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
21 changes: 21 additions & 0 deletions src/Factory/LocationFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use LanguageServerProtocol\Position;
use LanguageServerProtocol\Range;
use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Token;
use Microsoft\PhpParser\PositionUtilities;

class LocationFactory
Expand All @@ -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)
));
}
}
3 changes: 3 additions & 0 deletions src/Index/Index.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Copy link
Author

Choose a reason for hiding this comment

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

I don't really understand this function well enough to be confident about this code, but it fixes an exception that I got before and it seems intuitively right to have a base case for the recursion.

return;
}
$part = $parts[$level];

if ($level + 1 === count($parts)) {
Expand Down
107 changes: 94 additions & 13 deletions src/Server/TextDocument.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -23,6 +23,8 @@
TextDocumentIdentifier,
TextDocumentItem,
VersionedTextDocumentIdentifier,
WorkspaceEdit,
TextEdit,
CompletionContext
};
use Microsoft\PhpParser\Node;
Expand Down Expand Up @@ -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) {
Expand All @@ -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
) {
Expand All @@ -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 {
Expand All @@ -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);
Expand All @@ -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 <WorkspaceEdit|null>
*/
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.
Expand All @@ -271,6 +344,21 @@ public function signatureHelp(TextDocumentIdentifier $textDocument, Position $po
* @return Promise <Location|Location[]>
*/
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);
Expand All @@ -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;
});
}

Expand Down