Cache ranges for Highlight.js syntax markers

The cache is saving 5-10% time for syntax layer. Since the syntax layer
process whole files (not only diff chunks) there is a lot of duplicity
between original and modified file (left and right diff). Calculation
of ranges is an quite expensive operation, so any saving for skipping
calculation is improving the time of javascript execution.

Change-Id: I0976f25238c452a1240e96e4631e0aa9686dfa67
This commit is contained in:
Milutin Kristofic
2019-10-09 17:47:56 +02:00
parent bcd6dba57d
commit eada9d2819
2 changed files with 30 additions and 7 deletions

View File

@@ -259,12 +259,14 @@
lastNotify: {left: 1, right: 1},
};
const rangesCache = new Map();
this._processPromise = util.makeCancelable(this._loadHLJS()
.then(() => {
return new Promise(resolve => {
const nextStep = () => {
this._processHandle = null;
this._processNextLine(state);
this._processNextLine(state, rangesCache);
// Move to the next line in the section.
state.lineIndex++;
@@ -321,12 +323,21 @@
* Highlight.js emits and emit a list of text ranges and classes for the
* markers.
* @param {string} str The string of HTML.
* @param {Map<string, !Array<!Object>>} rangesCache A map for caching
* ranges for each string. A cache is read and written by this method.
* Since diff is mostly comparing same file on two sides, there is good rate
* of duplication at least for parts that are on left and right parts.
* @return {!Array<!Object>} The list of ranges.
*/
_rangesFromString(str) {
_rangesFromString(str, rangesCache) {
const cached = rangesCache.get(str);
if (cached) return cached;
const div = document.createElement('div');
div.innerHTML = str;
return this._rangesFromElement(div, 0);
const ranges = this._rangesFromElement(div, 0);
rangesCache.set(str, ranges);
return ranges;
},
_rangesFromElement(elem, offset) {
@@ -357,7 +368,7 @@
* lines).
* @param {!Object} state The processing state for the layer.
*/
_processNextLine(state) {
_processNextLine(state, rangesCache) {
let baseLine;
let revisionLine;
@@ -386,7 +397,8 @@
baseLine = this._workaround(this._baseLanguage, baseLine);
result = this._hljs.highlight(this._baseLanguage, baseLine, true,
state.baseContext);
this.push('_baseRanges', this._rangesFromString(result.value));
this.push('_baseRanges',
this._rangesFromString(result.value, rangesCache));
state.baseContext = result.top;
}
@@ -395,7 +407,8 @@
revisionLine = this._workaround(this._revisionLanguage, revisionLine);
result = this._hljs.highlight(this._revisionLanguage, revisionLine,
true, state.revisionContext);
this.push('_revisionRanges', this._rangesFromString(result.value));
this.push('_revisionRanges',
this._rangesFromString(result.value, rangesCache));
state.revisionContext = result.top;
}
},

View File

@@ -388,10 +388,20 @@ limitations under the License.
'<span class="non-whtelisted-class">',
'<span class="gr-diff gr-syntax gr-syntax-keyword">public</span>',
'</span>'].join('');
const result = element._rangesFromString(str);
const result = element._rangesFromString(str, new Map());
assert.notEqual(result.length, 0);
});
test('_rangesFromString cache same syntax markers', () => {
sandbox.spy(element, '_rangesFromElement');
const str =
'<span class="gr-diff gr-syntax gr-syntax-keyword">public</span>';
const cacheMap = new Map();
element._rangesFromString(str, cacheMap);
element._rangesFromString(str, cacheMap);
assert.isTrue(element._rangesFromElement.calledOnce);
});
test('_isSectionDone', () => {
let state = {sectionIndex: 0, lineIndex: 0};
assert.isFalse(element._isSectionDone(state));