refactor: moved cm markup to virtual doc for good performance gain

This commit is contained in:
Jamie Peabody
2022-01-15 14:18:05 +00:00
parent ddbf594bc7
commit 4f9cf3aa7e
3 changed files with 234 additions and 200 deletions

View File

@@ -22,7 +22,7 @@ document.onreadystatechange = function () {
const doc = new Mergely('#mergely', {
license: 'lgpl',
ignorews: true,
wrap_lines: true,
wrap_lines: false,
// change_timeout: 0,
viewport: true,
cmsettings: {
@@ -36,8 +36,7 @@ document.onreadystatechange = function () {
.replace(/love/g, 'hate')
.replace(/heart/g, 'head'));
},
_debug: 'api,event,scroll,change,diff',
// _debug: 'draw,event,diff,change'//'event,scroll,diff,draw'
_debug: 'draw,change,event'
});
// On init, scroll to first diff

View File

@@ -41,11 +41,11 @@ Fixed issue where unmarkup did not emit an updated event.
Fixed issue where init triggered an updated event when autoupdate is disabled.
Fixed documentation issue where `merge` incorrectly stated: from the specified `side` to the opposite side
Fixed performance issue scrolling (find #)
Fixed performance issue with large sections of deleted/added text
Fixed issue where initial render scrolled to first change, showing it at the bottom, as opposed to middle
TODO:
For some reason ignore-whitespace will mark the "red" differently
When wrap_lines is false, the CM editor grows, screwing up the layout
Introduce an async render pipeline as it's currently blocking UI
Fix issue where characters like `{}[].?` are not detected by LCS
Fix the popup
@@ -115,7 +115,7 @@ CodeMirrorDiffView.prototype.init = function(el, options = {}) {
this.el = el;
this.lhs_cmsettings = {
viewportMargin: Infinity,
// viewportMargin: Infinity,
...this.settings.cmsettings,
...this.settings.lhs_cmsettings,
// these override any user-defined CodeMirror settings
@@ -215,7 +215,7 @@ CodeMirrorDiffView.prototype.scrollToDiff = function(direction) {
this._current_diff = Math.max(--this._current_diff, 0);
}
}
if (this.settings._debug.includes('change')) {
if (this.settings._debug.includes('debug')) {
trace('change', Timer.stop(), 'current-diff', this._current_diff);
}
this._scroll_to_change(this.changes[this._current_diff]);
@@ -581,22 +581,25 @@ CodeMirrorDiffView.prototype.bind = function(el) {
this.editor.lhs.on('change', (instance, ev) => {
if (this.settings._debug.includes('event')) {
trace('event', Timer.stop(), 'change lhs', ev);
trace('event#lhs-change');
}
if (!this.settings.autoupdate) {
return;
}
this._changing();
if (this.settings._debug.includes('event')) {
trace('event#lhs-change [emitted]');
}
});
this.editor.lhs.on('scroll', () => {
if (this.settings._debug.includes('event')) {
trace('event', Timer.stop(), 'scroll lhs');
trace('event#lhs-scroll');
}
this._scrolling({ side: 'lhs', id: this.lhsId });
});
this.editor.rhs.on('change', (instance, ev) => {
if (this.settings._debug.includes('event')) {
trace('event', Timer.stop(), 'change rhs', ev);
trace('event#lhs-change');
}
if (!this.settings.autoupdate) {
return;
@@ -605,7 +608,7 @@ CodeMirrorDiffView.prototype.bind = function(el) {
});
this.editor.rhs.on('scroll', () => {
if (this.settings._debug.includes('event')) {
trace('event', Timer.stop(), 'scroll rhs');
trace('event#rhs-scroll');
}
this._scrolling({ side: 'rhs', id: this.rhsId });
});
@@ -614,13 +617,16 @@ CodeMirrorDiffView.prototype.bind = function(el) {
let resizeTimeout;
const resize = () => {
if (this.settings._debug.includes('event')) {
traceTimeStart('resize');
traceTimeStart('event#resize');
trace('event#resize [start]');
}
this.resize();
this.editor.lhs.refresh();
this.editor.rhs.refresh();
console.log('wtf refresh? >');
// this.editor.lhs.refresh();
// this.editor.rhs.refresh();
console.log('wtf refresh? <');
if (this.settings._debug.includes('event')) {
traceTimeEnd('resize');
traceTimeEnd('event#resize');
}
};
this._handleResize = () => {
@@ -630,9 +636,6 @@ CodeMirrorDiffView.prototype.bind = function(el) {
resizeTimeout = setTimeout(resize, this.settings.resize_timeout);
};
window.addEventListener('resize', () => {
if (this.settings._debug.includes('event')) {
trace('event', Timer.stop(), 'resize');
}
this._handleResize();
});
resize();
@@ -743,7 +746,7 @@ CodeMirrorDiffView.prototype._get_colors = function() {
bg: cStyle.backgroundColor
};
cColor.remove();
if (this.settings._debug.includes('draw')) {
if (this.settings._debug.includes('debug')) {
trace('draw', Timer.stop(), '_get_colors', this._colors);
}
}
@@ -873,12 +876,14 @@ CodeMirrorDiffView.prototype._scrolling = function({ side, id }) {
CodeMirrorDiffView.prototype._changing = function({ force } = { force: false }) {
if (this.settings._debug.includes('change')) {
traceTimeStart('_changing');
traceTimeStart('change#_changing');
trace('change#_changing [start]');
}
const handleChange = () => {
this._changedTimeout = null;
if (!force && !this.settings.autoupdate) {
if (this.settings._debug.includes('change')) {
if (this.settings._debug.includes('debug')
&& this.settings._debug.includes('change')) {
trace('change', Timer.stop(), '_changing', 'ignore change', force, this.settings.autoupdate);
}
return;
@@ -886,7 +891,8 @@ CodeMirrorDiffView.prototype._changing = function({ force } = { force: false })
this._changed();
};
if (this.settings.change_timeout > 0) {
if (this.settings._debug.includes('change')) {
if (this.settings._debug.includes('debug')
&& this.settings._debug.includes('change')) {
trace('change', Timer.stop(), 'setting timeout', this.settings.change_timeout)
}
if (this._changedTimeout != null) {
@@ -897,19 +903,21 @@ CodeMirrorDiffView.prototype._changing = function({ force } = { force: false })
handleChange();
}
if (this.settings._debug.includes('change')) {
traceTimeEnd('_changing');
traceTimeEnd('change#_changing');
}
};
CodeMirrorDiffView.prototype._changed = function() {
if (this.settings._debug.includes('change')) {
trace('change', Timer.stop(), '_changed', 'start');
traceTimeStart('_changed');
traceTimeStart('change#_changed');
trace('change#_changed [start]');
}
// NOTE: clear is handled by the beforeChange event
this._diff();
this._renderChanges();
this.el.dispatchEvent(new Event('updated'));
if (this.settings._debug.includes('change')) {
traceTimeEnd('_changed');
traceTimeEnd('change#_changed');
}
};
@@ -918,31 +926,30 @@ CodeMirrorDiffView.prototype._changed = function() {
*/
CodeMirrorDiffView.prototype._clear = function() {
if (this.settings._debug.includes('draw')) {
traceTimeStart('_clear');
trace('draw', Timer.stop(), '_clear', 'start');
traceTimeStart('draw#_clear');
}
this.changes = [];
this._clearMarkup();
this._clearCanvases();
if (this.settings._debug.includes('draw')) {
traceTimeEnd('_clear');
traceTimeEnd('draw#_clear');
}
};
CodeMirrorDiffView.prototype._clearMarkup = function () {
if (this.settings._debug.includes('draw')) {
traceTimeStart('_clearMarkup');
traceTimeStart('draw#_clearMarkup');
}
this._vdoc.clear();
this._vdoc = new VDoc();
if (this.settings._debug.includes('draw')) {
traceTimeEnd('_clearMarkup');
traceTimeEnd('draw#_clearMarkup');
}
}
CodeMirrorDiffView.prototype._clearCanvases = function() {
if (this.settings._debug.includes('draw')) {
traceTimeStart('_clearCanvases');
traceTimeStart(' draw#_clearCanvases');
}
const ex = this._draw_info();
@@ -968,37 +975,34 @@ CodeMirrorDiffView.prototype._clearCanvases = function() {
ctx.clearRect(0, 0, this.draw_mid_width, ex.visible_page_height);
if (this.settings._debug.includes('draw')) {
traceTimeEnd('_clearCanvases');
traceTimeEnd(' draw#_clearCanvases');
}
};
CodeMirrorDiffView.prototype._diff = function() {
if (this.settings._debug.includes('change')) {
traceTimeStart(' change#_diff');
}
const lhs = this.editor.lhs.getValue();
const rhs = this.editor.rhs.getValue();
if (this.settings._debug.includes('diff')) {
trace('draw', Timer.stop(), '_diff', 'start');
traceTimeStart('_diff');
}
const comparison = new diff(lhs, rhs, this.settings);
this.changes = DiffParser(comparison.normal_form());
if (this.settings._debug.includes('diff')) {
traceTimeEnd('_diff');
if (this.settings._debug.includes('change')) {
traceTimeEnd(' change#_diff');
}
this._renderChanges();
this.el.dispatchEvent(new Event('updated'));
};
CodeMirrorDiffView.prototype._renderChanges = function() {
if (this.settings._debug.includes('draw')) {
trace('draw', Timer.stop(), '_renderChanges', 'start');
traceTimeStart('_renderChanges');
traceTimeStart('draw#_renderChanges');
trace('draw#_renderChanges [start]');
}
this._clearCanvases();
this._calculateOffsets(this.changes);
this._markupLineChanges(this.changes);
this._renderDiff(this.changes);
if (this.settings._debug.includes('draw')) {
traceTimeEnd('_renderChanges');
traceTimeEnd('draw#_renderChanges');
}
}
@@ -1039,7 +1043,7 @@ CodeMirrorDiffView.prototype._set_top_offset = function (side) {
CodeMirrorDiffView.prototype._calculateOffsets = function (changes) {
if (this.settings._debug.includes('draw')) {
traceTimeStart('_calculateOffsets');
traceTimeStart(' draw#_calculateOffsets');
}
const {
lhs: led,
@@ -1136,13 +1140,13 @@ CodeMirrorDiffView.prototype._calculateOffsets = function (changes) {
change['rhs-y-end'] += 0.5;
}
if (this.settings._debug.includes('draw')) {
traceTimeEnd('_calculateOffsets');
traceTimeEnd(' draw#_calculateOffsets');
}
}
CodeMirrorDiffView.prototype._markupLineChanges = function (changes) {
if (this.settings._debug.includes('draw')) {
traceTimeStart('_markupLineChanges');
traceTimeStart(' draw#_markupLineChanges');
}
const {
lhs: led,
@@ -1153,160 +1157,67 @@ CodeMirrorDiffView.prototype._markupLineChanges = function (changes) {
const rhsvp = this._get_viewport_side('rhs');
const { _vdoc: vdoc } = this;
led.operation(() => {
for (let i = 0; i < changes.length; ++i) {
const change = changes[i];
if (!this._isChangeInView('lhs', lhsvp, change)) {
// if the change is outside the viewport, skip
continue;
}
if (vdoc.hasRendered('lhs', i)) {
// already rendered
continue;
}
// use the virtual doc to markup all the changes
for (let i = 0; i < changes.length; ++i) {
const change = changes[i];
const isCurrent = current_diff === i;
const lineDiff = this.settings.lcs !== false;
const lhsInView = this._isChangeInView('lhs', lhsvp, change);
const rhsInView = this._isChangeInView('rhs', rhsvp, change);
// lhs changes
if (lhsInView) {
// the change is inside the viewport
vdoc.addRender('lhs', change, i, {
isCurrent: current_diff === i,
isCurrent,
lineDiff,
// TODO: move out of loop
getMergeHandler: (change, side, oside) => {
return () => this._merge_change(change, side, oside);
},
mergeButton: red.getOption('readOnly')
? null : this.merge_rhs_button.cloneNode(true)
});
vdoc.update('lhs', i, led);
}
});
red.operation(() => {
for (let i = 0; i < changes.length; ++i) {
const change = changes[i];
if (!this._isChangeInView('rhs', rhsvp, change)) {
// if the change is outside the viewport, skip
continue;
}
if (vdoc.hasRendered('rhs', i)) {
// already rendered
continue;
}
// rhs changes
if (rhsInView) {
vdoc.addRender('rhs', change, i, {
isCurrent: current_diff === i,
isCurrent,
lineDiff,
// TODO: move out of loop
getMergeHandler: (change, side, oside) => {
return () => this._merge_change(change, side, oside);
},
mergeButton: led.getOption('readOnly')
? null : this.merge_lhs_button.cloneNode(true)
});
vdoc.update('rhs', i, red);
}
});
// mark text deleted, LCS changes
function getMarkupFunc({ marktext, editor, lineNum, op }) {
return (from, to) => {
marktext.push([
editor,
{ line: lineNum, ch: from },
{ line: lineNum, ch: to },
{ className: `mergely ch ${op}` }
]);
}
}
const marktext = [];
for (let i = 0; this.settings.lcs && i < changes.length; ++i) {
const change = changes[i];
const llf = change['lhs-line-from'] >= 0 ? change['lhs-line-from'] : 0;
const llt = change['lhs-line-to'] >= 0 ? change['lhs-line-to'] : 0;
const rlf = change['rhs-line-from'] >= 0 ? change['rhs-line-from'] : 0;
const rlt = change['rhs-line-to'] >= 0 ? change['rhs-line-to'] : 0;
if (!this._isChangeInView('lhs', lhsvp, change)
&& !this._isChangeInView('lhs', rhsvp, change)) {
continue;
}
if (change.op == 'd') {
// apply delete to cross-out (left-hand side only)
const from = llf;
const to = llt;
// the change is within the viewport
const to_ln = led.lineInfo(to);
if (to_ln) {
marktext.push([led, {line:from, ch:0}, {line:to, ch:to_ln.text.length}, {className: 'mergely ch d lhs'}]);
}
}
else if (change.op == 'c') {
// apply LCS changes to each line
for (let j = llf, k = rlf;
((j >= 0) && (j <= llt)) || ((k >= 0) && (k <= rlt));
++j, ++k) {
let lhs_line;
let rhs_line;
if (k > rlt) {
// lhs continues past rhs, mark lhs as deleted
lhs_line = led.getLine( j );
if (!lhs_line) {
continue;
if (lineDiff && (lhsInView || rhsInView)) {
vdoc.addInlineDiff(change, {
getText: (side, lineNum) => {
if (side === 'lhs') {
return led.getLine(lineNum);
} else {
return red.getLine(lineNum);
}
marktext.push([led, {line:j, ch:0}, {line:j, ch:lhs_line.length}, {className: 'mergely ch d lhs'}]);
continue;
}
if (j > llt) {
// rhs continues past lhs, mark rhs as added
rhs_line = red.getLine( k );
if (!rhs_line) {
continue;
}
marktext.push([red, {line:k, ch:0}, {line:k, ch:rhs_line.length}, {className: 'mergely ch a rhs'}]);
continue;
}
lhs_line = led.getLine( j );
rhs_line = red.getLine( k );
if (this.settings._debug.includes('diff')) {
traceTimeStart(`diff lcs change: ${i}`);
}
const lcs = new LCS(lhs_line, rhs_line, {
ignoreaccents: !!this.settings.ignoreaccents,
ignorews: !!this.settings.ignorews
});
lcs.diff(
getMarkupFunc({ marktext, editor: red, lineNum: k, op: 'a rhs' }),
getMarkupFunc({ marktext, editor: led, lineNum: j, op: 'd lhs' })
);
if (this.settings._debug.includes('diff')) {
traceTimeEnd(`diff lcs change: ${i}`);
}
}
});
}
}
if (this.settings._debug.includes('draw')) {
trace('draw', Timer.stop(), '_markupLineChanges applying', marktext.length, 'markups');
}
led.operation(() => {
// apply lhs markup
for (let i = 0; i < marktext.length; ++i) {
const m = marktext[i];
if (m[0].doc.id != led.getDoc().id) continue;
this.chfns.lhs.push(m[0].markText(m[1], m[2], m[3]));
for (let i = 0; i < changes.length; ++i) {
vdoc.update('lhs', i, led, lhsvp);
}
});
red.operation(() => {
// apply lhs markup
for (let i = 0; i < marktext.length; ++i) {
const m = marktext[i];
if (m[0].doc.id != red.getDoc().id) continue;
this.chfns.rhs.push(m[0].markText(m[1], m[2], m[3]));
for (let i = 0; i < changes.length; ++i) {
vdoc.update('rhs', i, red, rhsvp);
}
});
if (this.settings._debug.includes('draw')) {
traceTimeEnd('_markupLineChanges');
traceTimeEnd(' draw#_markupLineChanges');
}
};
@@ -1389,6 +1300,9 @@ CodeMirrorDiffView.prototype._draw_info = function() {
};
CodeMirrorDiffView.prototype._renderDiff = function(changes) {
if (this.settings._debug.includes('draw')) {
traceTimeStart(' draw#_renderDiff');
}
const ex = this._draw_info();
const mcanvas_lhs = ex.lhs_margin;
const mcanvas_rhs = ex.rhs_margin;
@@ -1396,7 +1310,7 @@ CodeMirrorDiffView.prototype._renderDiff = function(changes) {
const ctx_lhs = mcanvas_lhs.getContext('2d');
const ctx_rhs = mcanvas_rhs.getContext('2d');
if (this.settings._debug.includes('draw')) {
if (this.settings._debug.includes('debug')) {
trace('draw', Timer.stop(), '_renderDiff', 'visible page height', ex.visible_page_height);
trace('draw', Timer.stop(), '_renderDiff', 'scroller-top lhs', ex.lhs_scroller.scrollTop);
trace('draw', Timer.stop(), '_renderDiff', 'scroller-top rhs', ex.rhs_scroller.scrollTop);
@@ -1431,7 +1345,7 @@ CodeMirrorDiffView.prototype._renderDiff = function(changes) {
}
// draw margin indicators
if (this.settings._debug.includes('draw1')) {
if (this.settings._debug.includes('debug')) {
trace('draw', Timer.stop(), '_renderDiff', 'draw1', 'marker',
lhs_y_start, lhs_y_end, rhs_y_start, rhs_y_end);
}
@@ -1548,6 +1462,10 @@ CodeMirrorDiffView.prototype._renderDiff = function(changes) {
};
ex.lhs_margin.addEventListener('click', this._handleLhsMarginClick);
ex.rhs_margin.addEventListener('click', this._handleRhsMarginClick);
if (this.settings._debug.includes('draw')) {
traceTimeEnd(' draw#_renderDiff');
}
};
CodeMirrorDiffView.prototype.trace = function(name) {

View File

@@ -1,3 +1,5 @@
const LCS = require('./lcs');
class VDoc {
constructor() {
this._lines = {
@@ -10,54 +12,127 @@ class VDoc {
};
}
hasRendered(side, changeId) {
hasRenderedChange(side, changeId) {
return !!this._rendered[side][changeId];
}
addRender(side, change, changeId, { isCurrent, mergeButton, getMergeHandler }) {
this.setRendered(side, changeId);
addRender(side, change, changeId, options) {
const {
isCurrent,
lineDiff,
mergeButton,
getMergeHandler
} = options;
if (this.hasRenderedChange(side, change)) {
return;
}
const oside = (side === 'lhs') ? 'rhs' : 'lhs';
const bgClass = [ 'mergely', side, change.op, `cid-${changeId}` ];
const lf = change[`${side}-line-from`] >= 0 ? change[`${side}-line-from`] : 0;
const lt = change[`${side}-line-to`] >= 0 ? change[`${side}-line-to`] : 0;
const olf = change[`${oside}-line-from`] >= 0 ? change[`${oside}-line-from`] : 0;
const { lf, lt, olf, olt } = getExtents(side, change);
if (change[lf] < 0) {
bgClass.push('empty');
}
this.getLine(side, lf).addLineClass('background', 'start');
this.getLine(side, lt).addLineClass('background', 'end');
this._getLine(side, lf).addLineClass('background', 'start');
this._getLine(side, lt).addLineClass('background', 'end');
if (isCurrent) {
if (lf != lt) {
this.getLine(side, lf).addLineClass('background', 'current');
this._getLine(side, lf).addLineClass('background', 'current');
}
this.getLine(side, lt).addLineClass('background', 'current');
this._getLine(side, lt).addLineClass('background', 'current');
for (let j = lf; j <= lt; ++j) {
this.getLine(side, j).addLineClass('gutter', 'mergely current');
this._getLine(side, j).addLineClass('gutter', 'mergely current');
}
}
if (lf == 0 && lt == 0 && olf == 0) {
this.getLine(side, lf).addLineClass('background', bgClass.join(' '));
this.getLine(side, lf).addLineClass('background', 'first');
// test if this change is the first line of the side
this._getLine(side, lf).addLineClass('background', bgClass.join(' '));
this._getLine(side, lf).addLineClass('background', 'first');
// FIXME: need lineDiff here?
} else {
// apply change for each line in-between the changed lines
for (let j = lf; j <= lt; ++j) {
this.getLine(side, j).addLineClass('background', bgClass.join(' '));
this._getLine(side, j).addLineClass('background', bgClass.join(' '));
if (!lineDiff) {
// inner line diffs are disabled, skip the rest
continue;
}
if (side === 'lhs'
&& (change.op === 'd'
|| (change.op === 'c' && j > olt)
)) {
// mark entire line text with deleted (strikeout) if the
// change is a delete, or if it is changed text and the
// line goes past the end of the other side.
this._getLine(side, j).markText(0, undefined, 'mergely ch d lhs');
} else if (side == 'rhs'
&& (change.op === 'a'
|| (change.op === 'c' && j > olt)
)) {
// mark entire line text with added if the change is an
// add, or if it is changed text and the line goes past the
// end of the other side.
this._getLine(side, j).markText(0, undefined, 'mergely ch a rhs');
}
}
}
if (mergeButton) {
mergeButton.className = `merge-button merge-${oside}-button`;
const handler = getMergeHandler(change, side, oside);
this.getLine(side, lf).addMergeButton('merge', mergeButton, handler);
this._getLine(side, lf).addMergeButton('merge', mergeButton, handler);
}
this._setRenderedChange(side, changeId);
}
addInlineDiff(change, { getText }) {
const { lf, lt, olf, olt } = getExtents('lhs', change);
const vdoc = this;
for (let j = lf, k = olf;
((j >= 0) && (j <= lt)) || ((k >= 0) && (k <= olt));
++j, ++k) {
// if both lhs line and rhs are within the change range with
// respect to each other, do inline diff.
if (j <= lt && k <= olt) {
const lhsText = getText('lhs', j);
const rhsText = getText('rhs', k);
// TODO: there is an LCS performance gain here if either side
// is empty.
const lcs = new LCS(lhsText, rhsText, {
// TODO
ignoreaccents: false,
ignorews: false
});
// TODO: there might be an LCS performance gain here to move
// function declaration out of loop, but there shouldn't be
// too many lines here. it's no more than the viewport.
lcs.diff(
function _added(from, to) {
const line = vdoc._getLine('rhs', k);
line.markText(from, to, 'mergely ch a rhs');
},
function _removed(from, to) {
const line = vdoc._getLine('lhs', j);
line.markText(from, to, 'mergely ch d lhs');
}
);
}
}
}
setRendered(side, changeId) {
_setRenderedChange(side, changeId) {
return this._rendered[side][changeId] = true;
}
getLine(side, id) {
_getLine(side, id) {
let line = this._lines[side][id];
if (line) {
return line;
@@ -67,12 +142,19 @@ class VDoc {
return line;
}
update(side, changeId, editor) {
this.setRendered(side, changeId);
update(side, changeId, editor, viewport) {
this._setRenderedChange(side, changeId);
const lines = Object.keys(this._lines[side]);
for (let i = 0; i < lines.length; ++i) {
const id = lines[i];
const vline = this.getLine(side, id);
if (id < viewport.from || id > viewport.to) {
continue;
}
const vline = this._getLine(side, id);
if (vline.rendered) {
continue;
}
vline.update(editor);
}
}
@@ -84,13 +166,6 @@ class VDoc {
for (const lineId in this._lines.rhs) {
this._lines.rhs[lineId].clear();
}
// for (let i = 0; i < Object.keys(this._lines.lhs).length; ++i) {
// this._lines.lhs[i].clear();
// }
// for (let i = 0; i < Object.keys(this._lines.rhs).length; ++i) {
// this._lines.rhs[i].clear();
// }
}
}
@@ -101,6 +176,8 @@ class VLine {
this.gutter = new Set();
this.marker = null;
this.editor = null;
this.markup = [];
this.rendered = false;
}
addLineClass(location, clazz) {
@@ -111,7 +188,16 @@ class VLine {
this.marker = [ name, item, handler ];
}
markText(charFrom, charTo, className) {
this.markup = [ charFrom, charTo, className ];
}
update(editor) {
if (this.rendered) {
// FIXME: probably do not need this now
console.log('already rendered', this.id);
return;
}
this.editor = editor;
if (this.background.size) {
const clazz = Array.from(this.background).join(' ');
@@ -126,10 +212,28 @@ class VLine {
item.addEventListener('click', handler);
editor.setGutterMarker(this.id, name, item);
}
if (this.markup) {
const [ charFrom, charTo, className ] = this.markup;
const fromPos = { line: this.id };
const toPos = { line: this.id };
if (charFrom >= 0) {
fromPos.ch = charFrom;
}
if (charTo >= 0) {
toPos.ch = charTo;
}
this._clearMarkup = editor.markText(fromPos, toPos, { className });
}
this.rendered = true;
}
clear() {
const { editor } = this;
if (!this.rendered) {
return;
}
console.warn('clear editor', this.id);
if (this.background) {
editor.removeLineClass(this.id, 'background');
}
@@ -143,7 +247,20 @@ class VLine {
item.removeEventListener('click', handler);
item.remove();
}
if (this._clearMarkup) {
this._clearMarkup.clear();
}
}
}
function getExtents(side, change) {
const oside = (side === 'lhs') ? 'rhs' : 'lhs';
return {
lf: change[`${side}-line-from`] >= 0 ? change[`${side}-line-from`] : 0,
lt: change[`${side}-line-to`] >= 0 ? change[`${side}-line-to`] : 0,
olf: change[`${oside}-line-from`] >= 0 ? change[`${oside}-line-from`] : 0,
olt: change[`${oside}-line-to`] >= 0 ? change[`${oside}-line-to`] : 0
};
}
module.exports = VDoc;