blob: c97d329b2a888c30430caeb0a097ac29aa419e72 [file] [log] [blame]
// Copyright (c) 2012, 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:io';
import 'log.dart' as log;
import 'utils.dart';
/// A live-updating progress indicator for long-running log entries.
class Progress {
/// The timer used to write "..." during a progress log.
Timer _timer;
/// The [Stopwatch] used to track how long a progress log has been running.
final _stopwatch = Stopwatch();
/// The progress message as it's being incrementally appended.
///
/// When the progress is done, a single entry will be added to the log for it.
final String _message;
/// Gets the current progress time as a parenthesized, formatted string.
String get _time => '(${niceDuration(_stopwatch.elapsed)})';
/// The length of the most recently-printed [_time] string.
var _timeLength = 0;
/// Creates a new progress indicator.
///
/// If [fine] is passed, this will log progress messages on [log.Level.FINE]
/// as opposed to [log.Level.MESSAGE].
Progress(this._message, {bool fine = false}) {
_stopwatch.start();
var level = fine ? log.Level.FINE : log.Level.MESSAGE;
// The animation is only shown when it would be meaningful to a human.
// That means we're writing a visible message to a TTY at normal log levels
// with non-JSON output.
if (stdioType(stdout) != StdioType.terminal ||
!log.verbosity.isLevelVisible(level) ||
log.json.enabled ||
fine ||
log.verbosity.isLevelVisible(log.Level.FINE)) {
// Not animating, so just log the start and wait until the task is
// completed.
log.write(level, '$_message...');
return;
}
_timer = Timer.periodic(Duration(milliseconds: 100), (_) {
_update();
});
stdout.write('$_message... ');
}
/// Stops the progress indicator.
void stop() {
_stopwatch.stop();
// Always log the final time as [log.fine] because for the most part normal
// users don't care about the precise time information beyond what's shown
// in the animation.
log.fine('$_message finished $_time.');
// If we were animating, print one final update to show the user the final
// time.
if (_timer == null) return;
_timer.cancel();
_timer = null;
_update();
stdout.writeln();
}
/// Erases the progress message and stops the progress indicator.
Future<void> stopAndClear() async {
_stopwatch.stop();
if (_timer != null) {
stdout.write('\b' * (_message.length + '... '.length + _timeLength));
}
// Always log the final time as [log.fine] because for the most part normal
// users don't care about the precise time information beyond what's shown
// in the animation.
log.fine('$_message finished $_time.');
// If we were animating, print one final update to show the user the final
// time.
if (_timer == null) return;
_timer.cancel();
_timer = null;
}
/// Stop animating the progress indicator.
///
/// This will continue running the stopwatch so that the full time can be
/// logged in [stop].
void stopAnimating() {
if (_timer == null) return;
// Erase the time indicator so that we don't leave a misleading
// half-complete time indicator on the console.
stdout.writeln('\b' * _timeLength);
_timeLength = 0;
_timer.cancel();
_timer = null;
}
/// Refreshes the progress line.
void _update() {
if (log.isMuted) return;
// Show the time only once it gets noticeably long.
if (_stopwatch.elapsed.inSeconds == 0) return;
// Erase the last time that was printed. Erasing just the time using `\b`
// rather than using `\r` to erase the entire line ensures that we don't
// spam progress lines if they're wider than the terminal width.
stdout.write('\b' * _timeLength);
var time = _time;
_timeLength = time.length;
stdout.write(log.gray(time));
}
}