mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/CodeMirror
synced 2024-11-27 15:40:00 +00:00
Improve matchbrackets performance when moving the cursor
My previous patch Icbb1122 focused on the behavior of the matchbrackets addon when the text is *edited*. This patch here is about moving the cursor without changing the text. I realized the addon re-draws everything every time the cursor moves, even if the highlighted pair of brackets is still the same. This triggers very expensive code in the CodeMirror lib. I had a look at this expensive code, but did not found an easy win. It just is what it is: an expensive re-draw. Instead I introduced a caching layer that remembers the positions of the previously highlighted brackets and bails out as early as possible when nothing changed. The biggest chunk of code is that "did something change?" comparison. It looks expensive, but typically isn't. There are typically only 2 elements in the array for a single opening–closing pair. (Possibly more when there are multiple text selections.) The elements in the two arrays are typically in the same order. (Except the cursor is on the closing bracket.) Which means the nested `every` → `for` loop will typically be executed 2 times only – one time for each of the 2 elements. I won't upload this change upstream because it is only relevant together with our custom "in the middle" bracket highlighting. With our customization we have many, many situations where the highlighted brackets don't change. This (almost) doesn't happen upstream. Bug: T270317 Change-Id: I789b45362388f0818e797f789f6af427a35e3e06
This commit is contained in:
parent
26f41000a8
commit
6bc81ffd50
|
@ -135,6 +135,32 @@
|
|||
return null;
|
||||
}
|
||||
|
||||
function stillTheSameMarks( marks, config ) {
|
||||
var same = config.currentMarks &&
|
||||
// No need to compare the details if it's not even the same amount
|
||||
config.currentMarks.length === marks.length &&
|
||||
// We need the flexibility because the order can be closing → opening bracket as well
|
||||
marks.every( function ( mark ) {
|
||||
// These are typically only 2 elements for the opening and closing bracket
|
||||
for ( var i = 0; i < config.currentMarks.length; i++ ) {
|
||||
// Ordered from "most likely to change" to "least likely" for performance
|
||||
if ( config.currentMarks[i].from.ch === mark.from.ch &&
|
||||
config.currentMarks[i].from.line === mark.from.line &&
|
||||
config.currentMarks[i].match === mark.match
|
||||
) {
|
||||
// This assumes all elements are unique, which should be the case
|
||||
config.currentMarks.splice( i, 1 );
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} ) &&
|
||||
// Must be empty at the end, i.e. all elements must have been found and removed
|
||||
!config.currentMarks.length;
|
||||
config.currentMarks = marks;
|
||||
return same;
|
||||
}
|
||||
|
||||
function findMatchingBracket(cm, where, config) {
|
||||
var line = cm.getLineHandle(where.line), pos = where.ch - 1;
|
||||
var afterCursor = config && config.afterCursor
|
||||
|
@ -200,17 +226,35 @@
|
|||
// Disable brace matching in long lines, since it'll cause hugely slow updates
|
||||
var maxHighlightLen = cm.state.matchBrackets.maxHighlightLineLength || 1000,
|
||||
highlightNonMatching = config && config.highlightNonMatching;
|
||||
var marks = [], ranges = cm.listSelections();
|
||||
var markPositions = [], marks = [], ranges = cm.listSelections();
|
||||
for (var i = 0; i < ranges.length; i++) {
|
||||
var match = ranges[i].empty() && findMatchingBracket(cm, ranges[i].head, config);
|
||||
if (match && (match.match || highlightNonMatching !== false) && cm.getLine(match.from.line).length <= maxHighlightLen) {
|
||||
var style = match.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
|
||||
marks.push(cm.markText(match.from, Pos(match.from.line, match.from.ch + 1), {className: style}));
|
||||
// Note: Modified by WMDE, now uses an intermediate format to delay the actual highlighting
|
||||
markPositions.push(match);
|
||||
if (match.to && cm.getLine(match.to.line).length <= maxHighlightLen)
|
||||
marks.push(cm.markText(match.to, Pos(match.to.line, match.to.ch + 1), {className: style}));
|
||||
markPositions.push({from: match.to, match: match.match});
|
||||
}
|
||||
}
|
||||
|
||||
// There is no config when autoclear is true
|
||||
if (config && stillTheSameMarks(markPositions, config)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!autoclear && config.currentlyHighlighted) {
|
||||
config.currentlyHighlighted();
|
||||
config.currentlyHighlighted = null;
|
||||
}
|
||||
for (i = 0; i < markPositions.length; i++) {
|
||||
marks.push(cm.markText(
|
||||
markPositions[i].from,
|
||||
// Note: This assumes there is always exactly 1 character to highlight
|
||||
Pos(markPositions[i].from.line, markPositions[i].from.ch + 1),
|
||||
{className: markPositions[i].match ? 'CodeMirror-matchingbracket' : 'CodeMirror-nonmatchingbracket'}
|
||||
));
|
||||
}
|
||||
|
||||
if (marks.length) {
|
||||
// Kludge to work around the IE bug from issue #1193, where text
|
||||
// input stops going to the textarea whenever this fires.
|
||||
|
@ -222,17 +266,13 @@
|
|||
});
|
||||
};
|
||||
if (autoclear) setTimeout(clear, 800);
|
||||
else return clear;
|
||||
else config.currentlyHighlighted = clear;
|
||||
}
|
||||
}
|
||||
|
||||
function doMatchBrackets(cm) {
|
||||
cm.operation(function() {
|
||||
if (cm.state.matchBrackets.currentlyHighlighted) {
|
||||
cm.state.matchBrackets.currentlyHighlighted();
|
||||
cm.state.matchBrackets.currentlyHighlighted = null;
|
||||
}
|
||||
cm.state.matchBrackets.currentlyHighlighted = matchBrackets(cm, false, cm.state.matchBrackets);
|
||||
matchBrackets(cm, false, cm.state.matchBrackets);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue