diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart index ea70f44edf1..6b321095427 100644 --- a/packages/flutter_tools/lib/src/android/android_device.dart +++ b/packages/flutter_tools/lib/src/android/android_device.dart @@ -440,7 +440,7 @@ class AndroidDevice extends Device { final String result = (await runCheckedAsync(cmd)).stdout; // This invocation returns 0 even when it fails. if (result.contains('Error: ')) { - printError(result.trim()); + printError(result.trim(), wrap: false); return LaunchResult.failed(); } diff --git a/packages/flutter_tools/lib/src/android/apk.dart b/packages/flutter_tools/lib/src/android/apk.dart index 5b30036ed7f..fc28d54a3a8 100644 --- a/packages/flutter_tools/lib/src/android/apk.dart +++ b/packages/flutter_tools/lib/src/android/apk.dart @@ -34,7 +34,9 @@ Future buildApk({ final List validationResult = androidSdk.validateSdkWellFormed(); if (validationResult.isNotEmpty) { - validationResult.forEach(printError); + for (String message in validationResult) { + printError(message, wrap: false); + } throwToolExit('Try re-installing or updating your Android SDK.'); } diff --git a/packages/flutter_tools/lib/src/base/logger.dart b/packages/flutter_tools/lib/src/base/logger.dart index 41874723dca..eb8d92f5b5f 100644 --- a/packages/flutter_tools/lib/src/base/logger.dart +++ b/packages/flutter_tools/lib/src/base/logger.dart @@ -40,6 +40,8 @@ abstract class Logger { /// If [hangingIndent] is specified, then any wrapped lines will be indented /// by this much more than the first line, if wrapping is enabled in /// [outputPreferences]. + /// If [wrap] is specified, then it overrides the + /// [outputPreferences.wrapText] setting. void printError( String message, { StackTrace stackTrace, @@ -47,6 +49,7 @@ abstract class Logger { TerminalColor color, int indent, int hangingIndent, + bool wrap, }); /// Display normal output of the command. This should be used for things like @@ -68,6 +71,8 @@ abstract class Logger { /// If [hangingIndent] is specified, then any wrapped lines will be indented /// by this much more than the first line, if wrapping is enabled in /// [outputPreferences]. + /// If [wrap] is specified, then it overrides the + /// [outputPreferences.wrapText] setting. void printStatus( String message, { bool emphasis, @@ -75,6 +80,7 @@ abstract class Logger { bool newline, int indent, int hangingIndent, + bool wrap, }); /// Use this for verbose tracing output. Users can turn this output on in order @@ -111,9 +117,10 @@ class StdoutLogger extends Logger { TerminalColor color, int indent, int hangingIndent, + bool wrap, }) { message ??= ''; - message = wrapText(message, indent: indent, hangingIndent: hangingIndent); + message = wrapText(message, indent: indent, hangingIndent: hangingIndent, shouldWrap: wrap); _status?.cancel(); _status = null; if (emphasis == true) @@ -133,9 +140,10 @@ class StdoutLogger extends Logger { bool newline, int indent, int hangingIndent, + bool wrap, }) { message ??= ''; - message = wrapText(message, indent: indent, hangingIndent: hangingIndent); + message = wrapText(message, indent: indent, hangingIndent: hangingIndent, shouldWrap: wrap); _status?.cancel(); _status = null; if (emphasis == true) @@ -232,9 +240,10 @@ class BufferLogger extends Logger { TerminalColor color, int indent, int hangingIndent, + bool wrap, }) { _error.writeln(terminal.color( - wrapText(message, indent: indent, hangingIndent: hangingIndent), + wrapText(message, indent: indent, hangingIndent: hangingIndent, shouldWrap: wrap), color ?? TerminalColor.red, )); } @@ -247,11 +256,12 @@ class BufferLogger extends Logger { bool newline, int indent, int hangingIndent, + bool wrap, }) { if (newline != false) - _status.writeln(wrapText(message, indent: indent, hangingIndent: hangingIndent)); + _status.writeln(wrapText(message, indent: indent, hangingIndent: hangingIndent, shouldWrap: wrap)); else - _status.write(wrapText(message, indent: indent, hangingIndent: hangingIndent)); + _status.write(wrapText(message, indent: indent, hangingIndent: hangingIndent, shouldWrap: wrap)); } @override @@ -297,10 +307,11 @@ class VerboseLogger extends Logger { TerminalColor color, int indent, int hangingIndent, + bool wrap, }) { _emit( _LogType.error, - wrapText(message, indent: indent, hangingIndent: hangingIndent), + wrapText(message, indent: indent, hangingIndent: hangingIndent, shouldWrap: wrap), stackTrace, ); } @@ -313,8 +324,9 @@ class VerboseLogger extends Logger { bool newline, int indent, int hangingIndent, + bool wrap, }) { - _emit(_LogType.status, wrapText(message, indent: indent, hangingIndent: hangingIndent)); + _emit(_LogType.status, wrapText(message, indent: indent, hangingIndent: hangingIndent, shouldWrap: wrap)); } @override diff --git a/packages/flutter_tools/lib/src/base/process.dart b/packages/flutter_tools/lib/src/base/process.dart index 5632752b942..37ef5bfef97 100644 --- a/packages/flutter_tools/lib/src/base/process.dart +++ b/packages/flutter_tools/lib/src/base/process.dart @@ -148,7 +148,7 @@ Future runCommandAndStreamOutput(List cmd, { if (trace) printTrace(message); else - printStatus(message); + printStatus(message, wrap: false); } }); final StreamSubscription stderrSubscription = process.stderr @@ -159,7 +159,7 @@ Future runCommandAndStreamOutput(List cmd, { if (mapFunction != null) line = mapFunction(line); if (line != null) - printError('$prefix$line'); + printError('$prefix$line', wrap: false); }); // Wait for stdout to be fully processed diff --git a/packages/flutter_tools/lib/src/base/terminal.dart b/packages/flutter_tools/lib/src/base/terminal.dart index bd7e3497a30..8ad874c924e 100644 --- a/packages/flutter_tools/lib/src/base/terminal.dart +++ b/packages/flutter_tools/lib/src/base/terminal.dart @@ -45,7 +45,7 @@ class OutputPreferences { int wrapColumn, bool showColor, }) : wrapText = wrapText ?? io.stdio?.hasTerminal ?? const io.Stdio().hasTerminal, - wrapColumn = wrapColumn ?? io.stdio?.terminalColumns ?? const io.Stdio().terminalColumns ?? kDefaultTerminalColumns, + _overrideWrapColumn = wrapColumn, showColor = showColor ?? platform.stdoutSupportsAnsi ?? false; /// If [wrapText] is true, then any text sent to the context's [Logger] @@ -64,8 +64,12 @@ class OutputPreferences { /// To find out if we're writing to a terminal, it tries the context's stdio, /// and if that's not set, it tries creating a new [io.Stdio] and asks it, if /// that doesn't have an idea of the terminal width, then we just use a - /// default of 100. It will be ignored if wrapText is false. - final int wrapColumn; + /// default of 100. It will be ignored if [wrapText] is false. + final int _overrideWrapColumn; + int get wrapColumn { + return _overrideWrapColumn ?? io.stdio?.terminalColumns + ?? const io.Stdio().terminalColumns ?? kDefaultTerminalColumns; + } /// Whether or not to output ANSI color codes when writing to the output /// terminal. Defaults to whatever [platform.stdoutSupportsAnsi] says if diff --git a/packages/flutter_tools/lib/src/base/utils.dart b/packages/flutter_tools/lib/src/base/utils.dart index b9cb8360a28..0ed960ec6a7 100644 --- a/packages/flutter_tools/lib/src/base/utils.dart +++ b/packages/flutter_tools/lib/src/base/utils.dart @@ -313,7 +313,8 @@ const int kMinColumnWidth = 10; /// Wraps a block of text into lines no longer than [columnWidth]. /// /// Tries to split at whitespace, but if that's not good enough to keep it -/// under the limit, then it splits in the middle of a word. +/// under the limit, then it splits in the middle of a word. If [columnWidth] is +/// smaller than 10 columns, will wrap at 10 columns. /// /// Preserves indentation (leading whitespace) for each line (delimited by '\n') /// in the input, and will indent wrapped lines that same amount, adding @@ -339,11 +340,12 @@ const int kMinColumnWidth = 10; /// [outputPreferences.wrapColumn], which is set with the --wrap-column option. /// /// If [outputPreferences.wrapText] is false, then the text will be returned -/// unchanged. +/// unchanged. If [shouldWrap] is specified, then it overrides the +/// [outputPreferences.wrapText] setting. /// /// The [indent] and [hangingIndent] must be smaller than [columnWidth] when /// added together. -String wrapText(String text, {int columnWidth, int hangingIndent, int indent}) { +String wrapText(String text, {int columnWidth, int hangingIndent, int indent, bool shouldWrap}) { if (text == null || text.isEmpty) { return ''; } @@ -366,6 +368,7 @@ String wrapText(String text, {int columnWidth, int hangingIndent, int indent}) { final List firstLineWrap = _wrapTextAsLines( trimmedText, columnWidth: columnWidth - leadingWhitespace.length, + shouldWrap: shouldWrap, ); notIndented = [firstLineWrap.removeAt(0)]; trimmedText = trimmedText.substring(notIndented[0].length).trimLeft(); @@ -373,12 +376,14 @@ String wrapText(String text, {int columnWidth, int hangingIndent, int indent}) { notIndented.addAll(_wrapTextAsLines( trimmedText, columnWidth: columnWidth - leadingWhitespace.length - hangingIndent, + shouldWrap: shouldWrap, )); } } else { notIndented = _wrapTextAsLines( trimmedText, columnWidth: columnWidth - leadingWhitespace.length, + shouldWrap: shouldWrap, ); } String hangingIndentString; @@ -426,14 +431,16 @@ class _AnsiRun { /// default will be [outputPreferences.wrapColumn]. /// /// If [outputPreferences.wrapText] is false, then the text will be returned -/// simply split at the newlines, but not wrapped. -List _wrapTextAsLines(String text, {int start = 0, int columnWidth}) { +/// simply split at the newlines, but not wrapped. If [shouldWrap] is specified, +/// then it overrides the [outputPreferences.wrapText] setting. +List _wrapTextAsLines(String text, {int start = 0, int columnWidth, bool shouldWrap}) { if (text == null || text.isEmpty) { return ['']; } assert(columnWidth != null); assert(columnWidth >= 0); assert(start >= 0); + shouldWrap ??= outputPreferences.wrapText; /// Returns true if the code unit at [index] in [text] is a whitespace /// character. @@ -495,7 +502,7 @@ List _wrapTextAsLines(String text, {int start = 0, int columnWidth}) { for (String line in text.split('\n')) { // If the line is short enough, even with ANSI codes, then we can just add // add it and move on. - if (line.length <= effectiveLength || !outputPreferences.wrapText) { + if (line.length <= effectiveLength || !shouldWrap) { result.add(line); continue; } diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart index 203315e6502..f8799ae7474 100644 --- a/packages/flutter_tools/lib/src/commands/daemon.dart +++ b/packages/flutter_tools/lib/src/commands/daemon.dart @@ -758,6 +758,7 @@ class NotifyingLogger extends Logger { TerminalColor color, int indent, int hangingIndent, + bool wrap, }) { _messageController.add(LogMessage('error', message, stackTrace)); } @@ -770,6 +771,7 @@ class NotifyingLogger extends Logger { bool newline = true, int indent, int hangingIndent, + bool wrap, }) { _messageController.add(LogMessage('status', message)); } @@ -892,6 +894,7 @@ class _AppRunLogger extends Logger { TerminalColor color, int indent, int hangingIndent, + bool wrap, }) { if (parent != null) { parent.printError( @@ -900,6 +903,7 @@ class _AppRunLogger extends Logger { emphasis: emphasis, indent: indent, hangingIndent: hangingIndent, + wrap: wrap, ); } else { if (stackTrace != null) { @@ -925,6 +929,7 @@ class _AppRunLogger extends Logger { bool newline = true, int indent, int hangingIndent, + bool wrap, }) { if (parent != null) { parent.printStatus( @@ -934,6 +939,7 @@ class _AppRunLogger extends Logger { newline: newline, indent: indent, hangingIndent: hangingIndent, + wrap: wrap, ); } else { _sendLogEvent({'log': message}); diff --git a/packages/flutter_tools/lib/src/commands/fuchsia_reload.dart b/packages/flutter_tools/lib/src/commands/fuchsia_reload.dart index c8cc995cb9a..7dc1a888c7f 100644 --- a/packages/flutter_tools/lib/src/commands/fuchsia_reload.dart +++ b/packages/flutter_tools/lib/src/commands/fuchsia_reload.dart @@ -540,7 +540,7 @@ class FuchsiaDeviceCommandRunner { printTrace(args.join(' ')); final ProcessResult result = await processManager.run(args); if (result.exitCode != 0) { - printStatus('Command failed: $command\nstdout: ${result.stdout}\nstderr: ${result.stderr}'); + printStatus('Command failed: $command\nstdout: ${result.stdout}\nstderr: ${result.stderr}', wrap: false); return null; } printTrace(result.stdout); diff --git a/packages/flutter_tools/lib/src/commands/logs.dart b/packages/flutter_tools/lib/src/commands/logs.dart index de8283f3962..77759f696b0 100644 --- a/packages/flutter_tools/lib/src/commands/logs.dart +++ b/packages/flutter_tools/lib/src/commands/logs.dart @@ -51,7 +51,7 @@ class LogsCommand extends FlutterCommand { // Start reading. final StreamSubscription subscription = logReader.logLines.listen( - printStatus, + (String message) => printStatus(message, wrap: false), onDone: () { exitCompleter.complete(0); }, diff --git a/packages/flutter_tools/lib/src/commands/update_packages.dart b/packages/flutter_tools/lib/src/commands/update_packages.dart index 6c5f69d73f7..7df36b3fb99 100644 --- a/packages/flutter_tools/lib/src/commands/update_packages.dart +++ b/packages/flutter_tools/lib/src/commands/update_packages.dart @@ -342,7 +342,7 @@ class UpdatePackagesCommand extends FlutterCommand { if (path != null) buf.write(' <- '); } - printStatus(buf.toString()); + printStatus(buf.toString(), wrap: false); } if (paths.isEmpty) { diff --git a/packages/flutter_tools/lib/src/globals.dart b/packages/flutter_tools/lib/src/globals.dart index d0323261084..4c3f6b8fe8b 100644 --- a/packages/flutter_tools/lib/src/globals.dart +++ b/packages/flutter_tools/lib/src/globals.dart @@ -27,6 +27,7 @@ void printError( TerminalColor color, int indent, int hangingIndent, + bool wrap, }) { logger.printError( message, @@ -35,6 +36,7 @@ void printError( color: color, indent: indent, hangingIndent: hangingIndent, + wrap: wrap, ); } @@ -54,6 +56,7 @@ void printStatus( TerminalColor color, int indent, int hangingIndent, + bool wrap, }) { logger.printStatus( message, @@ -62,6 +65,7 @@ void printStatus( newline: newline ?? true, indent: indent, hangingIndent: hangingIndent, + wrap: wrap, ); } diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index 9333febc73e..f7630d22d93 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart @@ -240,7 +240,7 @@ class FlutterDevice { return; _loggingSubscription = device.getLogReader(app: package).logLines.listen((String line) { if (!line.contains('Observatory listening on http')) - printStatus(line); + printStatus(line, wrap: false); }); } diff --git a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart index 16016108b83..d6369a0659a 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart @@ -253,9 +253,11 @@ class FlutterCommandRunner extends CommandRunner { contextOverrides[Logger] = VerboseLogger(logger); } - final int terminalColumns = io.stdio.terminalColumns; - int wrapColumn = terminalColumns ?? kDefaultTerminalColumns; - if (topLevelResults['wrap-column'] != null) { + // Don't set wrapColumns unless the user said to: if it's set, then all + // wrapping will occur at this width explicitly, and won't adapt if the + // terminal size changes during a run. + int wrapColumn; + if (topLevelResults.wasParsed('wrap-column')) { try { wrapColumn = int.parse(topLevelResults['wrap-column']); if (wrapColumn < 0) { @@ -272,7 +274,7 @@ class FlutterCommandRunner extends CommandRunner { // anything, unless the user explicitly said to. final bool useWrapping = topLevelResults.wasParsed('wrap') ? topLevelResults['wrap'] - : terminalColumns == null ? false : topLevelResults['wrap']; + : io.stdio.terminalColumns == null ? false : topLevelResults['wrap']; contextOverrides[OutputPreferences] = OutputPreferences( wrapText: useWrapping, showColor: topLevelResults['color'], diff --git a/packages/flutter_tools/test/base/process_test.dart b/packages/flutter_tools/test/base/process_test.dart index 1281a92f371..5d459de1ccb 100644 --- a/packages/flutter_tools/test/base/process_test.dart +++ b/packages/flutter_tools/test/base/process_test.dart @@ -3,19 +3,22 @@ // found in the LICENSE file. import 'package:flutter_tools/src/base/io.dart'; +import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/process.dart'; +import 'package:flutter_tools/src/base/terminal.dart'; import 'package:mockito/mockito.dart'; import 'package:process/process.dart'; import '../src/common.dart'; import '../src/context.dart'; +import '../src/mocks.dart' show MockProcess, MockProcessManager; void main() { group('process exceptions', () { ProcessManager mockProcessManager; setUp(() { - mockProcessManager = MockProcessManager(); + mockProcessManager = PlainMockProcessManager(); }); testUsingContext('runCheckedAsync exceptions should be ProcessException objects', () async { @@ -56,6 +59,35 @@ void main() { expect(cleanup, 4); }); }); + group('output formatting', () { + MockProcessManager mockProcessManager; + BufferLogger mockLogger; + + setUp(() { + mockProcessManager = MockProcessManager(); + mockLogger = BufferLogger(); + }); + + MockProcess Function(List) processMetaFactory(List stdout, {List stderr = const []}) { + final Stream> stdoutStream = + Stream>.fromIterable(stdout.map>((String s) => s.codeUnits)); + final Stream> stderrStream = + Stream>.fromIterable(stderr.map>((String s) => s.codeUnits)); + return (List command) => MockProcess(stdout: stdoutStream, stderr: stderrStream); + } + + testUsingContext('Command output is not wrapped.', () async { + final List testString = ['0123456789' * 10]; + mockProcessManager.processFactory = processMetaFactory(testString, stderr: testString); + await runCommandAndStreamOutput(['command']); + expect(mockLogger.statusText, equals('${testString[0]}\n')); + expect(mockLogger.errorText, equals('${testString[0]}\n')); + }, overrides: { + Logger: () => mockLogger, + ProcessManager: () => mockProcessManager, + OutputPreferences: () => OutputPreferences(wrapText: true, wrapColumn: 40), + }); + }); } -class MockProcessManager extends Mock implements ProcessManager {} +class PlainMockProcessManager extends Mock implements ProcessManager {} \ No newline at end of file diff --git a/packages/flutter_tools/test/utils_test.dart b/packages/flutter_tools/test/utils_test.dart index 2f2822ab402..5ac06491ebe 100644 --- a/packages/flutter_tools/test/utils_test.dart +++ b/packages/flutter_tools/test/utils_test.dart @@ -4,6 +4,7 @@ import 'dart:async'; +import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/utils.dart'; import 'package:flutter_tools/src/base/version.dart'; import 'package:flutter_tools/src/base/terminal.dart'; @@ -165,7 +166,8 @@ baz=qux await Future.delayed(kShortDelay); }, kShortDelay); final Duration duration = await completer.future; - expect(duration, greaterThanOrEqualTo(Duration(milliseconds: kShortDelay.inMilliseconds * 2))); + expect( + duration, greaterThanOrEqualTo(Duration(milliseconds: kShortDelay.inMilliseconds * 2))); }); }); @@ -200,14 +202,16 @@ baz=qux const String _shortLine = 'Short line.'; const String _indentedLongLine = ' This is an indented long line that needs to be ' 'wrapped and indentation preserved.'; + final FakeStdio fakeStdio = FakeStdio(); - void testWrap(String description, Function body) { - testUsingContext(description, body, overrides: { + void testWrap(String description, Function body) { + testUsingContext(description, body, overrides: { OutputPreferences: () => OutputPreferences(wrapText: true, wrapColumn: _lineLength), }); } - void testNoWrap(String description, Function body) { - testUsingContext(description, body, overrides: { + + void testNoWrap(String description, Function body) { + testUsingContext(description, body, overrides: { OutputPreferences: () => OutputPreferences(wrapText: false), }); } @@ -215,6 +219,14 @@ baz=qux test('does not wrap by default in tests', () { expect(wrapText(_longLine), equals(_longLine)); }); + testNoWrap('can override wrap preference if preference is off', () { + expect(wrapText(_longLine, columnWidth: _lineLength, shouldWrap: true), equals(''' +This is a long line that needs to be +wrapped.''')); + }); + testWrap('can override wrap preference if preference is on', () { + expect(wrapText(_longLine, shouldWrap: false), equals(_longLine)); + }); testNoWrap('does not wrap at all if not told to wrap', () { expect(wrapText(_longLine), equals(_longLine)); }); @@ -226,6 +238,20 @@ baz=qux This is a long line that needs to be wrapped.''')); }); + testUsingContext('able to handle dynamically changing terminal column size', () { + fakeStdio.currentColumnSize = 20; + expect(wrapText(_longLine), equals(''' +This is a long line +that needs to be +wrapped.''')); + fakeStdio.currentColumnSize = _lineLength; + expect(wrapText(_longLine), equals(''' +This is a long line that needs to be +wrapped.''')); + }, overrides: { + OutputPreferences: () => OutputPreferences(wrapText: true), + Stdio: () => fakeStdio, + }); testWrap('wrap long lines with no whitespace', () { expect(wrapText('0123456789' * 5, columnWidth: _lineLength), equals(''' 0123456789012345678901234567890123456789 @@ -335,3 +361,12 @@ needs to be wrapped. }); }); } + +class FakeStdio extends Stdio { + FakeStdio(); + + int currentColumnSize = 20; + + @override + int get terminalColumns => currentColumnSize; +}