blob: 398d0b0283c3637d6af31245fc639fd85d54bf9b [file] [log] [blame]
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'dart:math';
import 'package:analysis_server/lsp_protocol/protocol_generated.dart';
import 'package:analysis_server/lsp_protocol/protocol_special.dart';
import 'package:analysis_server/src/lsp/lsp_analysis_server.dart';
/// Reports progress of long-running operations to the LSP client.
abstract class ProgressReporter {
/// A no-op reporter that does nothing.
static final noop = _NoopProgressReporter();
/// Creates a reporter for a token that was supplied by the client and does
/// not need creating prior to use.
factory ProgressReporter.clientProvided(
LspAnalysisServer server, Either2<int, String> token) =>
_TokenProgressReporter(server, token);
/// Creates a reporter for a new token that must be created prior to being
/// used.
///
/// If [token] is not supplied, a random identifier will be used.
factory ProgressReporter.serverCreated(LspAnalysisServer server,
[Either2<int, String>? token]) =>
_ServerCreatedProgressReporter(server, token);
ProgressReporter._();
// TODO(dantup): Add support for cancellable progress notifications.
FutureOr<void> begin(String title, {String? message});
FutureOr<void> end([String message]);
}
class _NoopProgressReporter extends ProgressReporter {
_NoopProgressReporter() : super._();
@override
void begin(String title, {String? message}) {}
@override
void end([String? message]) {}
}
class _ServerCreatedProgressReporter extends _TokenProgressReporter {
static final _random = Random();
Future<bool>? _tokenBeginRequest;
_ServerCreatedProgressReporter(
LspAnalysisServer server,
Either2<int, String>? token,
) : super(
server,
token ?? Either2<int, String>.t2(_randomTokenIdentifier()),
);
@override
Future<void> begin(String? title, {String? message}) async {
assert(_tokenBeginRequest == null,
'Begin should not be called more than once');
// Put the create/begin into a future so if end() is called before the
// begin is sent (which could happen because create is async), end will
// not be sent/return too early.
_tokenBeginRequest = _server
.sendRequest(Method.window_workDoneProgress_create,
WorkDoneProgressCreateParams(token: _token))
.then((response) {
// If the client did not create a token, do not send begin (and signal
// that we should also not send end).
if (response.error != null) return false;
super.begin(title, message: message);
return true;
});
await _tokenBeginRequest;
}
@override
Future<void> end([String? message]) async {
// Only end the token after both create/begin have completed, and return
// a Future to indicate that has happened to callers know when it's safe
// to re-use the token identifier.
final beginRequest = _tokenBeginRequest;
if (beginRequest != null) {
final didBegin = await beginRequest;
if (didBegin) {
super.end(message);
}
_tokenBeginRequest = null;
}
}
static String _randomTokenIdentifier() {
final millisecondsSinceEpoch = DateTime.now().millisecondsSinceEpoch;
final random = _random.nextInt(0x3fffffff);
return '$millisecondsSinceEpoch$random';
}
}
class _TokenProgressReporter extends ProgressReporter {
final LspAnalysisServer _server;
final Either2<int, String> _token;
bool _needsEnd = false;
_TokenProgressReporter(this._server, this._token) : super._();
@override
void begin(String? title, {String? message}) {
_needsEnd = true;
_sendNotification(
WorkDoneProgressBegin(title: title ?? 'Working…', message: message));
}
@override
void end([String? message]) {
if (!_needsEnd) return;
_needsEnd = false;
_sendNotification(WorkDoneProgressEnd(message: message));
}
void _sendNotification(ToJsonable value) async {
_server.sendNotification(NotificationMessage(
method: Method.progress,
params: ProgressParams(
token: _token,
value: value,
),
jsonrpc: jsonRpcVersion));
}
}