NNBD i13n: Add line numbers
Change-Id: Ifd81b3e99a3e8c0a1b1a0b6dbe29ad545f6e50f3
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/121779
Reviewed-by: Paul Berry <paulberry@google.com>
Commit-Queue: Samuel Rawlins <srawlins@google.com>
diff --git a/pkg/analysis_server/lib/src/edit/nnbd_migration/instrumentation_renderer.dart b/pkg/analysis_server/lib/src/edit/nnbd_migration/instrumentation_renderer.dart
index 49ab61d..8caa0b1 100644
--- a/pkg/analysis_server/lib/src/edit/nnbd_migration/instrumentation_renderer.dart
+++ b/pkg/analysis_server/lib/src/edit/nnbd_migration/instrumentation_renderer.dart
@@ -2,6 +2,8 @@
// 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:convert' show htmlEscape, LineSplitter;
+
import 'package:analysis_server/src/edit/nnbd_migration/migration_info.dart';
import 'package:analysis_server/src/edit/nnbd_migration/offset_mapper.dart';
import 'package:analysis_server/src/edit/nnbd_migration/path_mapper.dart';
@@ -76,8 +78,9 @@
}
.code {
- position: absolute;
left: 0.5em;
+ padding-left: 60px;
+ position: absolute;
top: 0.5em;
}
@@ -97,6 +100,28 @@
visibility: hidden;
}
+.regions table {
+ border-spacing: 0;
+}
+
+.regions td {
+ border: none;
+ padding: 0;
+ white-space: pre;
+}
+
+.regions td:empty:after {
+ content: "\00a0";
+}
+
+.regions td.line-no {
+ color: #999999;
+ padding-right: 4px;
+ text-align: right;
+ visibility: visible;
+ width: 50px;
+}
+
.region {
/* Green means this region was added. */
background-color: #ccffcc;
@@ -159,25 +184,9 @@
'{{{ navContent }}}'
'</div>'
'<div class="regions">'
- '{{! The regions are then written again, overlaying the first two copies }}'
- '{{! of the content, to provide tooltips for modified regions. }}'
- '{{# regions }}'
- '{{^ modified }}{{ content }}{{/ modified }}'
- '{{# modified }}'
- '<span class="region">{{ content }}'
- '<span class="tooltip"><p>{{ explanation }}</p>'
- ' <ul>'
- ' {{# details }}'
- ' <li>'
- ' {{# isLink }}<a href="{{ target }}">{{ description }}</a>{{/ isLink }}'
- ' {{^ isLink }}{{ description }}{{/ isLink }}'
- ' </li>'
- ' {{/ details }}'
- ' </ul>'
- '</span>'
- '</span>'
- '{{/ modified }}'
- '{{/ regions }}'
+ '{{! The regions are then written again, overlaying the first copy of }}'
+ '{{! the content, to provide tooltips for modified regions. }}'
+ '{{{ regionContent }}}'
'</div></div>'
r'''
{{/ units }}
@@ -213,8 +222,6 @@
/// Builds an HTML view of the instrumentation information in [unitInfo].
String render() {
- // TODO(brianwilkerson) Restore syntactic highlighting.
- // TODO(brianwilkerson) Add line numbers beside the content.
Map<String, dynamic> mustacheContext = {
'units': <Map<String, dynamic>>[],
'links': migrationInfo.unitLinks(unitInfo),
@@ -224,7 +231,7 @@
};
mustacheContext['units'].add({
'path': unitInfo.path,
- 'regions': _computeRegions(unitInfo),
+ 'regionContent': _computeRegionContent(unitInfo),
});
return _template.renderString(mustacheContext);
}
@@ -291,53 +298,72 @@
return navContent2.toString();
}
- /// Return a list of Mustache context, based on the [unitInfo] for both
- /// unmodified and modified regions:
- ///
- /// * 'modified': Whether this region represents modified source, or
- /// unmodified.
- /// * 'content': The textual content of this region.
- /// * 'explanation': The Mustache context for the tooltip explaining why the
- /// content in this region was modified.
- List<Map> _computeRegions(UnitInfo unitInfo) {
+ /// Return the content of regions, based on the [unitInfo] for both
+ /// unmodified and modified regions.
+ String _computeRegionContent(UnitInfo unitInfo) {
String unitDir = _directoryContaining(unitInfo);
String content = unitInfo.content;
- List<Map> regions = [];
+ StringBuffer regions = StringBuffer();
+ int lineNumber = 1;
+
+ void writeSplitLines(String lines) {
+ Iterator<String> lineIterator = LineSplitter.split(lines).iterator;
+ lineIterator.moveNext();
+
+ while (true) {
+ regions.write(htmlEscape.convert(lineIterator.current));
+ if (lineIterator.moveNext()) {
+ // If we're not on the last element, end this table row, and start a
+ // new table row.
+ lineNumber++;
+ regions.write(
+ '</td></tr>' '<tr><td class="line-no">$lineNumber</td><td>');
+ } else {
+ break;
+ }
+ }
+ }
+
int previousOffset = 0;
+ regions.write('<table><tbody><tr><td class="line-no">$lineNumber</td><td>');
for (var region in unitInfo.regions) {
int offset = region.offset;
int length = region.length;
if (offset > previousOffset) {
// Display a region of unmodified content.
- regions.add({
- 'modified': false,
- 'content': content.substring(previousOffset, offset),
- });
+ writeSplitLines(content.substring(previousOffset, offset));
previousOffset = offset + length;
}
- List<Map> details = [];
- for (var detail in region.details) {
- details.add({
- 'description': detail.description,
- 'target': _uriForTarget(detail.target, unitDir),
- 'isLink': detail.target != null,
- });
+ regions.write('<span class="region">'
+ '${content.substring(offset, offset + length)}'
+ '<span class="tooltip">'
+ '<p>${region.explanation}</p>');
+ if (region.details.isNotEmpty) {
+ regions.write('<ul>');
}
- regions.add({
- 'modified': true,
- 'content': content.substring(offset, offset + length),
- 'explanation': region.explanation,
- 'details': details,
- });
+ for (var detail in region.details) {
+ regions.write('<li>');
+
+ if (detail.target != null) {
+ regions.write('<a href="${_uriForTarget(detail.target, unitDir)}">');
+ }
+ writeSplitLines(detail.description);
+ if (detail.target != null) {
+ regions.write('</a>');
+ }
+ regions.write('</li>');
+ }
+ if (region.details.isNotEmpty) {
+ regions.write('</ul>');
+ }
+ regions.write('</span></span>');
}
if (previousOffset < content.length) {
// Last region of unmodified content.
- regions.add({
- 'modified': false,
- 'content': content.substring(previousOffset),
- });
+ writeSplitLines(content.substring(previousOffset));
}
- return regions;
+ regions.write('</td></tr></tbody></table>');
+ return regions.toString();
}
/// Return the path to the directory containing the output generated from the
diff --git a/pkg/analysis_server/test/edit/nnbd_migration/instrumentation_output_test.dart b/pkg/analysis_server/test/edit/nnbd_migration/instrumentation_output_test.dart
index 012e168..87fed8c 100644
--- a/pkg/analysis_server/test/edit/nnbd_migration/instrumentation_output_test.dart
+++ b/pkg/analysis_server/test/edit/nnbd_migration/instrumentation_output_test.dart
@@ -63,7 +63,7 @@
output,
contains('List<String><span class="region">?'
'<span class="tooltip"><p>null was assigned</p>'
- ' <ul> </ul></span></span> a = null;'));
+ '</span></span> a = null;'));
}
test_outputContainsEscapedHtml_ampersand() async {
@@ -84,7 +84,7 @@
output,
contains('int<span class="region">?'
'<span class="tooltip"><p>null was assigned</p>'
- ' <ul> </ul></span></span> a = null;'));
+ '</span></span> a = null;'));
}
UnitInfo unit(String path, String content, {List<RegionInfo> regions}) {