| // 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 ProgressReporter.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 ProgressReporter.serverCreated( |
| LspAnalysisServer server, [ |
| ProgressToken? 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, 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; |
| |
| _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) { |
| _server.sendLspNotification( |
| NotificationMessage( |
| method: Method.progress, |
| params: ProgressParams(token: _token, value: value), |
| jsonrpc: jsonRpcVersion, |
| ), |
| ); |
| } |
| } |