Skip to content

Commit b45e94c

Browse files
authored
Feat: Add scroll diff decorators (#5807)
* add diff decorators for split view * better offset calculation for decorators; separate ScrollDiffDecorator * improve diff scroll decorators placement * determine scroll decorator zone width by diff editor type * add tests for scroll diff decorators * fix: decorators initialization * fix: return decorators state on dispose * fix: make sure decorators restored after dispose * fix tests after merge conflicts
1 parent 35e1be5 commit b45e94c

File tree

9 files changed

+631
-103
lines changed

9 files changed

+631
-103
lines changed

src/ext/diff/base_diff_view.js

Lines changed: 105 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@ var Range = require("../../range").Range;
55
var dom = require("../../lib/dom");
66
var config = require("../../config");
77
var LineWidgets = require("../../line_widgets").LineWidgets;
8+
var ScrollDiffDecorator = require("./scroll_diff_decorator").ScrollDiffDecorator;
89

910
// @ts-ignore
1011
var css = require("./styles-css.js").cssText;
1112

1213
var Editor = require("../../editor").Editor;
1314
var Renderer = require("../../virtual_renderer").VirtualRenderer;
1415
var UndoManager = require("../../undomanager").UndoManager;
16+
var Decorator = require("../../layer/decorators").Decorator;
17+
1518
require("../../theme/textmate");
1619
// enable multiselect
1720
require("../../multi_select");
@@ -125,6 +128,8 @@ class BaseDiffView {
125128
diffModel.valueB || "")),
126129
chunks: []
127130
});
131+
132+
this.setupScrollbars();
128133
}
129134

130135
addGutterDecorators() {
@@ -141,7 +146,6 @@ class BaseDiffView {
141146
$setupModel(session, value) {
142147
var editor = new Editor(new Renderer(), session);
143148
editor.session.setUndoManager(new UndoManager());
144-
// editor.renderer.setOption("decoratorType", "diff");
145149
if (value) {
146150
editor.setValue(value, -1);
147151
}
@@ -290,13 +294,100 @@ class BaseDiffView {
290294
this.editorA && this.editorA.renderer.updateBackMarkers();
291295
this.editorB && this.editorB.renderer.updateBackMarkers();
292296

293-
//this.updateScrollBarDecorators();
297+
setTimeout(() => {
298+
this.updateScrollBarDecorators();
299+
}, 0);
294300

295301
if (this.$foldUnchangedOnInput) {
296302
this.foldUnchanged();
297303
}
298304
}
299305

306+
setupScrollbars() {
307+
/**
308+
* @param {Renderer & {$scrollDecorator: ScrollDiffDecorator}} renderer
309+
*/
310+
const setupScrollBar = (renderer) => {
311+
setTimeout(() => {
312+
this.$setScrollBarDecorators(renderer);
313+
this.updateScrollBarDecorators();
314+
}, 0);
315+
};
316+
317+
if (this.inlineDiffEditor) {
318+
setupScrollBar(this.activeEditor.renderer);
319+
}
320+
else {
321+
setupScrollBar(this.editorA.renderer);
322+
setupScrollBar(this.editorB.renderer);
323+
}
324+
325+
}
326+
327+
$setScrollBarDecorators(renderer) {
328+
if (renderer.$scrollDecorator) {
329+
renderer.$scrollDecorator.destroy();
330+
}
331+
renderer.$scrollDecorator = new ScrollDiffDecorator(renderer.scrollBarV, renderer, this.inlineDiffEditor);
332+
renderer.$scrollDecorator.setSessions(this.sessionA, this.sessionB);
333+
renderer.scrollBarV.setVisible(true);
334+
renderer.scrollBarV.element.style.bottom = renderer.scrollBarH.getHeight() + "px";
335+
}
336+
337+
$resetDecorators(renderer) {
338+
if (renderer.$scrollDecorator) {
339+
renderer.$scrollDecorator.destroy();
340+
}
341+
renderer.$scrollDecorator = new Decorator(renderer.scrollBarV, renderer);
342+
}
343+
344+
updateScrollBarDecorators() {
345+
if (this.inlineDiffEditor) {
346+
if (!this.activeEditor) {
347+
return;
348+
}
349+
this.activeEditor.renderer.$scrollDecorator.zones = [];
350+
}
351+
else {
352+
if (!this.editorA || !this.editorB) {
353+
return;
354+
}
355+
this.editorA.renderer.$scrollDecorator.zones = [];
356+
this.editorB.renderer.$scrollDecorator.zones = [];
357+
}
358+
359+
/**
360+
* @param {DiffChunk} change
361+
*/
362+
const updateDecorators = (editor, change) => {
363+
if (!editor) {
364+
return;
365+
}
366+
if (change.old.start.row != change.old.end.row) {
367+
editor.renderer.$scrollDecorator.addZone(change.old.start.row, change.old.end.row - 1, "delete");
368+
}
369+
if (change.new.start.row != change.new.end.row) {
370+
editor.renderer.$scrollDecorator.addZone(change.new.start.row, change.new.end.row - 1, "insert");
371+
}
372+
};
373+
374+
if (this.inlineDiffEditor) {
375+
this.chunks && this.chunks.forEach((lineChange) => {
376+
updateDecorators(this.activeEditor, lineChange);
377+
});
378+
this.activeEditor.renderer.$scrollDecorator.$updateDecorators(this.activeEditor.renderer.layerConfig);
379+
}
380+
else {
381+
this.chunks && this.chunks.forEach((lineChange) => {
382+
updateDecorators(this.editorA, lineChange);
383+
updateDecorators(this.editorB, lineChange);
384+
});
385+
386+
this.editorA.renderer.$scrollDecorator.$updateDecorators(this.editorA.renderer.layerConfig);
387+
this.editorB.renderer.$scrollDecorator.$updateDecorators(this.editorB.renderer.layerConfig);
388+
}
389+
}
390+
300391
/**
301392
*
302393
* @param {string[]} val1
@@ -366,7 +457,7 @@ class BaseDiffView {
366457
return row;
367458
}
368459

369-
/**
460+
/**
370461
* scroll locking
371462
* @abstract
372463
**/
@@ -468,8 +559,8 @@ class BaseDiffView {
468559
}
469560
}
470561
}
471-
472-
scheduleRealign() {
562+
563+
scheduleRealign() {
473564
if (!this.realignPending) {
474565
this.realignPending = true;
475566
this.editorA.renderer.on("beforeRender", this.realign);
@@ -500,6 +591,14 @@ class BaseDiffView {
500591
this.gutterDecoratorB && this.gutterDecoratorB.dispose();
501592
this.sessionA.selection.clearSelection();
502593
this.sessionB.selection.clearSelection();
594+
595+
if (this.savedOptionsA && this.savedOptionsA.customScrollbar) {
596+
this.$resetDecorators(this.editorA.renderer);
597+
}
598+
if (this.savedOptionsB &&this.savedOptionsB.customScrollbar) {
599+
this.$resetDecorators(this.editorB.renderer);
600+
}
601+
503602
this.editorA = this.editorB = null;
504603

505604
}
@@ -771,7 +870,7 @@ config.defineOptions(BaseDiffView.prototype, "DiffView", {
771870
return this.editorA.getTheme();
772871
}
773872
},
774-
});
873+
});
775874

776875
var emptyGutterRenderer = {
777876
getText: function name(params) {

src/ext/diff/diff_test.js

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ var {DiffProvider} = require("./providers/default");
1010
var ace = require("../../ace");
1111
var Range = require("../../range").Range;
1212
var editorA, editorB, diffView;
13+
const {Decorator} = require("../../layer/decorators");
14+
const {ScrollDiffDecorator} = require("./scroll_diff_decorator");
15+
1316

1417
var DEBUG = false;
1518

@@ -28,14 +31,14 @@ function setEditorPosition(editor) {
2831

2932
function getValueA(lines) {
3033
return lines.map(function(v) {
31-
return v[0];
34+
return v[0];
3235
}).filter(function(x) {
3336
return x != null;
3437
}).join("\n");
3538
}
3639
function getValueB(lines) {
3740
return lines.map(function(v) {
38-
return v.length == 2 ? v[1] : v[0];
41+
return v.length == 2 ? v[1] : v[0];
3942
}).filter(function(x) {
4043
return x != null;
4144
}).join("\n");
@@ -191,7 +194,7 @@ module.exports = {
191194

192195
diffView.setTheme("ace/theme/cloud_editor");
193196
assert.equal(diffView.editorA.getTheme(), "ace/theme/cloud_editor");
194-
assert.equal(diffView.editorB.getTheme(), "ace/theme/cloud_editor");
197+
assert.equal(diffView.editorB.getTheme(), "ace/theme/cloud_editor");
195198

196199
diffView.editorB.setTheme("ace/theme/textmate");
197200
assert.equal(diffView.editorA.getTheme(), "ace/theme/textmate");
@@ -212,7 +215,7 @@ module.exports = {
212215

213216
diffView.detach();
214217
checkEventRegistry();
215-
218+
216219
},
217220
"test: diff at ends": function() {
218221
var diffProvider = new DiffProvider();
@@ -353,6 +356,7 @@ module.exports = {
353356

354357
editorA.session.setValue(getValueA(simpleDiff));
355358
editorB.session.setValue(getValueB(simpleDiff));
359+
editorA.setOption("customScrollbar", true);
356360

357361
diffView = new InlineDiffView({
358362
editorA, editorB,
@@ -378,6 +382,8 @@ module.exports = {
378382

379383
assert.ok(!!diffView.editorB.renderer.$gutterLayer.$renderer);
380384

385+
assert.ok(editorA.renderer.$scrollDecorator instanceof ScrollDiffDecorator);
386+
381387
diffView.detach();
382388

383389
assert.equal(editorA.getOption("wrap"), "free");
@@ -388,9 +394,75 @@ module.exports = {
388394
assert.equal(editorB.getOption("fadeFoldWidgets"), false);
389395
assert.equal(editorB.getOption("showFoldWidgets"), true);
390396
assert.ok(!editorB.renderer.$gutterLayer.$renderer);
397+
398+
assert.ok(editorA.renderer.$scrollDecorator instanceof Decorator);
391399
},
400+
"test split diff scroll decorators": function(done) {
401+
editorA.session.setValue(["a", "b", "c"].join("\n"));
402+
editorB.session.setValue(["a", "c", "X"].join("\n"));
403+
404+
diffView = new DiffView({ editorA, editorB });
405+
diffView.setProvider(new DiffProvider());
406+
diffView.onInput();
407+
408+
409+
editorA.renderer.$loop._flush();
410+
editorB.renderer.$loop._flush();
411+
412+
setTimeout(() => {
413+
assertDecoratorsPlacement(editorA, false);
414+
done();
415+
}, 0);
416+
},
417+
"test inline diff scroll decorators": function(done) {
418+
editorA.session.setValue(["a", "b", "c"].join("\n"));
419+
editorB.session.setValue(["a", "c", "X"].join("\n"));
420+
421+
diffView = new InlineDiffView({ editorA, editorB, showSideA: true });
422+
diffView.setProvider(new DiffProvider());
423+
diffView.onInput();
424+
425+
editorA.renderer.$loop._flush();
426+
427+
setTimeout(() => {
428+
assertDecoratorsPlacement(editorA, true);
429+
done();
430+
}, 0);
431+
}
392432
};
393433

434+
function findPointFillStyle(imageData, y) {
435+
const data = imageData.slice(4 * y, 4 * (y + 1));
436+
const a = Math.round(data[3] / 256 * 100);
437+
if (a === 100) return "rgb(" + data.slice(0, 3).join(",") + ")";
438+
return "rgba(" + data.slice(0, 3).join(",") + "," + (a / 100) + ")";
439+
}
440+
441+
function assertDecoratorsPlacement(editor, inlineDiff) {
442+
const decoA = editor.renderer.$scrollDecorator;
443+
const ctxA = decoA.canvas.getContext("2d");
444+
const delRow = 1;
445+
const offA = decoA.sessionA.documentToScreenRow(delRow, 0) * decoA.lineHeight;
446+
const centerA = offA + decoA.lineHeight / 2;
447+
const yA = Math.round(decoA.heightRatio * centerA);
448+
let imgA = ctxA.getImageData(decoA.oneZoneWidth, 0, 1, decoA.canvasHeight).data;
449+
assert.equal(findPointFillStyle(imgA, yA), decoA.colors.light.delete);
450+
451+
if (inlineDiff) {
452+
//make sure that in inline diff, markers fills the whole line (except error decorators part)
453+
imgA = ctxA.getImageData(decoA.canvasWidth - 1, 0, 1, decoA.canvasHeight).data;
454+
assert.equal(findPointFillStyle(imgA, yA), decoA.colors.light.delete);
455+
}
456+
457+
const xB = decoA.oneZoneWidth * 2;
458+
const imgB = ctxA.getImageData(xB, 0, 1, decoA.canvasHeight).data;
459+
460+
const insRow = 2;
461+
const offB = decoA.sessionB.documentToScreenRow(insRow, 0) * decoA.lineHeight;
462+
const centerB = offB + decoA.lineHeight / 2;
463+
const yB = Math.round(decoA.heightRatio * centerB);
464+
assert.equal(findPointFillStyle(imgB, yB), decoA.colors.light.insert);
465+
}
394466

395467
if (typeof module !== "undefined" && module === require.main) {
396468
require("asyncjs").test.testcase(module.exports).exec();

src/ext/diff/providers/default.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2462,6 +2462,7 @@ var {DiffChunk} = require("../base_diff_view");
24622462
/**
24632463
* VSCode’s computeDiff provider
24642464
*/
2465+
24652466
class DiffProvider {
24662467
compute(originalLines, modifiedLines, opts) {
24672468
if (!opts) opts = {};

0 commit comments

Comments
 (0)