Skip to content

Commit eb4a437

Browse files
Enhancement: Render tabs and spaces in pretty HTML previews. (#15)
Previously the `prettyHtml` functions made some changes to diff content to better represent special characters, but not exhaustively. For instance, it escapes `<` as `&lt;` so that the HTML preview interprets the content as plaintext and not as HTML. Similarly, it replaces the LINE FEED character with `&para;<br>` so that when interpreted by a browser, the HTML will visually correspond to the newline (whereas HTML parsers would normalize the whitespace and not show an actual newline). This patch extends the existing transformation to represent horizontal tabs with an `&emsp;` EM SPACE and a space character with `&nbsp;` to further highlight changes to these whitespace characters, rather than having them blend in via whitespace normalization. Co-authored-by: Dennis Snell <[email protected]>
1 parent c32b811 commit eb4a437

File tree

10 files changed

+29
-10
lines changed

10 files changed

+29
-10
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Next version
22

3+
- Horizontal tab and space characters are rendered in `prettyHtml` via HTML
4+
character references to visually show the changed characters.
5+
36
## Java
47

58
- `Diff_Timeout` is now compared using a monotonically increasing high-resolution

cpp/diff_match_patch.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1276,7 +1276,8 @@ QString diff_match_patch::diff_prettyHtml(const QList<Diff> &diffs) {
12761276
foreach(Diff aDiff, diffs) {
12771277
text = aDiff.text;
12781278
text.replace("&", "&amp;").replace("<", "&lt;")
1279-
.replace(">", "&gt;").replace("\n", "&para;<br>");
1279+
.replace(">", "&gt;").replace("\n", "&para;<br>")
1280+
.replace("\t", "&emsp;").replace(" ", "&nbsp;");
12801281
switch (aDiff.operation) {
12811282
case INSERT:
12821283
html += QString("<ins style=\"background:#e6ffe6;\">") + text

csharp/DiffMatchPatch.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1338,7 +1338,7 @@ public string diff_prettyHtml(List<Diff> diffs) {
13381338
StringBuilder html = new StringBuilder();
13391339
foreach (Diff aDiff in diffs) {
13401340
string text = aDiff.text.Replace("&", "&amp;").Replace("<", "&lt;")
1341-
.Replace(">", "&gt;").Replace("\n", "&para;<br>");
1341+
.Replace(">", "&gt;").Replace("\n", "&para;<br>").Replace("\t", "&emsp;").Replace(" ", "&nbsp;");
13421342
switch (aDiff.operation) {
13431343
case Operation.INSERT:
13441344
html.Append("<ins style=\"background:#e6ffe6;\">").Append(text)

dart/DMPClass.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1256,7 +1256,9 @@ class DiffMatchPatch {
12561256
.replaceAll('&', '&amp;')
12571257
.replaceAll('<', '&lt;')
12581258
.replaceAll('>', '&gt;')
1259-
.replaceAll('\n', '&para;<br>');
1259+
.replaceAll('\n', '&para;<br>')
1260+
.replaceAll('\t', '&emsp;')
1261+
.replaceAll(' ', '&nbsp;');
12601262
switch (aDiff.operation) {
12611263
case Operation.insert:
12621264
html.write('<ins style="background:#e6ffe6;">');

java/src/name/fraser/neil/plaintext/diff_match_patch.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1382,7 +1382,8 @@ public String diff_prettyHtml(List<Diff> diffs) {
13821382
StringBuilder html = new StringBuilder();
13831383
for (Diff aDiff : diffs) {
13841384
String text = aDiff.text.replace("&", "&amp;").replace("<", "&lt;")
1385-
.replace(">", "&gt;").replace("\n", "&para;<br>");
1385+
.replace(">", "&gt;").replace("\n", "&para;<br>")
1386+
.replace("\t", "&emsp;").replace(" ", "&nbsp;");
13861387
switch (aDiff.operation) {
13871388
case INSERT:
13881389
html.append("<ins style=\"background:#e6ffe6;\">").append(text)

javascript/index.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1430,11 +1430,18 @@ diff_match_patch.prototype.diff_prettyHtml = function(diffs) {
14301430
var pattern_lt = /</g;
14311431
var pattern_gt = />/g;
14321432
var pattern_para = /\n/g;
1433+
var pattern_tab = /\t/g;
1434+
var pattern_space = / /g;
14331435
for (var x = 0; x < diffs.length; x++) {
14341436
var op = diffs[x][0]; // Operation (insert, delete, equal)
14351437
var data = diffs[x][1]; // Text of change.
1436-
var text = data.replace(pattern_amp, '&amp;').replace(pattern_lt, '&lt;')
1437-
.replace(pattern_gt, '&gt;').replace(pattern_para, '&para;<br>');
1438+
var text = data
1439+
.replace(pattern_amp, '&amp;')
1440+
.replace(pattern_lt, '&lt;')
1441+
.replace(pattern_gt, '&gt;')
1442+
.replace(pattern_para, '&para;<br>')
1443+
.replace(pattern_tab, '&emsp;')
1444+
.replace(pattern_space, '&nbsp;');
14381445
switch (op) {
14391446
case DIFF_INSERT:
14401447
html[x] = '<ins style="background:#e6ffe6;">' + text + '</ins>';

lua/diff_match_patch.lua

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,10 @@ local function indexOf(a, b, start)
5858
return strfind(a, b, start, true)
5959
end
6060

61-
local htmlEncode_pattern = '[&<>\n]'
61+
local htmlEncode_pattern = '[&<>\n\t ]'
6262
local htmlEncode_replace = {
63-
['&'] = '&amp;', ['<'] = '&lt;', ['>'] = '&gt;', ['\n'] = '&para;<br>'
63+
['&'] = '&amp;', ['<'] = '&lt;', ['>'] = '&gt;', ['\n'] = '&para;<br>',
64+
['\t'] = '&emsp;', [' '] = '&nbsp;'
6465
}
6566

6667
-- Public API Functions

objectivec/DiffMatchPatch.m

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1239,6 +1239,8 @@ - (NSString *)diff_prettyHtml:(NSMutableArray *)diffs;
12391239
[text replaceOccurrencesOfString:@"<" withString:@"&lt;" options:NSLiteralSearch range:NSMakeRange(0, text.length)];
12401240
[text replaceOccurrencesOfString:@">" withString:@"&gt;" options:NSLiteralSearch range:NSMakeRange(0, text.length)];
12411241
[text replaceOccurrencesOfString:@"\n" withString:@"&para;<br>" options:NSLiteralSearch range:NSMakeRange(0, text.length)];
1242+
[text replaceOccurrencesOfString:@"\t" withString:@"&emsp;" options:NSLiteralSearch range:NSMakeRange(0, text.length)];
1243+
[text replaceOccurrencesOfString:@" " withString:@"&nbsp;" options:NSLiteralSearch range:NSMakeRange(0, text.length)];
12421244

12431245
switch (aDiff.operation) {
12441246
case DIFF_INSERT:

python2/diff_match_patch.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1071,7 +1071,8 @@ def diff_prettyHtml(self, diffs):
10711071
html = []
10721072
for (op, data) in diffs:
10731073
text = (data.replace("&", "&amp;").replace("<", "&lt;")
1074-
.replace(">", "&gt;").replace("\n", "&para;<br>"))
1074+
.replace(">", "&gt;").replace("\n", "&para;<br>")
1075+
.replace("\t", "&emsp;").replace(" ", "&nbsp;"))
10751076
if op == self.DIFF_INSERT:
10761077
html.append("<ins style=\"background:#e6ffe6;\">%s</ins>" % text)
10771078
elif op == self.DIFF_DELETE:

python3/diff_match_patch.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1069,7 +1069,8 @@ def diff_prettyHtml(self, diffs):
10691069
html = []
10701070
for (op, data) in diffs:
10711071
text = (data.replace("&", "&amp;").replace("<", "&lt;")
1072-
.replace(">", "&gt;").replace("\n", "&para;<br>"))
1072+
.replace(">", "&gt;").replace("\n", "&para;<br>")
1073+
.replace("\t", "&emsp;").replace(" ", "&nbsp;"))
10731074
if op == self.DIFF_INSERT:
10741075
html.append("<ins style=\"background:#e6ffe6;\">%s</ins>" % text)
10751076
elif op == self.DIFF_DELETE:

0 commit comments

Comments
 (0)