| // 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.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 clientProvided(LspAnalysisServer server, ProgressToken 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 serverCreated(LspAnalysisServer server, [ProgressToken? token]) => | |
| _ServerCreatedProgressReporter(server, token); | |
| new _(); | |
| /// Starts progress reporting with a [title] and optional initial [message]. | |
| // TODO(dantup): Add support for cancellable progress notifications. | |
| FutureOr<void> begin(String title, {String? message}); | |
| /// Ends progress reporting with an optional final [message]. | |
| FutureOr<void> end([String message]); | |
| /// Reports progress with an updated [message]. | |
| FutureOr<void> report(String message); | |
| } | |
| class _NoopProgressReporter extends ProgressReporter { | |
| new() : super._(); | |
| @override | |
| void begin(String title, {String? message}) {} | |
| @override | |
| void end([String? message]) {} | |
| @override | |
| void report(String message) {} | |
| } | |
| class _ServerCreatedProgressReporter extends _TokenProgressReporter { | |
| static final _random = Random(); | |
| Future<bool>? _tokenBeginRequest; | |
| new(LspAnalysisServer server, ProgressToken? token) | |
| : super(server, token ?? ProgressToken.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 | |
| .sendLspRequest( | |
| 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. | |
| var beginRequest = _tokenBeginRequest; | |
| if (beginRequest != null) { | |
| var didBegin = await beginRequest; | |
| if (didBegin) { | |
| super.end(message); | |
| } | |
| _tokenBeginRequest = null; | |
| } | |
| } | |
| static String _randomTokenIdentifier() { | |
| var millisecondsSinceEpoch = DateTime.now().millisecondsSinceEpoch; | |
| var random = _random.nextInt(0x3fffffff); | |
| return '$millisecondsSinceEpoch$random'; | |
| } | |
| } | |
| class _TokenProgressReporter extends ProgressReporter { | |
| final LspAnalysisServer _server; | |
| final ProgressToken _token; | |
| bool _needsEnd = false; | |
| new(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)); | |
| } | |
| @override | |
| void report(String message) { | |
| if (!_needsEnd) return; | |
| _sendNotification(WorkDoneProgressReport(message: message)); | |
| } | |
| void _sendNotification(ToJsonable value) { | |
| _server.sendLspNotification( | |
| NotificationMessage( | |
| method: Method.progress, | |
| params: ProgressParams(token: _token, value: value), | |
| jsonrpc: jsonRpcVersion, | |
| ), | |
| ); | |
| } | |
| } |