Skip to content

Commit

Permalink
Modernize LSP progress reporting
Browse files Browse the repository at this point in the history
This will use `$/progress` when available and fall back to old
telemetry-based reporting otherwise
  • Loading branch information
weirdan committed Jul 25, 2023
1 parent be82c3a commit f963b79
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

namespace Psalm\Internal\LanguageServer\Client\Progress;

use LanguageServerProtocol\LogMessage;
use LanguageServerProtocol\MessageType;
use LogicException;
use Psalm\Internal\LanguageServer\ClientHandler;

final class LegacyProgress implements ProgressInterface

Check failure on line 10 in src/Psalm/Internal/LanguageServer/Client/Progress/LegacyProgress.php

View workflow job for this annotation

GitHub Actions / build

InternalClass

src/Psalm/Internal/LanguageServer/Client/Progress/LegacyProgress.php:10:13: InternalClass: Class Psalm\Internal\LanguageServer\Client\Progress\LegacyProgress must be marked @internal (see https://psalm.dev/174)
{
private ClientHandler $handler;
private ?string $title = null;

public function __construct(ClientHandler $handler)
{
$this->handler = $handler;
}

public function begin(string $title, ?string $message = null, ?int $percentage): void
{
if ($this->title !== null) {
throw new LogicException('Progress has already been started');
}

$this->title = $title;

$this->notify($message);
}

public function update(?string $message = null, ?int $percentage): void
{
if ($this->title === null) {
throw new LogicException('The progress has not been started yet');
}

$this->notify($message);
}

public function end(?string $message): void
{
if ($this->title === null) {
throw new LogicException('The progress has not been started yet');
}

$this->notify($message);
}

private function notify(?string $message): void
{
$this->handler->notify(
'telemetry/event',
new LogMessage(
$this->title . (empty($message) ? '' : (': ' . $message)),

Check failure on line 54 in src/Psalm/Internal/LanguageServer/Client/Progress/LegacyProgress.php

View workflow job for this annotation

GitHub Actions / build

InvalidScalarArgument

src/Psalm/Internal/LanguageServer/Client/Progress/LegacyProgress.php:54:17: InvalidScalarArgument: Argument 1 of LanguageServerProtocol\LogMessage::__construct expects int, but string provided (see https://psalm.dev/012)
MessageType::INFO,

Check failure on line 55 in src/Psalm/Internal/LanguageServer/Client/Progress/LegacyProgress.php

View workflow job for this annotation

GitHub Actions / build

InvalidScalarArgument

src/Psalm/Internal/LanguageServer/Client/Progress/LegacyProgress.php:55:17: InvalidScalarArgument: Argument 2 of LanguageServerProtocol\LogMessage::__construct expects string, but 3 provided (see https://psalm.dev/012)
),
);
}
}
101 changes: 101 additions & 0 deletions src/Psalm/Internal/LanguageServer/Client/Progress/Progress.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php

namespace Psalm\Internal\LanguageServer\Client\Progress;

use LogicException;
use Psalm\Internal\LanguageServer\ClientHandler;

/** @internal */
final class Progress implements ProgressInterface
{
private ClientHandler $handler;
private string $token;
private bool $withPercentage = false;
private bool $finished = false;

public function __construct(ClientHandler $handler, string $token)
{
$this->handler = $handler;
$this->token = $token;
}

public function begin(
string $title,
?string $message = null,
?int $percent = null

Check failure on line 25 in src/Psalm/Internal/LanguageServer/Client/Progress/Progress.php

View workflow job for this annotation

GitHub Actions / build

ParamNameMismatch

src/Psalm/Internal/LanguageServer/Client/Progress/Progress.php:25:14: ParamNameMismatch: Argument 3 of Psalm\Internal\LanguageServer\Client\Progress\Progress::begin has wrong name $percent, expecting $percentage as defined by Psalm\Internal\LanguageServer\Client\Progress\ProgressInterface::begin (see https://psalm.dev/230)
): void {
if ($this->finished) {
throw new LogicException('Progress has already been finished');
}

$notification = [
'token' => $this->token,
'value' => [
'kind' => 'begin',
'title' => $title,
],
];
if ($message !== null) {
$notification['value']['message'] = $message;
}

if ($percent !== null) {
$notification['value']['percentage'] = $percent;
$this->withPercentage = true;
}

$this->handler->notify('$/progress', $notification);
}

public function end(?string $message = null): void
{
if ($this->finished) {
throw new LogicException('Progress has already been finished');
}

$notification = [
'token' => $this->token,
'value' => [
'kind' => 'end',
],
];

if ($message !== null) {
$notification['value']['message'] = $message;
}

$this->handler->notify('$/progress', $notification);

$this->finished = true;
}

public function update(?string $message = null, ?int $percentage = null): void
{
if ($this->finished) {
throw new LogicException('Progress has already been finished');
}

$notification = [
'token' => $this->token,
'value' => [
'kind' => 'report',
],
];

if ($message !== null) {
$notification['value']['message'] = $message;
}

if ($percentage !== null) {
if ($this->withPercentage) {
throw new LogicException(
'Cannot update percentage for progress '
. 'that was started with percentage',
);
}
$notification['value']['percentage'] = $percentage;
}

$this->handler->notify('$/progress', $notification);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Psalm\Internal\LanguageServer\Client\Progress;

interface ProgressInterface
{
public function begin(
string $title,
?string $message = null,
?int $percentage
): void;

public function update(?string $message = null, ?int $percentage): void;
public function end(?string $message): void;
}
11 changes: 11 additions & 0 deletions src/Psalm/Internal/LanguageServer/LanguageClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
use JsonMapper;
use LanguageServerProtocol\LogMessage;
use LanguageServerProtocol\LogTrace;
use Psalm\Internal\LanguageServer\Client\Progress\LegacyProgress;
use Psalm\Internal\LanguageServer\Client\Progress\Progress;
use Psalm\Internal\LanguageServer\Client\TextDocument as ClientTextDocument;
use Psalm\Internal\LanguageServer\Client\Workspace as ClientWorkspace;

Expand Down Expand Up @@ -131,6 +133,15 @@ public function event(LogMessage $logMessage): void
);
}

public function makeProgress(string $token): Progress

Check failure on line 136 in src/Psalm/Internal/LanguageServer/LanguageClient.php

View workflow job for this annotation

GitHub Actions / build

InvalidReturnType

src/Psalm/Internal/LanguageServer/LanguageClient.php:136:50: InvalidReturnType: The declared return type 'Psalm\Internal\LanguageServer\Client\Progress\Progress' for Psalm\Internal\LanguageServer\LanguageClient::makeProgress is incorrect, got 'Psalm\Internal\LanguageServer\Client\Progress\LegacyProgress|Psalm\Internal\LanguageServer\Client\Progress\Progress' (see https://psalm.dev/011)
{
if ($this->server->clientCapabilities->window->workDoneProgress ?? false) {
return new Progress($this->handler, $token);
} else {
return new LegacyProgress($this->handler);

Check failure on line 141 in src/Psalm/Internal/LanguageServer/LanguageClient.php

View workflow job for this annotation

GitHub Actions / build

InvalidReturnStatement

src/Psalm/Internal/LanguageServer/LanguageClient.php:141:20: InvalidReturnStatement: The inferred type 'Psalm\Internal\LanguageServer\Client\Progress\LegacyProgress' does not match the declared return type 'Psalm\Internal\LanguageServer\Client\Progress\Progress' for Psalm\Internal\LanguageServer\LanguageClient::makeProgress (see https://psalm.dev/128)
}
}

/**
* Configuration Refreshed from Client
*
Expand Down
18 changes: 11 additions & 7 deletions src/Psalm/Internal/LanguageServer/LanguageServer.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
use function strpos;
use function substr;
use function trim;
use function uniqid;
use function urldecode;

use const JSON_PRETTY_PRINT;
Expand Down Expand Up @@ -398,7 +399,8 @@ public function initialize(
?string $rootPath = null,
?string $rootUri = null,
$initializationOptions = null,
?string $trace = null
?string $trace = null,
?string $workdDoneToken = null
//?array $workspaceFolders = null //error in json-dispatcher
): Promise {
$this->clientInfo = $clientInfo;
Expand All @@ -412,9 +414,11 @@ public function initialize(

return call(
/** @return Generator<int, true, mixed, InitializeResult> */
function () {
function () use ($workdDoneToken) {
$progress = $this->client->makeProgress($workdDoneToken ?? uniqid('tkn'));

$this->logInfo("Initializing...");
$this->clientStatus('initializing');
$progress->begin('Initialization', 'Starting');

// Eventually, this might block on something. Leave it as a generator.
/** @psalm-suppress TypeDoesNotContainType */
Expand All @@ -425,14 +429,14 @@ function () {
$this->project_analyzer->serverMode($this);

$this->logInfo("Initializing: Getting code base...");
$this->clientStatus('initializing', 'getting code base');
$progress->update('Getting code base');

$this->logInfo("Initializing: Scanning files ({$this->project_analyzer->threads} Threads)...");
$this->clientStatus('initializing', 'scanning files');
$progress->update('Scanning files');
$this->codebase->scanFiles($this->project_analyzer->threads);

$this->logInfo("Initializing: Registering stub files...");
$this->clientStatus('initializing', 'registering stub files');
$progress->update('Registering stub files');
$this->codebase->config->visitStubFiles($this->codebase, $this->project_analyzer->progress);

if ($this->textDocument === null) {
Expand Down Expand Up @@ -572,7 +576,7 @@ function () {
}

$this->logInfo("Initializing: Complete.");
$this->clientStatus('initialized');
$progress->end('Initialized');

/**
* Information about the server.
Expand Down

0 comments on commit f963b79

Please sign in to comment.