1
0
Fork 0
observables
Felix Becker 2017-01-28 01:07:06 +01:00
parent 9c590e38da
commit 14ff2c46b0
2 changed files with 111 additions and 103 deletions

View File

@ -171,11 +171,11 @@ class TextDocument
* *
* @param TextDocumentIdentifier $textDocument The document to format * @param TextDocumentIdentifier $textDocument The document to format
* @param FormattingOptions $options The format options * @param FormattingOptions $options The format options
* @return Promise <TextEdit[]> * @return Observable <TextEdit[]>
*/ */
public function formatting(TextDocumentIdentifier $textDocument, FormattingOptions $options) public function formatting(TextDocumentIdentifier $textDocument, FormattingOptions $options)
{ {
return $this->documentLoader->getOrLoad($textDocument->uri)->then(function (PhpDocument $document) { $this->documentLoader->getOrLoad($textDocument->uri)->map(function (PhpDocument $document) {
return $document->getFormattedText(); return $document->getFormattedText();
}); });
} }
@ -185,73 +185,66 @@ class TextDocument
* denoted by the given text document position. * denoted by the given text document position.
* *
* @param ReferenceContext $context * @param ReferenceContext $context
* @return Promise <Location[]> * @return Observable Emits JSON Patch operations that eventually result in Location[]
*/ */
public function references( public function references(
ReferenceContext $context, ReferenceContext $context,
TextDocumentIdentifier $textDocument, TextDocumentIdentifier $textDocument,
Position $position Position $position
): Promise { ): Observable {
return coroutine(function () use ($textDocument, $position) { return $this->documentLoader->getOrLoad($textDocument->uri)
$document = yield $this->documentLoader->getOrLoad($textDocument->uri); ->flatMap(function (PhpDocument $document) {
$node = $document->getNodeAtPosition($position); $node = $document->getNodeAtPosition($position);
if ($node === null) { if ($node === null) {
return []; return Observable::empty();
}
$locations = [];
// Variables always stay in the boundary of the file and need to be searched inside their function scope
// by traversing the AST
if (
$node instanceof Node\Expr\Variable
|| $node instanceof Node\Param
|| $node instanceof Node\Expr\ClosureUse
) {
if ($node->name instanceof Node\Expr) {
return null;
} }
// Find function/method/closure scope // Variables always stay in the boundary of the file and need to be searched inside their function scope
$n = $node; // by traversing the AST
while (isset($n) && !($n instanceof Node\FunctionLike)) { if (
$n = $n->getAttribute('parentNode'); $node instanceof Node\Expr\Variable
} || $node instanceof Node\Param
if (!isset($n)) { || $node instanceof Node\Expr\ClosureUse
$n = $node->getAttribute('ownerDocument'); ) {
} if ($node->name instanceof Node\Expr) {
$traverser = new NodeTraverser; return Observable::empty();
$refCollector = new VariableReferencesCollector($node->name);
$traverser->addVisitor($refCollector);
$traverser->traverse($n->getStmts());
foreach ($refCollector->nodes as $ref) {
$locations[] = Location::fromNode($ref);
}
} else {
// Definition with a global FQN
$fqn = DefinitionResolver::getDefinedFqn($node);
// Wait until indexing finished
if (!$this->index->isComplete()) {
yield waitForEvent($this->index, 'complete');
}
if ($fqn === null) {
$fqn = $this->definitionResolver->resolveReferenceNodeToFqn($node);
if ($fqn === null) {
return [];
} }
} // Find function/method/closure scope
$refDocuments = yield Promise\all(array_map( $n = $node;
[$this->documentLoader, 'getOrLoad'], while (isset($n) && !($n instanceof Node\FunctionLike)) {
$this->index->getReferenceUris($fqn) $n = $n->getAttribute('parentNode');
)); }
foreach ($refDocuments as $document) { if (!isset($n)) {
$refs = $document->getReferenceNodesByFqn($fqn); $n = $node->getAttribute('ownerDocument');
if ($refs !== null) { }
foreach ($refs as $ref) { $traverser = new NodeTraverser;
$locations[] = Location::fromNode($ref); $refCollector = new VariableReferencesCollector($node->name);
$traverser->addVisitor($refCollector);
$traverser->traverse($n->getStmts());
return Observable::fromArray($refCollector->nodes);
} else {
// Definition with a global FQN
$fqn = DefinitionResolver::getDefinedFqn($node);
// Wait until indexing finished
if (!$this->index->isComplete()) {
yield waitForEvent($this->index, 'complete');
}
if ($fqn === null) {
$fqn = $this->definitionResolver->resolveReferenceNodeToFqn($node);
if ($fqn === null) {
return Observable::empty();
} }
} }
return Observable::fromArray($this->index->getReferenceUris($fqn))
->flatMap([$this->documentLoader, 'getOrLoad'])
->flatMap(function (PhpDocument $document) {
return Observable::from($document->getReferenceNodesByFqn($fqn));
});
} }
} })
return $locations; ->map(function (Node $node) {
}); return new Operation\Add('/-', Location::fromNode($node));
})
->startWith(new Operation\Replace('/', []));
} }
/** /**

View File

@ -95,7 +95,7 @@ class Workspace
* *
* @param SymbolDescriptor $query Partial metadata about the symbol that is being searched for. * @param SymbolDescriptor $query Partial metadata about the symbol that is being searched for.
* @param string[] $files An optional list of files to restrict the search to. * @param string[] $files An optional list of files to restrict the search to.
* @return Observable ReferenceInformation[] * @return Observable That emits JSON Patch operations that eventually result in ReferenceInformation[]
*/ */
public function xreferences($query, array $files = null): Observable public function xreferences($query, array $files = null): Observable
{ {
@ -109,25 +109,20 @@ class Workspace
return observableFromEvent($this->index, 'complete')->take(1); return observableFromEvent($this->index, 'complete')->take(1);
} }
}) })
// Get all definition FQNs in dependencies // Get all definitions in dependencies
->flatMap(function () { ->flatMap(function () use ($query) {
if (isset($query->fqsen)) { if (isset($query->fqsen)) {
$fqns = [$this->dependenciesIndex->getDefinition($query->fqsen)]; $defs = [$this->dependenciesIndex->getDefinition($query->fqsen)];
} else { } else {
$fqns = $this->dependenciesIndex->getDefinitions(); $defs = $this->dependenciesIndex->getDefinitions();
} }
return Observable::fromArray($fqns); return Observable::fromArray($defs);
}) })
// Get all URIs in the project source that reference those definitions ->map(function (Definition $def) {
->flatMap(function (Definition $def) { // Create SymbolDescriptor for Definition
return Observable::fromArray($this->sourceIndex->getReferenceUris($fqn));
})
->distinct()
->flatMap(function (string $uri) {
return $this->documentLoader->getOrLoad($uri);
$symbol = new SymbolDescriptor; $symbol = new SymbolDescriptor;
$symbol->fqsen = $fqn; $symbol->fqsen = $def->fqn;
foreach (get_object_vars($def->symbolInformation) as $prop => $val) { foreach (get_object_vars($def->symbolInformation) as $prop => &$val) {
$symbol->$prop = $val; $symbol->$prop = $val;
} }
// Find out package name // Find out package name
@ -139,52 +134,72 @@ class Workspace
break; break;
} }
} }
return $symbol;
}) })
->filter(function (SymbolDescriptor $symbol) {
// If there was no FQSEN provided, check if query attributes match // If there was no FQSEN provided, check if query attributes match
$matches = true;
if (!isset($query->fqsen)) { if (!isset($query->fqsen)) {
$matches = true;
foreach (get_object_vars($query) as $prop => $val) { foreach (get_object_vars($query) as $prop => $val) {
if ($query->$prop != $symbol->$prop) { if ($query->$prop != $symbol->$prop) {
$matches = false; $matches = false;
break; break;
} }
} }
if (!$matches) {
continue;
}
}
foreach ($doc->getReferenceNodesByFqn($fqn) as $node) {
$refInfo = new ReferenceInformation;
$refInfo->reference = Location::fromNode($node);
$refInfo->symbol = $symbol;
$refInfos[] = $refInfo;
} }
return $matches;
}) })
->flatMap(function (PhpDocument $doc) use ($fqn) { // Get all URIs in the project source that reference those definitions
->flatMap(function (SymbolDescriptor $symbol) {
return Observable::fromArray($this->sourceIndex->getReferenceUris($symbol->fqsen))->map(function ($uri) use ($symbol) {
return ['uri' => $uri, 'symbol' => $symbol];
});
})
// ['uri' => string, 'symbol' => SymbolDescriptor]
->groupBy(function (array $ref) {
return $ref['uri'];
})
// Observable<['uri' => string, 'symbol' => SymbolDescriptor]>
->map(function (Observable $refs) {
// Get document by URI
$uri = $refs->getKey();
return $this->documentLoader->getOrLoad($uri)
->flatMap(function (PhpDocument $doc) use ($refs) {
return $refs
->pluck('symbol')
->flatMap(function (SymbolDescriptor $symbol) use ($doc) {
return Observable::fromArray($doc->getReferenceNodesByFqn($symbol->fqsen))
->map(function (Node $node) use ($symbol) {
return new ReferenceInformation(Location::fromNode($node), $symbol);
});
});
});
})
->map(function (ReferenceInformation $refInfo) {
return new Operation\Add('/-', $refInfo);
}) })
$refInfos = [];
foreach ($refs as $uri => $fqns) {
foreach ($fqns as $fqn) {
}
}
return $refInfos;
})
->startWith(new Operation\Replace('/', [])); ->startWith(new Operation\Replace('/', []));
} }
/** /**
* @return DependencyReference[] * @return Observable for JSON Patch operations of DependencyReference[]
*/ */
public function xdependencies(): array public function xdependencies(): Observable
{ {
if ($this->composerLock === null) { return Observable::just(null)
return []; ->flatMap(function () {
} if ($this->composerLock === null) {
$dependencyReferences = []; return Observable::empty();
foreach ($this->composerLock->packages as $package) { }
$dependencyReferences[] = new DependencyReference($package); $dependencyReferences = [];
} return Observable::fromArray(array_merge(
return $dependencyReferences; $this->composerLock->packages,
$this->composerLock->{'packages-dev'}
));
})
->map(function (\stdClass $package) {
return new Operation\Add('/-', new DependencyReference($package));
})
->startWith(new Operation\Replace('/', []));
} }
} }