-
Notifications
You must be signed in to change notification settings - Fork 49
Expand file tree
/
Copy pathhighlightChangesInLine.ts
More file actions
109 lines (98 loc) · 3.02 KB
/
highlightChangesInLine.ts
File metadata and controls
109 lines (98 loc) · 3.02 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import { Change, diffWords } from 'diff';
import { Context } from './context';
import { FormattedString } from './formattedString';
import { ThemeColor } from './themes';
import { zip } from './zip';
const HIGHLIGHT_CHANGE_RATIO = 1.0;
/**
* Given a pair of lines from a diff, returns more granular changes to the
* lines. Attempts to only return changes that are useful. The current heuristic
* is to only show granular changes if the ratio of change to unchanged parts in
* the line is below a threshold, otherwise the lines have changed substantially
* enough for the granular diffs to not be useful.
*/
function getChangesInLine(
context: Context,
lineA: string | null,
lineB: string | null
): Change[] | null {
const { HIGHLIGHT_LINE_CHANGES } = context;
if (!HIGHLIGHT_LINE_CHANGES || lineA === null || lineB === null) {
return null;
}
// Drop the prefix
const lineTextA = lineA.slice(1);
const lineTextB = lineB.slice(1);
const changes = diffWords(lineTextA, lineTextB, {
ignoreCase: false,
ignoreWhitespace: false,
});
// Count how many words changed vs total words. Note that a replacement gets
// double counted.
let changedWords = 0;
let totalWords = 0;
for (const { added, removed, count } of changes) {
if (added || removed) {
changedWords += count ?? 0;
} else {
totalWords += count ?? 0;
}
}
if (changedWords > totalWords * HIGHLIGHT_CHANGE_RATIO) {
return null;
}
return changes;
}
export function getChangesInLines(
context: Context,
linesA: (string | null)[],
linesB: (string | null)[]
): Array<Change[] | null> {
const changes = [];
for (const [lineA, lineB] of zip(linesA, linesB)) {
changes.push(getChangesInLine(context, lineA ?? null, lineB ?? null));
}
return changes;
}
export function highlightChangesInLine(
context: Context,
linePrefix: string,
formattedLine: FormattedString,
changes: Change[] | null
): void {
if (!changes) {
return;
}
const { DELETED_WORD_COLOR, INSERTED_WORD_COLOR, UNMODIFIED_LINE_COLOR } =
context;
let wordColor: ThemeColor;
switch (linePrefix) {
case '-':
wordColor = DELETED_WORD_COLOR;
break;
case '+':
wordColor = INSERTED_WORD_COLOR;
break;
default:
wordColor = UNMODIFIED_LINE_COLOR; // This is actually not used
break;
}
let lineIndex = 0;
for (const change of changes) {
// Skip changes that would not be present in the line
if (change.removed && linePrefix === '+') {
continue;
}
if (change.added && linePrefix === '-') {
continue;
}
if (change.removed || change.added) {
formattedLine.addSpan(
lineIndex,
lineIndex + change.value.length,
wordColor
);
}
lineIndex += change.value.length;
}
}