2018-03-04 20:07:01 +01:00
|
|
|
// The following code is heavily based on the standard CodeMirror
|
|
|
|
// search addon found at: https://codemirror.net/addon/search/search.js
|
|
|
|
// I added/removed and modified code in order to get a closer match to a
|
|
|
|
// browser's built-in find-in-page feature which are just enough for
|
|
|
|
// uBlock Origin.
|
2020-08-02 18:18:01 +02:00
|
|
|
//
|
|
|
|
// This file was originally wholly imported from:
|
|
|
|
// https://github.com/codemirror/CodeMirror/blob/3e1bb5fff682f8f6cbfaef0e56c61d62403d4798/addon/search/search.js
|
|
|
|
//
|
|
|
|
// And has been modified over time to better suit uBO's usage and coding style:
|
|
|
|
// https://github.com/gorhill/uBlock/commits/master/src/js/codemirror/search.js
|
|
|
|
//
|
|
|
|
// The original copyright notice is reproduced below:
|
|
|
|
|
|
|
|
// =====
|
2018-03-04 20:07:01 +01:00
|
|
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
|
|
|
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
|
|
|
|
|
|
|
// Define search commands. Depends on dialog.js or another
|
|
|
|
// implementation of the openDialog method.
|
|
|
|
|
|
|
|
// Replace works a little oddly -- it will do the replace on the next
|
|
|
|
// Ctrl-G (or whatever is bound to findNext) press. You prevent a
|
|
|
|
// replace by making sure the match is no longer selected when hitting
|
|
|
|
// Ctrl-G.
|
2020-08-02 18:18:01 +02:00
|
|
|
// =====
|
2018-03-04 20:07:01 +01:00
|
|
|
|
|
|
|
'use strict';
|
|
|
|
|
2020-08-02 18:18:01 +02:00
|
|
|
(function(CodeMirror) {
|
|
|
|
|
|
|
|
const searchOverlay = function(query, caseInsensitive) {
|
|
|
|
if ( typeof query === 'string' )
|
|
|
|
query = new RegExp(
|
|
|
|
query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'),
|
|
|
|
caseInsensitive ? 'gi' : 'g'
|
|
|
|
);
|
|
|
|
else if ( !query.global )
|
|
|
|
query = new RegExp(query.source, query.ignoreCase ? 'gi' : 'g');
|
2018-03-04 20:07:01 +01:00
|
|
|
|
|
|
|
return {
|
|
|
|
token: function(stream) {
|
|
|
|
query.lastIndex = stream.pos;
|
2020-08-02 18:18:01 +02:00
|
|
|
const match = query.exec(stream.string);
|
|
|
|
if ( match && match.index === stream.pos ) {
|
2018-03-04 20:07:01 +01:00
|
|
|
stream.pos += match[0].length || 1;
|
2020-08-02 18:18:01 +02:00
|
|
|
return 'searching';
|
|
|
|
} else if ( match ) {
|
2018-03-04 20:07:01 +01:00
|
|
|
stream.pos = match.index;
|
|
|
|
} else {
|
|
|
|
stream.skipToEnd();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2020-08-02 18:18:01 +02:00
|
|
|
};
|
2018-03-04 20:07:01 +01:00
|
|
|
|
2020-08-02 18:18:01 +02:00
|
|
|
const searchWidgetKeydownHandler = function(cm, ev) {
|
|
|
|
const keyName = CodeMirror.keyName(ev);
|
2018-03-04 20:07:01 +01:00
|
|
|
if ( !keyName ) { return; }
|
|
|
|
CodeMirror.lookupKey(
|
|
|
|
keyName,
|
|
|
|
cm.getOption('keyMap'),
|
|
|
|
function(command) {
|
|
|
|
if ( widgetCommandHandler(cm, command) ) {
|
|
|
|
ev.preventDefault();
|
|
|
|
ev.stopPropagation();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
2020-08-02 18:18:01 +02:00
|
|
|
};
|
2018-03-04 20:07:01 +01:00
|
|
|
|
2020-08-02 18:18:01 +02:00
|
|
|
const searchWidgetInputHandler = function(cm) {
|
2018-07-09 21:21:45 +02:00
|
|
|
let state = getSearchState(cm);
|
|
|
|
if ( queryTextFromSearchWidget(cm) === state.queryText ) { return; }
|
|
|
|
if ( state.queryTimer !== null ) {
|
|
|
|
clearTimeout(state.queryTimer);
|
2018-03-04 20:07:01 +01:00
|
|
|
}
|
2018-07-09 21:21:45 +02:00
|
|
|
state.queryTimer = setTimeout(
|
|
|
|
() => {
|
|
|
|
state.queryTimer = null;
|
|
|
|
findCommit(cm, 0);
|
|
|
|
},
|
|
|
|
350
|
|
|
|
);
|
2020-08-02 18:18:01 +02:00
|
|
|
};
|
2018-03-04 20:07:01 +01:00
|
|
|
|
2020-08-02 18:18:01 +02:00
|
|
|
const searchWidgetClickHandler = function(cm, ev) {
|
|
|
|
const tcl = ev.target.classList;
|
2018-03-04 20:07:01 +01:00
|
|
|
if ( tcl.contains('cm-search-widget-up') ) {
|
2018-07-09 21:21:45 +02:00
|
|
|
findNext(cm, -1);
|
2018-03-04 20:07:01 +01:00
|
|
|
} else if ( tcl.contains('cm-search-widget-down') ) {
|
2018-07-09 21:21:45 +02:00
|
|
|
findNext(cm, 1);
|
2018-03-04 23:13:27 +01:00
|
|
|
}
|
|
|
|
if ( ev.target.localName !== 'input' ) {
|
|
|
|
ev.preventDefault();
|
|
|
|
} else {
|
|
|
|
ev.stopImmediatePropagation();
|
2018-03-04 20:07:01 +01:00
|
|
|
}
|
2020-08-02 18:18:01 +02:00
|
|
|
};
|
2018-03-04 20:07:01 +01:00
|
|
|
|
2020-08-02 18:18:01 +02:00
|
|
|
const queryTextFromSearchWidget = function(cm) {
|
2019-11-03 19:14:29 +01:00
|
|
|
return getSearchState(cm).widget.querySelector('input[type="search"]').value;
|
2020-08-02 18:18:01 +02:00
|
|
|
};
|
2018-03-04 20:07:01 +01:00
|
|
|
|
2020-08-02 18:18:01 +02:00
|
|
|
const queryTextToSearchWidget = function(cm, q) {
|
|
|
|
const input = getSearchState(cm).widget.querySelector('input[type="search"]');
|
2018-03-04 20:07:01 +01:00
|
|
|
if ( typeof q === 'string' && q !== input.value ) {
|
|
|
|
input.value = q;
|
|
|
|
}
|
|
|
|
input.setSelectionRange(0, input.value.length);
|
|
|
|
input.focus();
|
2020-08-02 18:18:01 +02:00
|
|
|
};
|
2018-03-04 20:07:01 +01:00
|
|
|
|
2020-08-02 18:18:01 +02:00
|
|
|
const SearchState = function(cm) {
|
2018-03-04 20:07:01 +01:00
|
|
|
this.query = null;
|
|
|
|
this.panel = null;
|
2020-08-02 18:18:01 +02:00
|
|
|
const widgetParent = document.querySelector('.cm-search-widget-template').cloneNode(true);
|
2019-05-24 01:29:59 +02:00
|
|
|
this.widget = widgetParent.children[0];
|
2018-03-04 20:07:01 +01:00
|
|
|
this.widget.addEventListener('keydown', searchWidgetKeydownHandler.bind(null, cm));
|
|
|
|
this.widget.addEventListener('input', searchWidgetInputHandler.bind(null, cm));
|
2018-03-04 23:13:27 +01:00
|
|
|
this.widget.addEventListener('mousedown', searchWidgetClickHandler.bind(null, cm));
|
2018-03-04 20:07:01 +01:00
|
|
|
if ( typeof cm.addPanel === 'function' ) {
|
|
|
|
this.panel = cm.addPanel(this.widget);
|
|
|
|
}
|
|
|
|
this.queryText = '';
|
|
|
|
this.queryTimer = null;
|
2020-08-02 18:18:01 +02:00
|
|
|
this.dirty = true;
|
|
|
|
this.lines = [];
|
|
|
|
cm.on('changes', (cm, changes) => {
|
|
|
|
for ( const change of changes ) {
|
|
|
|
if ( change.text.length !== 0 || change.removed !== 0 ) {
|
|
|
|
this.dirty = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
cm.on('cursorActivity', cm => {
|
2020-08-03 14:55:02 +02:00
|
|
|
updateCount(cm);
|
2020-08-02 18:18:01 +02:00
|
|
|
});
|
|
|
|
};
|
2018-03-04 20:07:01 +01:00
|
|
|
|
|
|
|
// We want the search widget to behave as if the focus was on the
|
|
|
|
// CodeMirror editor.
|
|
|
|
|
2019-05-24 01:29:59 +02:00
|
|
|
const reSearchCommands = /^(?:find|findNext|findPrev|newlineAndIndent)$/;
|
2018-03-04 20:07:01 +01:00
|
|
|
|
2020-08-02 18:18:01 +02:00
|
|
|
const widgetCommandHandler = function(cm, command) {
|
2018-03-04 20:07:01 +01:00
|
|
|
if ( reSearchCommands.test(command) === false ) { return false; }
|
2020-08-02 18:18:01 +02:00
|
|
|
const queryText = queryTextFromSearchWidget(cm);
|
2018-03-04 20:07:01 +01:00
|
|
|
if ( command === 'find' ) {
|
|
|
|
queryTextToSearchWidget(cm);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if ( queryText.length !== 0 ) {
|
2018-07-09 21:21:45 +02:00
|
|
|
findNext(cm, command === 'findPrev' ? -1 : 1);
|
2018-03-04 20:07:01 +01:00
|
|
|
}
|
|
|
|
return true;
|
2020-08-02 18:18:01 +02:00
|
|
|
};
|
2018-03-04 20:07:01 +01:00
|
|
|
|
2020-08-02 18:18:01 +02:00
|
|
|
const getSearchState = function(cm) {
|
2018-03-04 20:07:01 +01:00
|
|
|
return cm.state.search || (cm.state.search = new SearchState(cm));
|
2020-08-02 18:18:01 +02:00
|
|
|
};
|
2018-03-04 20:07:01 +01:00
|
|
|
|
2020-08-02 18:18:01 +02:00
|
|
|
const queryCaseInsensitive = function(query) {
|
|
|
|
return typeof query === 'string' && query === query.toLowerCase();
|
|
|
|
};
|
2018-03-04 20:07:01 +01:00
|
|
|
|
2020-08-02 18:18:01 +02:00
|
|
|
// Heuristic: if the query string is all lowercase, do a case insensitive search.
|
|
|
|
const getSearchCursor = function(cm, query, pos) {
|
2018-03-04 21:52:34 +01:00
|
|
|
return cm.getSearchCursor(
|
|
|
|
query,
|
|
|
|
pos,
|
2019-04-24 01:26:02 +02:00
|
|
|
{ caseFold: queryCaseInsensitive(query), multiline: false }
|
2018-03-04 21:52:34 +01:00
|
|
|
);
|
2020-08-02 18:18:01 +02:00
|
|
|
};
|
2018-03-04 20:07:01 +01:00
|
|
|
|
2019-07-07 12:29:14 +02:00
|
|
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/658
|
|
|
|
// Modified to backslash-escape ONLY widely-used control characters.
|
2020-08-02 18:18:01 +02:00
|
|
|
const parseString = function(string) {
|
|
|
|
return string.replace(/\\[nrt\\]/g, match => {
|
|
|
|
if ( match === '\\n' ) { return '\n'; }
|
|
|
|
if ( match === '\\r' ) { return '\r'; }
|
|
|
|
if ( match === '\\t' ) { return '\t'; }
|
|
|
|
if ( match === '\\\\' ) { return '\\'; }
|
2019-07-11 15:50:12 +02:00
|
|
|
return match;
|
2018-03-04 20:07:01 +01:00
|
|
|
});
|
2020-08-02 18:18:01 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
const reEscape = /[.*+\-?^${}()|[\]\\]/g;
|
|
|
|
|
|
|
|
// Must always return a RegExp object.
|
|
|
|
//
|
|
|
|
// Assume case-sensitivity if there is at least one uppercase in plain
|
|
|
|
// query text.
|
|
|
|
const parseQuery = function(query) {
|
|
|
|
let flags = 'i';
|
|
|
|
let reParsed = query.match(/^\/(.+)\/([iu]*)$/);
|
|
|
|
if ( reParsed !== null ) {
|
2020-06-13 17:13:48 +02:00
|
|
|
try {
|
2020-08-02 18:18:01 +02:00
|
|
|
const re = new RegExp(reParsed[1], reParsed[2]);
|
|
|
|
query = re.source;
|
|
|
|
flags = re.flags;
|
2020-06-13 17:13:48 +02:00
|
|
|
}
|
|
|
|
catch (e) {
|
2020-08-02 18:18:01 +02:00
|
|
|
reParsed = null;
|
2020-06-13 17:13:48 +02:00
|
|
|
}
|
|
|
|
}
|
2020-08-02 18:18:01 +02:00
|
|
|
if ( reParsed === null ) {
|
|
|
|
if ( /[A-Z]/.test(query) ) { flags = ''; }
|
|
|
|
query = parseString(query).replace(reEscape, '\\$&');
|
2018-03-04 20:07:01 +01:00
|
|
|
}
|
2020-06-13 17:13:48 +02:00
|
|
|
if ( typeof query === 'string' ? query === '' : query.test('') ) {
|
2020-08-02 18:18:01 +02:00
|
|
|
query = 'x^';
|
2020-06-13 17:13:48 +02:00
|
|
|
}
|
2020-08-02 18:18:01 +02:00
|
|
|
return new RegExp(query, 'gm' + flags);
|
|
|
|
};
|
|
|
|
|
|
|
|
let intlNumberFormat;
|
|
|
|
|
|
|
|
const formatNumber = function(n) {
|
|
|
|
if ( intlNumberFormat === undefined ) {
|
|
|
|
intlNumberFormat = null;
|
|
|
|
if ( Intl.NumberFormat instanceof Function ) {
|
|
|
|
const intl = new Intl.NumberFormat(undefined, {
|
|
|
|
notation: 'compact',
|
|
|
|
maximumSignificantDigits: 3
|
|
|
|
});
|
|
|
|
if (
|
|
|
|
intl.resolvedOptions instanceof Function &&
|
|
|
|
intl.resolvedOptions().hasOwnProperty('notation')
|
|
|
|
) {
|
|
|
|
intlNumberFormat = intl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return n > 10000 && intlNumberFormat instanceof Object
|
|
|
|
? intlNumberFormat.format(n)
|
|
|
|
: n.toLocaleString();
|
|
|
|
};
|
|
|
|
|
|
|
|
const updateCount = function(cm) {
|
|
|
|
const state = getSearchState(cm);
|
|
|
|
const lines = state.lines;
|
|
|
|
const current = cm.getCursor().line;
|
|
|
|
let l = 0;
|
|
|
|
let r = lines.length;
|
|
|
|
let i = -1;
|
|
|
|
while ( l < r ) {
|
|
|
|
i = l + r >>> 1;
|
|
|
|
const candidate = lines[i];
|
|
|
|
if ( current === candidate ) { break; }
|
|
|
|
if ( current < candidate ) {
|
|
|
|
r = i;
|
|
|
|
} else /* if ( current > candidate ) */ {
|
|
|
|
l = i + 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let text = '';
|
|
|
|
if ( i !== -1 ) {
|
|
|
|
text = formatNumber(i + 1);
|
|
|
|
if ( lines[i] !== current ) {
|
|
|
|
text = '~' + text;
|
|
|
|
}
|
|
|
|
text = text + '\xA0/\xA0';
|
|
|
|
}
|
2020-08-03 14:55:02 +02:00
|
|
|
const count = lines.length;
|
|
|
|
text += formatNumber(count);
|
|
|
|
const span = state.widget.querySelector('.cm-search-widget-count');
|
2020-08-02 18:18:01 +02:00
|
|
|
span.textContent = text;
|
2020-08-03 14:55:02 +02:00
|
|
|
span.title = count.toLocaleString();
|
2020-08-02 18:18:01 +02:00
|
|
|
};
|
2018-03-04 20:07:01 +01:00
|
|
|
|
2020-08-02 18:18:01 +02:00
|
|
|
const startSearch = function(cm, state) {
|
2018-03-04 20:07:01 +01:00
|
|
|
state.query = parseQuery(state.queryText);
|
2020-08-02 18:18:01 +02:00
|
|
|
if ( state.overlay !== undefined ) {
|
2018-03-04 20:07:01 +01:00
|
|
|
cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query));
|
|
|
|
}
|
|
|
|
state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query));
|
|
|
|
cm.addOverlay(state.overlay);
|
2020-08-02 18:46:52 +02:00
|
|
|
if ( state.dirty || self.searchThread.needHaystack() ) {
|
2020-08-02 18:18:01 +02:00
|
|
|
self.searchThread.setHaystack(cm.getValue());
|
|
|
|
state.dirty = false;
|
|
|
|
}
|
|
|
|
self.searchThread.search(state.query).then(lines => {
|
|
|
|
if ( Array.isArray(lines) === false ) { return; }
|
|
|
|
state.lines = lines;
|
|
|
|
const count = lines.length;
|
|
|
|
updateCount(cm);
|
|
|
|
if ( state.annotate !== undefined ) {
|
2018-03-04 20:07:01 +01:00
|
|
|
state.annotate.clear();
|
2020-08-02 18:18:01 +02:00
|
|
|
state.annotate = undefined;
|
2018-03-04 20:07:01 +01:00
|
|
|
}
|
2020-08-02 18:18:01 +02:00
|
|
|
if ( count === 0 ) { return; }
|
|
|
|
state.annotate = cm.annotateScrollbar('CodeMirror-search-match');
|
|
|
|
const annotations = [];
|
|
|
|
let lineBeg = -1;
|
|
|
|
let lineEnd = -1;
|
|
|
|
for ( const line of lines ) {
|
|
|
|
if ( lineBeg === -1 ) {
|
|
|
|
lineBeg = line;
|
|
|
|
lineEnd = line + 1;
|
|
|
|
continue;
|
|
|
|
} else if ( line === lineEnd ) {
|
|
|
|
lineEnd = line + 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
annotations.push({
|
|
|
|
from: { line: lineBeg, ch: 0 },
|
|
|
|
to: { line: lineEnd, ch: 0 }
|
|
|
|
});
|
|
|
|
lineBeg = -1;
|
|
|
|
}
|
|
|
|
if ( lineBeg !== -1 ) {
|
|
|
|
annotations.push({
|
|
|
|
from: { line: lineBeg, ch: 0 },
|
|
|
|
to: { line: lineEnd, ch: 0 }
|
|
|
|
});
|
|
|
|
}
|
|
|
|
state.annotate.update(annotations);
|
|
|
|
});
|
|
|
|
state.widget.setAttribute('data-query', state.queryText);
|
|
|
|
// Ensure the caret is visible
|
2020-08-03 14:55:02 +02:00
|
|
|
const input = state.widget.querySelector('.cm-search-widget-input input');
|
2020-08-02 18:18:01 +02:00
|
|
|
input.selectionStart = input.selectionStart;
|
|
|
|
};
|
2018-03-04 20:07:01 +01:00
|
|
|
|
2020-08-02 18:18:01 +02:00
|
|
|
const findNext = function(cm, dir, callback) {
|
2018-03-04 20:07:01 +01:00
|
|
|
cm.operation(function() {
|
2020-08-02 18:18:01 +02:00
|
|
|
const state = getSearchState(cm);
|
2018-03-04 23:13:27 +01:00
|
|
|
if ( !state.query ) { return; }
|
2020-08-02 18:18:01 +02:00
|
|
|
let cursor = getSearchCursor(
|
2018-03-04 21:52:34 +01:00
|
|
|
cm,
|
|
|
|
state.query,
|
2018-07-09 21:21:45 +02:00
|
|
|
dir <= 0 ? cm.getCursor('from') : cm.getCursor('to')
|
2018-03-04 21:52:34 +01:00
|
|
|
);
|
2020-08-02 18:18:01 +02:00
|
|
|
const previous = dir < 0;
|
2018-07-09 21:21:45 +02:00
|
|
|
if (!cursor.find(previous)) {
|
2018-03-04 20:07:01 +01:00
|
|
|
cursor = getSearchCursor(
|
|
|
|
cm,
|
|
|
|
state.query,
|
2020-08-02 18:18:01 +02:00
|
|
|
previous
|
|
|
|
? CodeMirror.Pos(cm.lastLine())
|
|
|
|
: CodeMirror.Pos(cm.firstLine(), 0)
|
2018-03-04 20:07:01 +01:00
|
|
|
);
|
2018-07-09 21:21:45 +02:00
|
|
|
if (!cursor.find(previous)) return;
|
2018-03-04 20:07:01 +01:00
|
|
|
}
|
|
|
|
cm.setSelection(cursor.from(), cursor.to());
|
2020-08-04 16:14:38 +02:00
|
|
|
const { clientHeight } = cm.getScrollInfo();
|
|
|
|
cm.scrollIntoView(
|
|
|
|
{ from: cursor.from(), to: cursor.to() },
|
|
|
|
clientHeight >>> 1
|
|
|
|
);
|
2018-03-04 20:07:01 +01:00
|
|
|
if (callback) callback(cursor.from(), cursor.to());
|
|
|
|
});
|
2020-08-02 18:18:01 +02:00
|
|
|
};
|
2018-03-04 20:07:01 +01:00
|
|
|
|
2020-08-02 18:18:01 +02:00
|
|
|
const clearSearch = function(cm, hard) {
|
2018-03-04 20:07:01 +01:00
|
|
|
cm.operation(function() {
|
2020-08-02 18:18:01 +02:00
|
|
|
const state = getSearchState(cm);
|
2018-03-04 20:07:01 +01:00
|
|
|
if ( state.query ) {
|
|
|
|
state.query = state.queryText = null;
|
|
|
|
}
|
2020-08-02 18:18:01 +02:00
|
|
|
state.lines = [];
|
|
|
|
if ( state.overlay !== undefined ) {
|
2018-03-04 20:07:01 +01:00
|
|
|
cm.removeOverlay(state.overlay);
|
2020-08-02 18:18:01 +02:00
|
|
|
state.overlay = undefined;
|
2018-03-04 20:07:01 +01:00
|
|
|
}
|
|
|
|
if ( state.annotate ) {
|
|
|
|
state.annotate.clear();
|
2020-08-02 18:18:01 +02:00
|
|
|
state.annotate = undefined;
|
2018-03-04 20:07:01 +01:00
|
|
|
}
|
|
|
|
state.widget.removeAttribute('data-query');
|
|
|
|
if ( hard ) {
|
|
|
|
state.panel.clear();
|
|
|
|
state.panel = null;
|
|
|
|
state.widget = null;
|
|
|
|
cm.state.search = null;
|
|
|
|
}
|
|
|
|
});
|
2020-08-02 18:18:01 +02:00
|
|
|
};
|
2018-03-04 20:07:01 +01:00
|
|
|
|
2020-08-02 18:18:01 +02:00
|
|
|
const findCommit = function(cm, dir) {
|
|
|
|
const state = getSearchState(cm);
|
2018-03-04 20:07:01 +01:00
|
|
|
if ( state.queryTimer !== null ) {
|
|
|
|
clearTimeout(state.queryTimer);
|
|
|
|
state.queryTimer = null;
|
|
|
|
}
|
2020-08-02 18:18:01 +02:00
|
|
|
const queryText = queryTextFromSearchWidget(cm);
|
2018-03-04 20:07:01 +01:00
|
|
|
if ( queryText === state.queryText ) { return; }
|
|
|
|
state.queryText = queryText;
|
|
|
|
if ( state.queryText === '' ) {
|
|
|
|
clearSearch(cm);
|
|
|
|
} else {
|
|
|
|
cm.operation(function() {
|
|
|
|
startSearch(cm, state);
|
2018-07-09 21:21:45 +02:00
|
|
|
findNext(cm, dir);
|
2018-03-04 20:07:01 +01:00
|
|
|
});
|
|
|
|
}
|
2020-08-02 18:18:01 +02:00
|
|
|
};
|
2018-03-04 20:07:01 +01:00
|
|
|
|
2020-08-02 18:18:01 +02:00
|
|
|
const findCommand = function(cm) {
|
|
|
|
let queryText = cm.getSelection() || undefined;
|
2018-03-04 20:07:01 +01:00
|
|
|
if ( !queryText ) {
|
2020-08-02 18:18:01 +02:00
|
|
|
const word = cm.findWordAt(cm.getCursor());
|
2018-03-04 20:07:01 +01:00
|
|
|
queryText = cm.getRange(word.anchor, word.head);
|
|
|
|
if ( /^\W|\W$/.test(queryText) ) {
|
|
|
|
queryText = undefined;
|
|
|
|
}
|
|
|
|
cm.setCursor(word.anchor);
|
|
|
|
}
|
|
|
|
queryTextToSearchWidget(cm, queryText);
|
2018-07-09 21:21:45 +02:00
|
|
|
findCommit(cm, 1);
|
2020-08-02 18:18:01 +02:00
|
|
|
};
|
2018-03-04 20:07:01 +01:00
|
|
|
|
2020-08-02 18:18:01 +02:00
|
|
|
const findNextCommand = function(cm) {
|
|
|
|
const state = getSearchState(cm);
|
2018-07-09 21:21:45 +02:00
|
|
|
if ( state.query ) { return findNext(cm, 1); }
|
2020-08-02 18:18:01 +02:00
|
|
|
};
|
2018-03-04 20:07:01 +01:00
|
|
|
|
2020-08-02 18:18:01 +02:00
|
|
|
const findPrevCommand = function(cm) {
|
|
|
|
const state = getSearchState(cm);
|
2018-07-09 21:21:45 +02:00
|
|
|
if ( state.query ) { return findNext(cm, -1); }
|
2020-08-02 18:18:01 +02:00
|
|
|
};
|
2018-03-04 20:07:01 +01:00
|
|
|
|
2019-05-24 01:29:59 +02:00
|
|
|
{
|
|
|
|
const searchWidgetTemplate =
|
|
|
|
'<div class="cm-search-widget-template" style="display:none;">' +
|
|
|
|
'<div class="cm-search-widget">' +
|
|
|
|
'<span class="cm-search-widget-input">' +
|
2020-08-03 14:55:02 +02:00
|
|
|
'<span class="fa-icon fa-icon-ro">search</span> ' +
|
|
|
|
'<input type="search" spellcheck="false"> ' +
|
|
|
|
'<span class="cm-search-widget-up cm-search-widget-button fa-icon">angle-up</span> ' +
|
|
|
|
'<span class="cm-search-widget-down cm-search-widget-button fa-icon fa-icon-vflipped">angle-up</span> ' +
|
|
|
|
'<span class="cm-search-widget-count"></span>' +
|
|
|
|
'</span>' +
|
2019-05-24 01:29:59 +02:00
|
|
|
'<a class="fa-icon sourceURL" href>external-link</a>' +
|
|
|
|
'</div>' +
|
|
|
|
'</div>';
|
|
|
|
const domParser = new DOMParser();
|
|
|
|
const doc = domParser.parseFromString(searchWidgetTemplate, 'text/html');
|
|
|
|
const widgetTemplate = document.adoptNode(doc.body.firstElementChild);
|
|
|
|
document.body.appendChild(widgetTemplate);
|
|
|
|
}
|
|
|
|
|
2018-03-04 20:07:01 +01:00
|
|
|
CodeMirror.commands.find = findCommand;
|
|
|
|
CodeMirror.commands.findNext = findNextCommand;
|
|
|
|
CodeMirror.commands.findPrev = findPrevCommand;
|
2018-03-04 23:13:27 +01:00
|
|
|
|
|
|
|
CodeMirror.defineInitHook(function(cm) {
|
|
|
|
getSearchState(cm);
|
|
|
|
});
|
2020-08-02 18:18:01 +02:00
|
|
|
})(self.CodeMirror);
|