Skip to content

Commit 6451503

Browse files
pouryafard75tsantalis
authored andcommitted
ASTDiff Gui: Further improvements on highlighting
1 parent 0c5b66c commit 6451503

File tree

7 files changed

+313
-320
lines changed

7 files changed

+313
-320
lines changed

src/main/java/gui/webdiff/viewers/monaco/MonacoCore.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import org.rendersnake.HtmlCanvas;
1111

1212
import java.io.IOException;
13+
import java.util.Set;
1314

1415
import static org.rendersnake.HtmlAttributesFactory.*;
1516

@@ -191,7 +192,6 @@ String getRightJsConfig() {
191192
String getMappingsJsConfig() {
192193
if (diff instanceof ASTDiff) {
193194
ASTDiff astDiff = (ASTDiff) diff;
194-
MappingStore monoMappingStore = astDiff.getAllMappings().getMonoMappingStore();
195195
ExtendedTreeClassifier c = (ExtendedTreeClassifier) diff.createRootNodesClassifier();
196196
StringBuilder b = new StringBuilder();
197197
b.append("[");
@@ -201,12 +201,13 @@ String getMappingsJsConfig() {
201201
b.append(String.format("[%s, %s, %s, %s], ", t.getPos(), t.getEndPos(), d.getPos(), d.getEndPos()));
202202
}
203203
else {
204-
if (monoMappingStore.isSrcMapped(t)) {
204+
Set<Tree> dsts = astDiff.getAllMappings().getDsts(t);
205+
for (Tree dst : dsts) {
205206
b.append(String.format("[%s, %s, %s, %s], ",
206207
t.getPos(),
207208
t.getEndPos(),
208-
monoMappingStore.getDstForSrc(t).getPos(),
209-
monoMappingStore.getDstForSrc(t).getEndPos()));
209+
dst.getPos(),
210+
dst.getEndPos()));
210211
}
211212
}
212213
}

src/main/java/gui/webdiff/viewers/monaco/MonacoView.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ public void renderOn(HtmlCanvas html) throws IOException {
7878
// .macros().stylesheet(JQ_UI_CSS)
7979
.macros().javascript(WebDiff.BOOTSTRAP_JS_URL)
8080
.macros().javascript("/monaco/min/vs/loader.js")
81+
.macros().javascript("/dist/utils.js")
82+
.macros().javascript("/dist/folding.js")
83+
.macros().javascript("/dist/decorations.js")
8184
.macros().javascript("/dist/monaco.js")
8285
._head();
8386
}

src/main/java/gui/webdiff/viewers/spv/SinglePageView.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ protected void makeHead(HtmlCanvas html) throws IOException {
2121
.meta(name("viewport").content("width=device-width, initial-scale=1.0"))
2222
.macros().stylesheet("/dist/single.css")
2323
.macros().stylesheet("/dist/monaco.css")
24+
.macros().javascript("/dist/utils.js")
25+
.macros().javascript("/dist/folding.js")
26+
.macros().javascript("/dist/decorations.js")
2427
.macros().javascript("/dist/monaco.js")
2528
.macros().javascript("/monaco/min/vs/loader.js")
2629
._head();
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
function getDecorationNoLeadingWhiteSpace(range, pos, endPos, editor) {
2+
const decorations = [];
3+
// Helper function to get adjusted column position by skipping leading whitespace
4+
function getAdjustedPos(lineNumber, column) {
5+
const lineContent = editor.getModel().getLineContent(lineNumber);
6+
const trimmedStartColumn = lineContent.search(/\S|$/) + 1;
7+
return Math.max(column, trimmedStartColumn);
8+
}
9+
if (pos.lineNumber === endPos.lineNumber) {
10+
// Single-line decoration
11+
const adjustedPos = {
12+
lineNumber: pos.lineNumber,
13+
column: getAdjustedPos(pos.lineNumber, pos.column),
14+
};
15+
decorations.push({
16+
range: new monaco.Range(adjustedPos.lineNumber, adjustedPos.column, endPos.lineNumber, endPos.column),
17+
options: {
18+
className: range.kind,
19+
zIndex: range.index,
20+
hoverMessage: {
21+
value: range.tooltip,
22+
},
23+
overviewRuler: {
24+
color: getEditColor(range.kind),
25+
},
26+
},
27+
});
28+
}
29+
else {
30+
// Multi-line decoration
31+
// Decorate the first line
32+
const adjustedStartPos = {
33+
lineNumber: pos.lineNumber,
34+
column: getAdjustedPos(pos.lineNumber, pos.column),
35+
};
36+
decorations.push({
37+
range: new monaco.Range(adjustedStartPos.lineNumber, adjustedStartPos.column, pos.lineNumber, editor.getModel().getLineMaxColumn(pos.lineNumber)),
38+
options: {
39+
className: range.kind,
40+
zIndex: range.index,
41+
hoverMessage: {
42+
value: range.tooltip,
43+
},
44+
overviewRuler: {
45+
color: getEditColor(range.kind),
46+
},
47+
},
48+
});
49+
// Decorate each line in between, avoiding leading whitespace
50+
for (let line = pos.lineNumber + 1; line < endPos.lineNumber; line++) {
51+
const adjustedColumn = getAdjustedPos(line, 1); // Adjust for leading whitespace
52+
decorations.push({
53+
range: new monaco.Range(line, adjustedColumn, line, editor.getModel().getLineMaxColumn(line)),
54+
options: {
55+
className: range.kind,
56+
zIndex: range.index,
57+
hoverMessage: {
58+
value: range.tooltip,
59+
},
60+
overviewRuler: {
61+
color: getEditColor(range.kind),
62+
},
63+
},
64+
});
65+
}
66+
// Decorate the last line, skipping leading whitespace
67+
const adjustedEndPos = {
68+
lineNumber: endPos.lineNumber,
69+
column: getAdjustedPos(endPos.lineNumber, 1),
70+
};
71+
decorations.push({
72+
range: new monaco.Range(endPos.lineNumber, adjustedEndPos.column, endPos.lineNumber, endPos.column),
73+
options: {
74+
className: range.kind,
75+
zIndex: range.index,
76+
hoverMessage: {
77+
value: range.tooltip,
78+
},
79+
overviewRuler: {
80+
color: getEditColor(range.kind),
81+
},
82+
},
83+
});
84+
}
85+
// return [];
86+
return decorations;
87+
}
88+
function getDecoration(range, pos, endPos) {
89+
return {
90+
range: new monaco.Range(pos.lineNumber, pos.column, endPos.lineNumber, endPos.column),
91+
options: {
92+
className: range.kind,
93+
zIndex: range.index,
94+
hoverMessage: {
95+
value: range.tooltip,
96+
},
97+
overviewRuler: {
98+
color: getEditColor(range.kind),
99+
},
100+
},
101+
};
102+
}
103+
function onClick(ed, mappings, dstIndex) {
104+
mapping = mappings[0]
105+
ed.revealRangeInCenter(mapping[dstIndex]);
106+
107+
highlightDuration = 1000
108+
109+
for (let i = 0; i < mappings.length; i++) {
110+
currentMapping = mappings[i];
111+
mappingElement = currentMapping[dstIndex]
112+
const decorationId = ed.deltaDecorations([], [
113+
{
114+
range: mappingElement,
115+
options: {
116+
className: 'highlighted-range',
117+
inlineClassName: 'highlighted-range-inline',
118+
}
119+
}
120+
]);
121+
setTimeout(() => {
122+
ed.deltaDecorations([decorationId], []); // Remove the decoration
123+
}, highlightDuration);
124+
}
125+
}
126+
function onClickHelper(config, index, activatedRange, ed, dstIndex) {
127+
candidates = config.mappings
128+
.filter(mapping =>
129+
mapping[index].startColumn <= activatedRange.startColumn
130+
&& mapping[index].startLineNumber <= activatedRange.startLineNumber
131+
&& !(mapping[index].endLineNumber === activatedRange.endLineNumber &&
132+
mapping[index].endColumn <= activatedRange.endColumn)
133+
);
134+
candidates = candidates.filter(candidate => candidate[index].containsRange(activatedRange))
135+
candidates
136+
.sort((a, b) => {
137+
if (b[index].startLineNumber !== a[index].startLineNumber) {
138+
return b[index].startLineNumber - a[index].startLineNumber;
139+
}
140+
if (b[index].startColumn !== a[index].startColumn) {
141+
return b[index].startColumn - a[index].startColumn;
142+
}
143+
if (a[index].endLineNumber !== b[index].endLineNumber) {
144+
return a[index].endLineNumber - b[index].endLineNumber;
145+
}
146+
return a[index].endColumn - b[index].endColumn;
147+
});
148+
// Find all candidates that tie with the first candidate (in case of multi-mapping
149+
const mappings = candidates.filter(candidate =>
150+
candidate[index].startLineNumber === candidates[0][index].startLineNumber &&
151+
candidate[index].startColumn === candidates[0][index].startColumn &&
152+
candidate[index].endLineNumber === candidates[0][index].endLineNumber &&
153+
candidate[index].endColumn === candidates[0][index].endColumn
154+
);
155+
if (mappings) {
156+
if (mappings.length >= 1) {
157+
onClick(ed, mappings, dstIndex);
158+
}
159+
}
160+
}
161+
function editorMouseDown(config, srcEditor, dstEditor, index, destIndex) {
162+
return (event) => {
163+
if (event.target.range) {
164+
const allDecorations = srcEditor.getModel().getDecorationsInRange(event.target.range, srcEditor.id, true)
165+
if (allDecorations.length >= 1) {
166+
let activatedRange = allDecorations[0].range;
167+
if (allDecorations.length > 1) {
168+
for (let i = 1; i < allDecorations.length; i = i + 1) {
169+
const candidateRange = allDecorations[i].range;
170+
if (activatedRange.containsRange(candidateRange))
171+
activatedRange = candidateRange;
172+
}
173+
}
174+
onClickHelper(config, index, activatedRange, dstEditor, destIndex);
175+
}
176+
}
177+
};
178+
}
179+
function deltaDecorations(ed, dec) {
180+
ed.getModel().decorations = dec;
181+
ed.deltaDecorations([], dec);
182+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
function getLinesToFold(config, model, margin = 5) {
2+
3+
decorations = model.decorations;
4+
const lineCount = model.getLineCount();
5+
const linesToKeepVisible = new Set();
6+
7+
decorations.forEach(decoration => {
8+
if (model.moved){
9+
if (decoration.options.className === "inserted" || decoration.options.className === "deleted")
10+
{
11+
return;
12+
}
13+
}
14+
for (let line = Math.max(1, decoration.range.startLineNumber - margin);
15+
line <= Math.min(lineCount, decoration.range.endLineNumber + margin);
16+
line++) {
17+
linesToKeepVisible.add(line);
18+
}
19+
});
20+
21+
const linesToFold = [];
22+
for (let line = 1; line <= lineCount; line++) {
23+
if (!linesToKeepVisible.has(line)) {
24+
linesToFold.push(line);
25+
}
26+
}
27+
return linesToFold;
28+
}
29+
function createFoldingRanges(linesToFold) {
30+
// Ensure linesToFold is sorted
31+
linesToFold.sort((a, b) => a - b);
32+
33+
const ranges = [];
34+
let start = linesToFold[0];
35+
let end = linesToFold[0];
36+
37+
for (let i = 1; i < linesToFold.length; i++) {
38+
if (linesToFold[i] === end + 1) {
39+
// Continue the current range
40+
end = linesToFold[i];
41+
} else {
42+
// Push the completed range
43+
ranges.push(new monaco.Range(start, 1, end, 1));
44+
// Start a new range
45+
start = linesToFold[i];
46+
end = linesToFold[i];
47+
}
48+
}
49+
// Push the last range
50+
ranges.push(new monaco.Range(start, 1, end, 1));
51+
return ranges;
52+
}
53+
function setAllFoldings(config, leftEditor, rightEditor) {
54+
const foldingRangeProvider = {
55+
provideFoldingRanges: (model, context, token) => {
56+
if (model.id !== config.id) return;
57+
let rangesToFold = createFoldingRanges(getLinesToFold(config, model, 2));
58+
return rangesToFold.map(range => ({
59+
start: range.startLineNumber,
60+
end: range.endLineNumber,
61+
kind: monaco.languages.FoldingRangeKind.Region,
62+
}));
63+
},
64+
};
65+
//Assume both languages are the same
66+
var languageId = rightEditor.getModel().getLanguageId();
67+
if (!monaco.languages.getLanguages().some(lang => lang.id === languageId && lang.foldingRangeProvider)) {
68+
monaco.languages.registerFoldingRangeProvider(languageId, foldingRangeProvider);
69+
}
70+
}

0 commit comments

Comments
 (0)