mirror of
https://github.com/danog/psalm.git
synced 2025-01-21 21:31:13 +01:00
Update Amp usage
Fixed a few errors and used byte-stream for reading and writing.
This commit is contained in:
parent
263a4c8cf1
commit
b0d97843ce
@ -22,7 +22,8 @@
|
||||
"webmozart/glob": "^4.1",
|
||||
"webmozart/path-util": "^2.3",
|
||||
"symfony/console": "^3.0||^4.0",
|
||||
"amphp/amp": "^2.1"
|
||||
"amphp/amp": "^2.1",
|
||||
"amphp/byte-stream": "^1.5"
|
||||
},
|
||||
"bin": ["psalm", "psalter", "psalm-language-server", "psalm-plugin"],
|
||||
"autoload": {
|
||||
|
@ -36,7 +36,7 @@
|
||||
</ignoreExceptions>
|
||||
|
||||
<stubs>
|
||||
<file name="src/Psalm/Internal/Stubs/SabreEvent.php"/>
|
||||
<file name="src/Psalm/Internal/Stubs/Amp.php"/>
|
||||
</stubs>
|
||||
|
||||
<plugins>
|
||||
|
@ -7,6 +7,7 @@ use Psalm\Internal\LanguageServer\ClientHandler;
|
||||
use LanguageServerProtocol\{Diagnostic, TextDocumentItem, TextDocumentIdentifier};
|
||||
use Amp\Promise;
|
||||
use JsonMapper;
|
||||
use function Amp\call;
|
||||
|
||||
/**
|
||||
* Provides method handlers for all textDocument/* methods
|
||||
@ -53,21 +54,15 @@ class TextDocument
|
||||
*/
|
||||
public function xcontent(TextDocumentIdentifier $textDocument): Promise
|
||||
{
|
||||
$promise = $this->handler->request(
|
||||
'textDocument/xcontent',
|
||||
['textDocument' => $textDocument]
|
||||
);
|
||||
return call(
|
||||
function () use ($textDocument) {
|
||||
$result = yield $this->handler->request(
|
||||
'textDocument/xcontent',
|
||||
['textDocument' => $textDocument]
|
||||
);
|
||||
|
||||
$promise->onResolve(
|
||||
/**
|
||||
* @param object $result
|
||||
* @return object
|
||||
*/
|
||||
function ($result) {
|
||||
return $this->mapper->map($result, new TextDocumentItem);
|
||||
}
|
||||
);
|
||||
|
||||
return $promise;
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,9 @@ declare(strict_types = 1);
|
||||
namespace Psalm\Internal\LanguageServer;
|
||||
|
||||
use AdvancedJsonRpc;
|
||||
use Amp\Deferred;
|
||||
use Amp\Promise;
|
||||
use function Amp\call;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -38,54 +40,48 @@ class ClientHandler
|
||||
*
|
||||
* @param string $method The method to call
|
||||
* @param array|object $params The method parameters
|
||||
* @return Promise Resolved with the result of the request or rejected with an error
|
||||
* @return Promise <mixed> Resolved with the result of the request or rejected with an error
|
||||
*/
|
||||
public function request(string $method, $params): Promise
|
||||
{
|
||||
$id = $this->idGenerator->generate();
|
||||
|
||||
$promise = $this->protocolWriter->write(
|
||||
new Message(
|
||||
new AdvancedJsonRpc\Request($id, $method, (object)$params)
|
||||
)
|
||||
);
|
||||
return call(function () use ($id, $method, $params) {
|
||||
yield $this->protocolWriter->write(
|
||||
new Message(
|
||||
new AdvancedJsonRpc\Request($id, $method, (object) $params)
|
||||
)
|
||||
);
|
||||
|
||||
$promise->onResolve(
|
||||
/**
|
||||
* @return Promise
|
||||
*/
|
||||
function () use ($id) {
|
||||
$deferred = new \Amp\Deferred();
|
||||
$deferred = new Deferred();
|
||||
|
||||
$listener =
|
||||
$listener =
|
||||
/**
|
||||
* @param callable $listener
|
||||
* @return void
|
||||
*/
|
||||
function (Message $msg) use ($id, $deferred, &$listener) {
|
||||
error_log('request handler');
|
||||
/**
|
||||
* @param callable $listener
|
||||
* @return void
|
||||
* @psalm-suppress UndefinedPropertyFetch
|
||||
* @psalm-suppress MixedArgument
|
||||
*/
|
||||
function (Message $msg) use ($id, $deferred, &$listener) {
|
||||
/**
|
||||
* @psalm-suppress UndefinedPropertyFetch
|
||||
* @psalm-suppress MixedArgument
|
||||
*/
|
||||
if ($msg->body
|
||||
&& AdvancedJsonRpc\Response::isResponse($msg->body)
|
||||
&& $msg->body->id === $id
|
||||
) {
|
||||
// Received a response
|
||||
$this->protocolReader->removeListener('message', $listener);
|
||||
if (AdvancedJsonRpc\SuccessResponse::isSuccessResponse($msg->body)) {
|
||||
$deferred->resolve($msg->body->result);
|
||||
} else {
|
||||
$deferred->fail($msg->body->error);
|
||||
}
|
||||
if ($msg->body
|
||||
&& AdvancedJsonRpc\Response::isResponse($msg->body)
|
||||
&& $msg->body->id === $id
|
||||
) {
|
||||
// Received a response
|
||||
$this->protocolReader->removeListener('message', $listener);
|
||||
if (AdvancedJsonRpc\SuccessResponse::isSuccessResponse($msg->body)) {
|
||||
$deferred->resolve($msg->body->result);
|
||||
} else {
|
||||
$deferred->fail($msg->body->error);
|
||||
}
|
||||
};
|
||||
$this->protocolReader->on('message', $listener);
|
||||
return $deferred->promise();
|
||||
}
|
||||
);
|
||||
|
||||
return $promise;
|
||||
}
|
||||
};
|
||||
$this->protocolReader->on('message', $listener);
|
||||
return $deferred->promise();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -26,9 +26,10 @@ use Psalm\Internal\LanguageServer\Server\TextDocument;
|
||||
use LanguageServerProtocol\{Range, Position, Diagnostic, DiagnosticSeverity};
|
||||
use AdvancedJsonRpc;
|
||||
use Amp\Promise;
|
||||
use function Amp\coroutine;
|
||||
use Throwable;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use function Amp\call;
|
||||
use function Amp\asyncCoroutine;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -100,70 +101,60 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
||||
$this->protocolReader->on(
|
||||
'message',
|
||||
/** @return void */
|
||||
function (Message $msg) {
|
||||
\Amp\call(
|
||||
/** @return \Generator<int, Promise, mixed, void> */
|
||||
function () use ($msg) {
|
||||
if (!$msg->body) {
|
||||
return;
|
||||
}
|
||||
asyncCoroutine(function (Message $msg) {
|
||||
if (!$msg->body) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore responses, this is the handler for requests and notifications
|
||||
if (AdvancedJsonRpc\Response::isResponse($msg->body)) {
|
||||
return;
|
||||
}
|
||||
// Ignore responses, this is the handler for requests and notifications
|
||||
if (AdvancedJsonRpc\Response::isResponse($msg->body)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$result = null;
|
||||
$error = null;
|
||||
try {
|
||||
// Invoke the method handler to get a result
|
||||
/**
|
||||
* @var Promise
|
||||
* @psalm-suppress UndefinedClass
|
||||
*/
|
||||
$dispatched = $this->dispatch($msg->body);
|
||||
$result = yield $dispatched;
|
||||
} catch (AdvancedJsonRpc\Error $e) {
|
||||
// If a ResponseError is thrown, send it back in the Response
|
||||
$error = $e;
|
||||
} catch (Throwable $e) {
|
||||
// If an unexpected error occurred, send back an INTERNAL_ERROR error response
|
||||
$error = new AdvancedJsonRpc\Error(
|
||||
(string)$e,
|
||||
AdvancedJsonRpc\ErrorCode::INTERNAL_ERROR,
|
||||
null,
|
||||
$e
|
||||
);
|
||||
}
|
||||
// Only send a Response for a Request
|
||||
// Notifications do not send Responses
|
||||
/**
|
||||
* @psalm-suppress UndefinedPropertyFetch
|
||||
* @psalm-suppress MixedArgument
|
||||
*/
|
||||
if (AdvancedJsonRpc\Request::isRequest($msg->body)) {
|
||||
if ($error !== null) {
|
||||
$responseBody = new AdvancedJsonRpc\ErrorResponse($msg->body->id, $error);
|
||||
} else {
|
||||
$responseBody = new AdvancedJsonRpc\SuccessResponse($msg->body->id, $result);
|
||||
}
|
||||
$this->protocolWriter->write(new Message($responseBody));
|
||||
}
|
||||
$result = null;
|
||||
$error = null;
|
||||
try {
|
||||
// Invoke the method handler to get a result
|
||||
/**
|
||||
* @var Promise
|
||||
* @psalm-suppress UndefinedClass
|
||||
*/
|
||||
$dispatched = $this->dispatch($msg->body);
|
||||
$result = yield $dispatched;
|
||||
} catch (AdvancedJsonRpc\Error $e) {
|
||||
// If a ResponseError is thrown, send it back in the Response
|
||||
$error = $e;
|
||||
} catch (Throwable $e) {
|
||||
// If an unexpected error occurred, send back an INTERNAL_ERROR error response
|
||||
$error = new AdvancedJsonRpc\Error(
|
||||
(string) $e,
|
||||
AdvancedJsonRpc\ErrorCode::INTERNAL_ERROR,
|
||||
null,
|
||||
$e
|
||||
);
|
||||
}
|
||||
// Only send a Response for a Request
|
||||
// Notifications do not send Responses
|
||||
/**
|
||||
* @psalm-suppress UndefinedPropertyFetch
|
||||
* @psalm-suppress MixedArgument
|
||||
*/
|
||||
if (AdvancedJsonRpc\Request::isRequest($msg->body)) {
|
||||
if ($error !== null) {
|
||||
$responseBody = new AdvancedJsonRpc\ErrorResponse($msg->body->id, $error);
|
||||
} else {
|
||||
$responseBody = new AdvancedJsonRpc\SuccessResponse($msg->body->id, $result);
|
||||
}
|
||||
);
|
||||
}
|
||||
yield $this->protocolWriter->write(new Message($responseBody));
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
$this->protocolReader->on(
|
||||
'readMessageGroup',
|
||||
/** @return void */
|
||||
function () {
|
||||
\Amp\call(
|
||||
/** @return null */
|
||||
function () {
|
||||
$this->doAnalysis();
|
||||
}
|
||||
);
|
||||
$this->doAnalysis();
|
||||
}
|
||||
);
|
||||
|
||||
@ -186,7 +177,7 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
||||
string $rootPath = null,
|
||||
int $processId = null
|
||||
): Promise {
|
||||
return \Amp\call(
|
||||
return call(
|
||||
/** @return \Generator<int, true, mixed, InitializeResult> */
|
||||
function () use ($capabilities, $rootPath, $processId) {
|
||||
// Eventually, this might block on something. Leave it as a generator.
|
||||
@ -442,22 +433,4 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
||||
}
|
||||
return $filepath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an exception on the next tick.
|
||||
* Useful for letting a promise crash the process on rejection.
|
||||
*
|
||||
* @param Throwable $err
|
||||
* @return void
|
||||
* @psalm-suppress PossiblyUnusedMethod
|
||||
*/
|
||||
public static function crash(Throwable $err)
|
||||
{
|
||||
\Amp\Loop::defer(
|
||||
/** @return void */
|
||||
function () use ($err) {
|
||||
throw $err;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,9 @@ declare(strict_types = 1);
|
||||
namespace Psalm\Internal\LanguageServer;
|
||||
|
||||
use AdvancedJsonRpc\Message as MessageBody;
|
||||
use Amp\ByteStream\ResourceInputStream;
|
||||
use Exception;
|
||||
use Psalm\Internal\LanguageServer\Message;
|
||||
use Amp\Loop;
|
||||
use function Amp\asyncCall;
|
||||
|
||||
/**
|
||||
* Source: https://github.com/felixfbecker/php-language-server/tree/master/src/ProtocolStreamReader.php
|
||||
@ -18,11 +18,6 @@ class ProtocolStreamReader implements ProtocolReader
|
||||
const PARSE_HEADERS = 1;
|
||||
const PARSE_BODY = 2;
|
||||
|
||||
/** @var resource */
|
||||
private $input;
|
||||
|
||||
/** @var string */
|
||||
private $read_watcher;
|
||||
/**
|
||||
* This is checked by ProtocolStreamReader so that it will stop reading from streams in the forked process.
|
||||
* There could be buffered bytes in stdin/over TCP, those would be processed by TCP if it were not for this check.
|
||||
@ -45,49 +40,36 @@ class ProtocolStreamReader implements ProtocolReader
|
||||
*/
|
||||
public function __construct($input)
|
||||
{
|
||||
$this->input = $input;
|
||||
|
||||
$read_watcher = Loop::onReadable(
|
||||
$this->input,
|
||||
/** @return void */
|
||||
function () {
|
||||
if (feof($this->input)) {
|
||||
// If stream_select reported a status change for this stream,
|
||||
// but the stream is EOF, it means it was closed.
|
||||
$this->emitClose();
|
||||
return;
|
||||
}
|
||||
if (!$this->is_accepting_new_requests) {
|
||||
// If we fork, don't read any bytes in the input buffer from the worker process.
|
||||
$this->emitClose();
|
||||
return;
|
||||
}
|
||||
$emitted_messages = $this->readMessages();
|
||||
if ($emitted_messages > 0) {
|
||||
$this->emit('readMessageGroup');
|
||||
}
|
||||
$input = new ResourceInputStream($input);
|
||||
asyncCall(function () use ($input): \Generator {
|
||||
while (($chunk = yield $input->read()) !== null) {
|
||||
/** @var string $chunk */
|
||||
$this->readMessages($chunk);
|
||||
}
|
||||
);
|
||||
|
||||
$this->read_watcher = $read_watcher;
|
||||
$this->emitClose();
|
||||
});
|
||||
|
||||
$this->on(
|
||||
'close',
|
||||
/** @return void */
|
||||
function () {
|
||||
Loop::cancel($this->read_watcher);
|
||||
static function () use ($input) {
|
||||
$input->close();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $buffer
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function readMessages() : int
|
||||
private function readMessages(string $buffer) : int
|
||||
{
|
||||
$emitted_messages = 0;
|
||||
while (($c = fgetc($this->input)) !== false && $c !== '') {
|
||||
$this->buffer .= $c;
|
||||
$i = 0;
|
||||
while (($buffer[$i] ?? '') !== '') {
|
||||
$this->buffer .= $buffer[$i++];
|
||||
switch ($this->parsing_mode) {
|
||||
case self::PARSE_HEADERS:
|
||||
if ($this->buffer === "\r\n") {
|
||||
|
@ -3,13 +3,8 @@ declare(strict_types = 1);
|
||||
|
||||
namespace Psalm\Internal\LanguageServer;
|
||||
|
||||
use Psalm\Internal\LanguageServer\Message;
|
||||
use Amp\{
|
||||
Deferred,
|
||||
Loop,
|
||||
Promise
|
||||
};
|
||||
use RuntimeException;
|
||||
use Amp\ByteStream\ResourceOutputStream;
|
||||
use Amp\Promise;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -17,26 +12,16 @@ use RuntimeException;
|
||||
class ProtocolStreamWriter implements ProtocolWriter
|
||||
{
|
||||
/**
|
||||
* @var resource $output
|
||||
* @var \Amp\ByteStream\ResourceOutputStream
|
||||
*/
|
||||
private $output;
|
||||
|
||||
/**
|
||||
* @var ?string
|
||||
*/
|
||||
private $output_watcher;
|
||||
|
||||
/**
|
||||
* @var array<int, array{message: string, deferred: Deferred}> $messages
|
||||
*/
|
||||
private $messages = [];
|
||||
|
||||
/**
|
||||
* @param resource $output
|
||||
*/
|
||||
public function __construct($output)
|
||||
{
|
||||
$this->output = $output;
|
||||
$this->output = new ResourceOutputStream($output);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -44,58 +29,6 @@ class ProtocolStreamWriter implements ProtocolWriter
|
||||
*/
|
||||
public function write(Message $msg): Promise
|
||||
{
|
||||
// if the message queue is currently empty, register a write handler.
|
||||
if (empty($this->messages)) {
|
||||
$this->output_watcher = Loop::onWritable(
|
||||
$this->output,
|
||||
/** @return void */
|
||||
function () {
|
||||
$this->flush();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
$deferred = new \Amp\Deferred();
|
||||
$this->messages[] = [
|
||||
'message' => (string)$msg,
|
||||
'deferred' => $deferred
|
||||
];
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes pending messages to the output stream.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function flush()
|
||||
{
|
||||
$keepWriting = true;
|
||||
while ($keepWriting) {
|
||||
$message = $this->messages[0]['message'];
|
||||
$deferred = $this->messages[0]['deferred'];
|
||||
|
||||
$bytesWritten = @fwrite($this->output, $message);
|
||||
|
||||
if ($bytesWritten > 0) {
|
||||
$message = substr($message, $bytesWritten);
|
||||
}
|
||||
|
||||
// Determine if this message was completely sent
|
||||
if (strlen($message) === 0) {
|
||||
array_shift($this->messages);
|
||||
|
||||
// This was the last message in the queue, remove the write handler.
|
||||
if (count($this->messages) === 0 && $this->output_watcher) {
|
||||
Loop::cancel($this->output_watcher);
|
||||
$keepWriting = false;
|
||||
}
|
||||
|
||||
$deferred->resolve();
|
||||
} else {
|
||||
$this->messages[0]['message'] = $message;
|
||||
$keepWriting = false;
|
||||
}
|
||||
}
|
||||
return $this->output->write((string)$msg);
|
||||
}
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ use Psalm\Internal\LanguageServer\Index\ReadableIndex;
|
||||
use Psalm\Internal\Analyzer\FileAnalyzer;
|
||||
use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
|
||||
use Amp\Promise;
|
||||
use function Amp\coroutine;
|
||||
use Amp\Success;
|
||||
use function Psalm\Internal\LanguageServer\{waitForEvent, isVendored};
|
||||
|
||||
/**
|
||||
@ -174,45 +174,36 @@ class TextDocument
|
||||
*/
|
||||
public function definition(TextDocumentIdentifier $textDocument, Position $position): Promise
|
||||
{
|
||||
return \Amp\call(
|
||||
/**
|
||||
* @return \Generator<int, true, mixed, Hover|Location>
|
||||
*/
|
||||
function () use ($textDocument, $position) {
|
||||
if (false) {
|
||||
yield true;
|
||||
}
|
||||
$file_path = LanguageServer::uriToPath($textDocument->uri);
|
||||
|
||||
$file_path = LanguageServer::uriToPath($textDocument->uri);
|
||||
try {
|
||||
$reference_location = $this->codebase->getReferenceAtPosition($file_path, $position);
|
||||
} catch (\Psalm\Exception\UnanalyzedFileException $e) {
|
||||
$this->codebase->file_provider->openFile($file_path);
|
||||
$this->server->queueFileAnalysis($file_path, $textDocument->uri);
|
||||
return new Success(new Hover([]));
|
||||
}
|
||||
|
||||
try {
|
||||
$reference_location = $this->codebase->getReferenceAtPosition($file_path, $position);
|
||||
} catch (\Psalm\Exception\UnanalyzedFileException $e) {
|
||||
$this->codebase->file_provider->openFile($file_path);
|
||||
$this->server->queueFileAnalysis($file_path, $textDocument->uri);
|
||||
return new Hover([]);
|
||||
}
|
||||
if ($reference_location === null) {
|
||||
return new Success(new Hover([]));
|
||||
}
|
||||
|
||||
if ($reference_location === null) {
|
||||
return new Hover([]);
|
||||
}
|
||||
list($reference) = $reference_location;
|
||||
|
||||
list($reference) = $reference_location;
|
||||
$code_location = $this->codebase->getSymbolLocation($file_path, $reference);
|
||||
|
||||
$code_location = $this->codebase->getSymbolLocation($file_path, $reference);
|
||||
if (!$code_location) {
|
||||
return new Success(new Hover([]));
|
||||
}
|
||||
|
||||
if (!$code_location) {
|
||||
return new Hover([]);
|
||||
}
|
||||
|
||||
return new Location(
|
||||
LanguageServer::pathToUri($code_location->file_path),
|
||||
new Range(
|
||||
new Position($code_location->getLineNumber() - 1, $code_location->getColumn() - 1),
|
||||
new Position($code_location->getEndLineNumber() - 1, $code_location->getEndColumn() - 1)
|
||||
)
|
||||
);
|
||||
}
|
||||
return new Success(
|
||||
new Location(
|
||||
LanguageServer::pathToUri($code_location->file_path),
|
||||
new Range(
|
||||
new Position($code_location->getLineNumber() - 1, $code_location->getColumn() - 1),
|
||||
new Position($code_location->getEndLineNumber() - 1, $code_location->getEndColumn() - 1)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -226,40 +217,29 @@ class TextDocument
|
||||
*/
|
||||
public function hover(TextDocumentIdentifier $textDocument, Position $position): Promise
|
||||
{
|
||||
return \Amp\call(
|
||||
/**
|
||||
* @return \Generator<int, true, mixed, Hover>
|
||||
*/
|
||||
function () use ($textDocument, $position) {
|
||||
if (false) {
|
||||
yield true;
|
||||
}
|
||||
$file_path = LanguageServer::uriToPath($textDocument->uri);
|
||||
|
||||
$file_path = LanguageServer::uriToPath($textDocument->uri);
|
||||
try {
|
||||
$reference_location = $this->codebase->getReferenceAtPosition($file_path, $position);
|
||||
} catch (\Psalm\Exception\UnanalyzedFileException $e) {
|
||||
$this->codebase->file_provider->openFile($file_path);
|
||||
$this->server->queueFileAnalysis($file_path, $textDocument->uri);
|
||||
return new Success(new Hover([]));
|
||||
}
|
||||
|
||||
try {
|
||||
$reference_location = $this->codebase->getReferenceAtPosition($file_path, $position);
|
||||
} catch (\Psalm\Exception\UnanalyzedFileException $e) {
|
||||
$this->codebase->file_provider->openFile($file_path);
|
||||
$this->server->queueFileAnalysis($file_path, $textDocument->uri);
|
||||
return new Hover([]);
|
||||
}
|
||||
if ($reference_location === null) {
|
||||
return new Success(new Hover([]));
|
||||
}
|
||||
|
||||
if ($reference_location === null) {
|
||||
return new Hover([]);
|
||||
}
|
||||
list($reference, $range) = $reference_location;
|
||||
|
||||
list($reference, $range) = $reference_location;
|
||||
|
||||
$contents = [];
|
||||
$contents[] = new MarkedString(
|
||||
'php',
|
||||
$this->codebase->getSymbolInformation($file_path, $reference)
|
||||
);
|
||||
|
||||
return new Hover($contents, $range);
|
||||
}
|
||||
$contents = [];
|
||||
$contents[] = new MarkedString(
|
||||
'php',
|
||||
$this->codebase->getSymbolInformation($file_path, $reference)
|
||||
);
|
||||
|
||||
return new Success(new Hover($contents, $range));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -278,92 +258,81 @@ class TextDocument
|
||||
*/
|
||||
public function completion(TextDocumentIdentifier $textDocument, Position $position): Promise
|
||||
{
|
||||
return \Amp\call(
|
||||
/**
|
||||
* @return \Generator<int, true, mixed, array<empty, empty>|CompletionList>
|
||||
*/
|
||||
function () use ($textDocument, $position) {
|
||||
if (false) {
|
||||
yield true;
|
||||
$file_path = LanguageServer::uriToPath($textDocument->uri);
|
||||
|
||||
$completion_data = $this->codebase->getCompletionDataAtPosition($file_path, $position);
|
||||
|
||||
if (!$completion_data) {
|
||||
error_log('completion not found at ' . $position->line . ':' . $position->character);
|
||||
return new Success([]);
|
||||
}
|
||||
|
||||
list($recent_type, $gap) = $completion_data;
|
||||
|
||||
error_log('gap: "' . $gap . '" and type: "' . $recent_type . '"');
|
||||
|
||||
$completion_items = [];
|
||||
|
||||
if ($gap === '->' || $gap === '::') {
|
||||
$instance_completion_items = [];
|
||||
$static_completion_items = [];
|
||||
|
||||
try {
|
||||
$class_storage = $this->codebase->classlike_storage_provider->get($recent_type);
|
||||
|
||||
foreach ($class_storage->appearing_method_ids as $declaring_method_id) {
|
||||
$method_storage = $this->codebase->methods->getStorage($declaring_method_id);
|
||||
|
||||
$instance_completion_items[] = new CompletionItem(
|
||||
(string)$method_storage,
|
||||
CompletionItemKind::METHOD,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
$method_storage->cased_name . '()'
|
||||
);
|
||||
}
|
||||
|
||||
$file_path = LanguageServer::uriToPath($textDocument->uri);
|
||||
foreach ($class_storage->declaring_property_ids as $property_name => $declaring_class) {
|
||||
$property_storage = $this->codebase->properties->getStorage(
|
||||
$declaring_class . '::$' . $property_name
|
||||
);
|
||||
|
||||
$completion_data = $this->codebase->getCompletionDataAtPosition($file_path, $position);
|
||||
|
||||
if (!$completion_data) {
|
||||
error_log('completion not found at ' . $position->line . ':' . $position->character);
|
||||
return [];
|
||||
$instance_completion_items[] = new CompletionItem(
|
||||
$property_storage->getInfo() . ' $' . $property_name,
|
||||
CompletionItemKind::PROPERTY,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
($gap === '::' ? '$' : '') . $property_name
|
||||
);
|
||||
}
|
||||
|
||||
list($recent_type, $gap) = $completion_data;
|
||||
|
||||
error_log('gap: "' . $gap . '" and type: "' . $recent_type . '"');
|
||||
|
||||
$completion_items = [];
|
||||
|
||||
if ($gap === '->' || $gap === '::') {
|
||||
$instance_completion_items = [];
|
||||
$static_completion_items = [];
|
||||
|
||||
try {
|
||||
$class_storage = $this->codebase->classlike_storage_provider->get($recent_type);
|
||||
|
||||
foreach ($class_storage->appearing_method_ids as $declaring_method_id) {
|
||||
$method_storage = $this->codebase->methods->getStorage($declaring_method_id);
|
||||
|
||||
$instance_completion_items[] = new CompletionItem(
|
||||
(string)$method_storage,
|
||||
CompletionItemKind::METHOD,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
$method_storage->cased_name . '()'
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($class_storage->declaring_property_ids as $property_name => $declaring_class) {
|
||||
$property_storage = $this->codebase->properties->getStorage(
|
||||
$declaring_class . '::$' . $property_name
|
||||
);
|
||||
|
||||
$instance_completion_items[] = new CompletionItem(
|
||||
$property_storage->getInfo() . ' $' . $property_name,
|
||||
CompletionItemKind::PROPERTY,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
($gap === '::' ? '$' : '') . $property_name
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($class_storage->class_constant_locations as $const_name => $_) {
|
||||
$static_completion_items[] = new CompletionItem(
|
||||
'const ' . $const_name,
|
||||
CompletionItemKind::VARIABLE,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
$const_name
|
||||
);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
error_log($e->getMessage());
|
||||
return [];
|
||||
}
|
||||
|
||||
$completion_items = $gap === '->'
|
||||
? $instance_completion_items
|
||||
: array_merge($instance_completion_items, $static_completion_items);
|
||||
|
||||
error_log('Found ' . count($completion_items) . ' items');
|
||||
foreach ($class_storage->class_constant_locations as $const_name => $_) {
|
||||
$static_completion_items[] = new CompletionItem(
|
||||
'const ' . $const_name,
|
||||
CompletionItemKind::VARIABLE,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
$const_name
|
||||
);
|
||||
}
|
||||
|
||||
return new CompletionList($completion_items, false);
|
||||
} catch (\Exception $e) {
|
||||
error_log($e->getMessage());
|
||||
return new Success([]);
|
||||
}
|
||||
);
|
||||
|
||||
$completion_items = $gap === '->'
|
||||
? $instance_completion_items
|
||||
: array_merge($instance_completion_items, $static_completion_items);
|
||||
|
||||
error_log('Found ' . count($completion_items) . ' items');
|
||||
}
|
||||
|
||||
return new Success(new CompletionList($completion_items, false));
|
||||
}
|
||||
}
|
||||
|
41
src/Psalm/Internal/Stubs/Amp.php
Normal file
41
src/Psalm/Internal/Stubs/Amp.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Amp;
|
||||
|
||||
/**
|
||||
* @template TReturn
|
||||
* @param callable():\Generator<mixed, mixed, mixed, TReturn> $gen
|
||||
* @return Promise<TReturn>
|
||||
*/
|
||||
function coroutine(callable $gen) : Promise {}
|
||||
|
||||
/**
|
||||
* @template TReturn
|
||||
* @param callable():(\Generator<mixed, mixed, mixed, TReturn>|null) $gen
|
||||
* @return Promise<TReturn>
|
||||
*/
|
||||
function call(callable $gen) : Promise {}
|
||||
|
||||
/**
|
||||
* @template TReturn
|
||||
*/
|
||||
interface Promise {
|
||||
/**
|
||||
* @param callable(\Throwable|null $exception, TReturn|null $result):void
|
||||
* @return void
|
||||
*/
|
||||
function onResolve(callable $onResolved);
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TReturn
|
||||
*
|
||||
* @template-implements Promise<TReturn>
|
||||
*/
|
||||
class Success implements Promise {
|
||||
/**
|
||||
* @param callable(\Throwable|null $exception, TReturn|null $result):void
|
||||
* @return void
|
||||
*/
|
||||
function onResolve(callable $onResolved) {}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Amp;
|
||||
|
||||
/**
|
||||
* @template TReturn
|
||||
* @param callable():\Generator<mixed, mixed, mixed, TReturn> $gen
|
||||
* @return Promise<TReturn>
|
||||
*/
|
||||
function coroutine(callable $gen) : Promise {}
|
||||
|
||||
/**
|
||||
* @template TReturn
|
||||
* @param callable():(\Generator<mixed, mixed, mixed, TReturn>|null) $gen
|
||||
* @return Promise<TReturn>
|
||||
*/
|
||||
function call(callable $gen) : Promise {}
|
||||
|
||||
/**
|
||||
* @template TReturn
|
||||
*/
|
||||
class Promise {
|
||||
/**
|
||||
* @return TReturn
|
||||
*/
|
||||
function wait() {}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user