-
Notifications
You must be signed in to change notification settings - Fork 1
/
diffAnnotator.ts
113 lines (91 loc) · 3.92 KB
/
diffAnnotator.ts
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
110
111
112
113
import * as vscode from 'vscode';
import { delDecorationType, mediaUri } from './extension';
import Patch from './patch';
function range (start: number, end: number) { return [...Array(1+end-start).keys()].map(v => start+v) }
function endOfDocument(document: vscode.TextDocument) : vscode.Position {
return document.lineAt(document.lineCount - 1).rangeIncludingLineBreak.end;
}
export default class DiffAnnotator {
public styleUri: vscode.Uri;
public editorRef: vscode.TextEditor;
public currentDiff: Patch[] = [];
public insets: vscode.WebviewEditorInset[] = [];
constructor(styleUri: vscode.Uri, editorRef: vscode.TextEditor) {
this.styleUri = styleUri;
this.editorRef = editorRef;
}
public async discardCurrentDiff() {
this.currentDiff = [];
this.editorRef.setDecorations(delDecorationType, []);
// TODO Animate
}
public async applyCurrentDiff() {
if (!this.currentDiff) {
return;
}
let doc = this.editorRef.document;
let edits: [vscode.Range, string][] = [];
for (let patch of this.currentDiff) {
// TODO Should probably be eol sensitive...
let replacement = patch.addCount > 0 ? patch.addedLines.join("\n") + "\n" : "";
// If we need to insert at the end of the file, just use the last line's end
let removeStart = doc.lineCount === patch.oldFilePos ? endOfDocument(doc) : doc.lineAt(patch.oldFilePos).rangeIncludingLineBreak.start;
if (patch.delCount > 0) {
// Use the start of the next line if available, otherwise just delete till the end.
let removeEnd = doc.lineAt(patch.oldFilePos + patch.delCount - 1).rangeIncludingLineBreak.end;
edits.push([new vscode.Range(removeStart, removeEnd), replacement]);
}
else {
edits.push([new vscode.Range(removeStart, removeStart), replacement]);
}
}
await this.editorRef.edit(editBuilder => {
for (let [range, replacement] of edits) {
editBuilder.replace(range, replacement);
}
});
this.currentDiff = [];
// TODO Animate Away
this.editorRef.setDecorations(delDecorationType, []);
// Will auto-remove themselves on discard.
this.insets.forEach(x => x.dispose());
this.insets = [];
}
public async setCurrentDiff(patches: Patch[]) {
if (this.currentDiff) {
this.discardCurrentDiff();
}
this.currentDiff = patches;
// Insert annotations
const dels: vscode.Range[] = [];
for (let patch of patches) {
if (patch.delCount > 0) {
dels.push(new vscode.Range(
this.editorRef.document.lineAt(patch.oldFilePos).range.start,
this.editorRef.document.lineAt(patch.oldFilePos + patch.delCount - 1).range.end
));
}
if (patch.addCount > 0) {
const inset = vscode.window.createWebviewTextEditorInset(this.editorRef, patch.oldFilePos, patch.addCount, {
// At least in this function, localResourceRoots defaults to 'no access to anything', as far as I can tell.
// This fixes the "asWebviewUri" call.
localResourceRoots: [mediaUri]
});
const myStyle = inset.webview.asWebviewUri(this.styleUri);
inset.webview.html = `
<!DOCTYPE html>
<html>
<head>
<link href="${myStyle}" rel="stylesheet" />
</head>
<body>
${patch.addedLines.join("<br/>")}
</body>
</html>
`;
this.insets.push(inset);
}
}
this.editorRef.setDecorations(delDecorationType, dels);
}
}