blob: 5ad801809e1a3e11eba6053031fa157ae1e581c3 [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 'package:cli_util/cli_logging.dart';
import 'package:meta/meta.dart';
import 'package:nnbd_migration/src/edit_plan.dart';
import 'package:nnbd_migration/src/utilities/source_edit_diff_output.dart';
/// Information about the style in which we display diffs.
abstract class DiffStyle {
/// Creates a DiffStyle suitable for use with a terminal whose ANSI support is
/// indicated by [ansi].
///
/// If the terminal supports ANSI, we use a compact diff style with intra-line
/// diffs, where insertions are represented by green reversed text and
/// deletions are represented by red reversed text.
///
/// If the terminal does not support ANSI, we use a traditional diff style
/// with before and after lines.
factory DiffStyle(Ansi ansi) = _DiffStyleForProduction;
/// Creates a DiffStyle suitable for unit testing. Instead of using ANSI
/// escape codes for color coding, we use `{+` and `+}` to surround added
/// text, and `{-` and `-}` to surround deleted text.
@visibleForTesting
factory DiffStyle.forTesting(bool compact) = _DiffStyleForTesting;
DiffStyle._();
/// Formats a diff showing the result of applying the given [edits] to the
/// given [origText].
List<String> formatDiff(String origText, Map<int, List<AtomicEdit>> edits);
}
abstract class DiffStyleImpl extends DiffStyle {
DiffStyleImpl._() : super._();
String get bullet;
String deleted(String text);
@override
List<String> formatDiff(String origText, Map<int, List<AtomicEdit>> edits) {
var output = _createOutput();
int prevOffset = 0;
for (var offset in edits.keys.toList()..sort()) {
if (offset > prevOffset) {
var text = origText.substring(prevOffset, offset);
var splitText = text.split('\n');
output.addUnchangedText(splitText.first);
if (splitText.length > 1) {
output.skipUnchangedNewlines(splitText.length - 1);
output.addUnchangedText(splitText.last);
}
prevOffset = offset;
}
for (var edit in edits[offset]!) {
if (edit.length > 0) {
var offset = prevOffset + edit.length;
var text = origText.substring(prevOffset, offset);
var splitText = text.split('\n');
output.addDeletedText(splitText.first);
for (int i = 1; i < splitText.length; i++) {
output.addDeletedNewline();
output.addDeletedText(splitText[i]);
}
prevOffset = offset;
}
if (edit.replacement.isNotEmpty) {
var splitText = edit.replacement.split('\n');
output.addInsertedText(splitText.first);
for (int i = 1; i < splitText.length; i++) {
output.addInsertedNewline();
output.addInsertedText(splitText[i]);
}
}
}
}
var text = origText.substring(prevOffset);
var splitText = text.split('\n');
output.addUnchangedText(splitText.first);
output.skipUnchangedNewlines(splitText.length - 1);
if (splitText.length > 1) {
output.addUnchangedText(splitText.last);
}
return output.finish();
}
String inserted(String text);
String lineHeader(int? lineNum, String separator) {
const String emptyLineHeader = ' ';
if (lineNum == null) {
return emptyLineHeader + separator;
} else {
var text = 'line $lineNum';
if (text.length < emptyLineHeader.length) {
text = text + ' ' * (emptyLineHeader.length - text.length);
}
return text + separator;
}
}
String unchanged(String text);
SourceEditDiffOutput _createOutput();
}
/// Implementation of [DiffStyle] used in production.
class _DiffStyleForProduction extends DiffStyleImpl {
final Ansi _ansi;
_DiffStyleForProduction(this._ansi) : super._();
@override
String get bullet => '•';
@override
String deleted(String text) =>
'${_ansi.red}${_ansi.reversed}$text${_ansi.none}';
@override
String inserted(String text) =>
'${_ansi.green}${_ansi.reversed}$text${_ansi.none}';
@override
String unchanged(String text) => text;
@override
SourceEditDiffOutput _createOutput() =>
_ansi.useAnsi ? CompactOutput(this) : TraditionalOutput(this);
}
/// Implementation of [DiffStyle] used in unit tests. Instead of using ANSI
/// escape sequences and unicode characters, we use characters easy to type into
/// unit tests.
class _DiffStyleForTesting extends DiffStyleImpl {
final bool _compact;
_DiffStyleForTesting(this._compact) : super._();
@override
String get bullet => '*';
@override
String deleted(String text) => '{-$text-}';
@override
String inserted(String text) => '{+$text+}';
@override
String unchanged(String text) => text;
@override
SourceEditDiffOutput _createOutput() =>
_compact ? CompactOutput(this) : TraditionalOutput(this);
}