forked from refined-github/refined-github
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathresolve-conflicts.ts
More file actions
128 lines (110 loc) · 3.85 KB
/
resolve-conflicts.ts
File metadata and controls
128 lines (110 loc) · 3.85 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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import {regexJoinWithSeparator} from 'regex-join';
import type CodeMirror from 'codemirror';
declare module 'codemirror' {
interface LineHandle {
widgets: unknown[];
lineNo: () => number;
}
}
const editor = document.querySelector<Element & {CodeMirror: CodeMirror.Editor}>('.CodeMirror')!.CodeMirror;
// Event fired when each file is loaded
editor.on('swapDoc', () => setTimeout(addWidget, 1));
// Restore widget on undo
editor.on('changes', (_, [firstChange]) => {
if (firstChange.origin === 'undo' && firstChange.text[0].startsWith('<<<<<<<')) {
addWidget();
// Reset cursor position to one line instead of multiple
editor.setCursor(editor.getCursor());
}
});
function getLineNumber(lineChild: Element): number {
return Number(
lineChild
.closest(['.CodeMirror-gutter-wrapper', '.CodeMirror-linewidget'])!
.parentElement!
.querySelector('.CodeMirror-linenumber')!
.textContent,
) - 1;
}
function appendLineInfo(lineHandle: CodeMirror.LineHandle, text: string): void {
// Only append text if it's not already there
if (!lineHandle.text.includes(text)) {
const line = lineHandle.lineNo();
editor.replaceRange(text, {line, ch: Number.POSITIVE_INFINITY}); // Infinity = end of line
editor.clearHistory();
}
}
// Create and add widget if not already in the document
function addWidget(): void {
editor.eachLine(lineHandle => {
if (Array.isArray(lineHandle.widgets) && lineHandle.widgets.length > 0) {
return;
}
if (lineHandle.text.startsWith('<<<<<<<')) {
appendLineInfo(lineHandle, ' -- Incoming Change');
const line = lineHandle.lineNo();
editor.addLineClass(line, '', 'rgh-resolve-conflicts');
editor.addLineWidget(line, newWidget(), {
above: true,
noHScroll: true,
});
} else if (lineHandle.text.startsWith('>>>>>>>')) {
appendLineInfo(lineHandle, ' -- Current Change');
}
});
}
function createButton(branch: string, title = `Accept ${branch} Change`): HTMLButtonElement {
const link = document.createElement('button');
link.type = 'button';
link.className = 'btn-link';
link.textContent = title;
link.addEventListener('click', ({target}) => {
acceptBranch(branch, getLineNumber(target as Element));
});
return link;
}
// Create and return conflict resolve widget for specific conflict
function newWidget(): HTMLDivElement {
const widget = document.createElement('div');
widget.style.fontWeight = 'bold';
widget.append(
createButton('Current'),
' | ',
createButton('Incoming'),
' | ',
createButton('Both', 'Accept Both Changes'),
);
return widget;
}
const currentChange = /^>>>>>>> .+ -- Current Change$/;
const incomingChange = /^<<<<<<< .+ -- Incoming Change$/;
const middle = /^=======$/;
const anyMarker = regexJoinWithSeparator('|', [currentChange, incomingChange, middle]);
// Accept one or both of branches and remove unnecessary lines
function acceptBranch(branch: string, line: number): void {
// This variable is only changed when a marker is encountered and is meant to stay positive/negative until the next marker
let inDeletableSection = false;
const linesToRemove: number[] = [];
editor.eachLine(line, Number.POSITIVE_INFINITY, lineHandle => {
const currentLine = lineHandle.text;
// Determine whether to remove the following line(s)
if (incomingChange.test(currentLine)) {
inDeletableSection = branch === 'Current';
} else if (currentLine === '=======') {
inDeletableSection = branch === 'Incoming';
}
// Delete tracked lines and all conflict markers
if (inDeletableSection || anyMarker.test(currentLine)) {
linesToRemove.push(lineHandle.lineNo());
}
return currentChange.test(currentLine); // `true` ends loop
});
// Delete all lines at once in a performant way
const ranges = linesToRemove.map(line => ({
anchor: {line, ch: 0},
head: {line, ch: 0},
}));
editor.setSelections(ranges);
editor.execCommand('deleteLine');
editor.setCursor(linesToRemove[0]);
}