mirror of https://github.com/gorhill/uBlock.git
address #3567
This commit is contained in:
parent
d9350d7847
commit
bc61bef9a7
|
@ -6,7 +6,6 @@
|
||||||
<title>uBlock — Your filters</title>
|
<title>uBlock — Your filters</title>
|
||||||
|
|
||||||
<link rel="stylesheet" type="text/css" href="lib/codemirror/lib/codemirror.css">
|
<link rel="stylesheet" type="text/css" href="lib/codemirror/lib/codemirror.css">
|
||||||
<link rel="stylesheet" type="text/css" href="lib/codemirror/addon/dialog/dialog.css">
|
|
||||||
|
|
||||||
<link rel="stylesheet" type="text/css" href="css/common.css">
|
<link rel="stylesheet" type="text/css" href="css/common.css">
|
||||||
<link rel="stylesheet" type="text/css" href="css/dashboard-common.css">
|
<link rel="stylesheet" type="text/css" href="css/dashboard-common.css">
|
||||||
|
@ -32,10 +31,13 @@
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<script src="lib/codemirror/lib/codemirror.js"></script>
|
<script src="lib/codemirror/lib/codemirror.js"></script>
|
||||||
<script src="lib/codemirror/addon/dialog/dialog.js"></script>
|
<script src="lib/codemirror/addon/display/panel.js"></script>
|
||||||
<script src="lib/codemirror/addon/search/search.js"></script>
|
|
||||||
<script src="lib/codemirror/addon/search/searchcursor.js"></script>
|
<script src="lib/codemirror/addon/search/searchcursor.js"></script>
|
||||||
<script src="lib/codemirror/addon/mode/ubo-static-filtering.js"></script>
|
<script src="lib/codemirror/addon/scroll/annotatescrollbar.js"></script>
|
||||||
|
<script src="lib/codemirror/addon/search/matchesonscrollbar.js"></script>
|
||||||
|
|
||||||
|
<script src="js/codemirror/search.js"></script>
|
||||||
|
<script src="js/codemirror/ubo-static-filtering.js"></script>
|
||||||
|
|
||||||
<script src="js/vapi.js"></script>
|
<script src="js/vapi.js"></script>
|
||||||
<script src="js/vapi-common.js"></script>
|
<script src="js/vapi-common.js"></script>
|
||||||
|
|
|
@ -3,9 +3,10 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>uBlock — Asset</title>
|
<title>uBlock — Asset</title>
|
||||||
<link rel="stylesheet" type="text/css" href="lib/codemirror/lib/codemirror.css">
|
<link rel="stylesheet" href="lib/codemirror/lib/codemirror.css">
|
||||||
<link rel="stylesheet" type="text/css" href="lib/codemirror/addon/dialog/dialog.css">
|
<link rel="stylesheet" href="lib/codemirror/addon/search/matchesonscrollbar.css">
|
||||||
<link rel="stylesheet" type="text/css" href="css/codemirror.css">
|
|
||||||
|
<link rel="stylesheet" href="css/codemirror.css">
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
border: 0;
|
border: 0;
|
||||||
|
@ -19,13 +20,17 @@ body {
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div id="content" class="codeMirrorContainer"></div>
|
<div id="content" class="codeMirrorContainer"></div>
|
||||||
|
|
||||||
<script src="lib/codemirror/lib/codemirror.js"></script>
|
<script src="lib/codemirror/lib/codemirror.js"></script>
|
||||||
<script src="lib/codemirror/addon/dialog/dialog.js"></script>
|
<script src="lib/codemirror/addon/display/panel.js"></script>
|
||||||
<script src="lib/codemirror/addon/search/search.js"></script>
|
|
||||||
<script src="lib/codemirror/addon/search/searchcursor.js"></script>
|
<script src="lib/codemirror/addon/search/searchcursor.js"></script>
|
||||||
<script src="lib/codemirror/addon/mode/ubo-static-filtering.js"></script>
|
<script src="lib/codemirror/addon/scroll/annotatescrollbar.js"></script>
|
||||||
|
<script src="lib/codemirror/addon/search/matchesonscrollbar.js"></script>
|
||||||
|
|
||||||
|
<script src="js/codemirror/search.js"></script>
|
||||||
|
<script src="js/codemirror/ubo-static-filtering.js"></script>
|
||||||
|
|
||||||
<script src="js/vapi.js"></script>
|
<script src="js/vapi.js"></script>
|
||||||
<script src="js/vapi-common.js"></script>
|
<script src="js/vapi-common.js"></script>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
.codeMirrorContainer {
|
.codeMirrorContainer {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
.CodeMirror {
|
.CodeMirror {
|
||||||
border: 1px solid #ddd;
|
border: 1px solid #ddd;
|
||||||
|
@ -12,3 +13,50 @@
|
||||||
.cm-staticext {color: #008;}
|
.cm-staticext {color: #008;}
|
||||||
.cm-staticnet.cm-block {color: #800;}
|
.cm-staticnet.cm-block {color: #800;}
|
||||||
.cm-staticnet.cm-allow {color: #004f00;}
|
.cm-staticnet.cm-allow {color: #004f00;}
|
||||||
|
|
||||||
|
|
||||||
|
.cm-search-widget {
|
||||||
|
align-items: center;
|
||||||
|
background-color: #eee;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 4px 8px;
|
||||||
|
/* position: absolute; */
|
||||||
|
right: 2em;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
.cm-search-widget > span {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.cm-search-widget .cm-search-widget-count {
|
||||||
|
align-items: center;
|
||||||
|
bottom: 0;
|
||||||
|
color: #888;
|
||||||
|
display: none;
|
||||||
|
margin-right: 4px;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
.cm-search-widget[data-query] .cm-search-widget-count {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.cm-search-widget .cm-search-widget-svg {
|
||||||
|
height: 1.2em;
|
||||||
|
padding: 4px;
|
||||||
|
width: 1.2em;
|
||||||
|
}
|
||||||
|
.cm-search-widget .cm-search-widget-arrow {
|
||||||
|
padding-left: 2px;
|
||||||
|
padding-right: 2px;
|
||||||
|
width: 1.4em;
|
||||||
|
}
|
||||||
|
.cm-search-widget .cm-search-widget-svg:hover {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
.cm-search-widget svg {
|
||||||
|
height: 100%;
|
||||||
|
pointer-events: none;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@ var cachedUserFilters = '';
|
||||||
var cmEditor = new CodeMirror(
|
var cmEditor = new CodeMirror(
|
||||||
document.getElementById('userFilters'),
|
document.getElementById('userFilters'),
|
||||||
{
|
{
|
||||||
|
autofocus: true,
|
||||||
lineNumbers: true,
|
lineNumbers: true,
|
||||||
lineWrapping: true,
|
lineWrapping: true,
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,7 @@
|
||||||
var cmEditor = new CodeMirror(
|
var cmEditor = new CodeMirror(
|
||||||
document.getElementById('content'),
|
document.getElementById('content'),
|
||||||
{
|
{
|
||||||
|
autofocus: true,
|
||||||
lineNumbers: true,
|
lineNumbers: true,
|
||||||
lineWrapping: true,
|
lineWrapping: true,
|
||||||
readOnly: true
|
readOnly: true
|
||||||
|
|
|
@ -0,0 +1,316 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
/* globals define, require, CodeMirror */
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports === "object" && typeof module === "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"), require("./searchcursor"), require("../dialog/dialog"));
|
||||||
|
else if (typeof define === "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror", "./searchcursor", "../dialog/dialog"], mod);
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
|
||||||
|
function searchOverlay(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");
|
||||||
|
|
||||||
|
return {
|
||||||
|
token: function(stream) {
|
||||||
|
query.lastIndex = stream.pos;
|
||||||
|
var match = query.exec(stream.string);
|
||||||
|
if (match && match.index === stream.pos) {
|
||||||
|
stream.pos += match[0].length || 1;
|
||||||
|
return "searching";
|
||||||
|
} else if (match) {
|
||||||
|
stream.pos = match.index;
|
||||||
|
} else {
|
||||||
|
stream.skipToEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var searchWidgetHtml =
|
||||||
|
'<div class="cm-search-widget">' +
|
||||||
|
'<span>' +
|
||||||
|
'<input type="text" size="32">' +
|
||||||
|
'<span class="cm-search-widget-count">' +
|
||||||
|
'<span><!-- future use --></span><span>0</span>' +
|
||||||
|
'</span>' +
|
||||||
|
'</span> ' +
|
||||||
|
'<span class="cm-search-widget-svg cm-search-widget-arrow cm-search-widget-up">' +
|
||||||
|
'<svg xmlns="http://www.w3.org/2000/svg" height="32" width="32" viewBox="0 0 32 32" preserveAspectRatio="none"><path d="M2,28l14,-24l14,24" style="fill:none;stroke:#000000;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round;" /></svg>' +
|
||||||
|
'</span> ' +
|
||||||
|
'<span class="cm-search-widget-svg cm-search-widget-arrow cm-search-widget-down">' +
|
||||||
|
'<svg xmlns="http://www.w3.org/2000/svg" height="32" width="32" viewBox="0 0 32 32" preserveAspectRatio="none"><path d="M2,4l14,24l14,-24" style="fill:none;stroke:#000000;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round;" /></svg>' +
|
||||||
|
'</span>' +
|
||||||
|
'<span class="cm-search-widget-svg cm-search-widget-close">' +
|
||||||
|
'<svg xmlns="http://www.w3.org/2000/svg" height="32" width="32" viewBox="0 0 32 32" preserveAspectRatio="none"><path d="M4,4l24,24M4,28l24,-24" style="fill:none;stroke:#000000;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round;" /></svg>' +
|
||||||
|
'</span>' +
|
||||||
|
'</div>';
|
||||||
|
|
||||||
|
function searchWidgetKeydownHandler(cm, ev) {
|
||||||
|
var keyName = CodeMirror.keyName(ev);
|
||||||
|
if ( !keyName ) { return; }
|
||||||
|
CodeMirror.lookupKey(
|
||||||
|
keyName,
|
||||||
|
cm.getOption('keyMap'),
|
||||||
|
function(command) {
|
||||||
|
if ( widgetCommandHandler(cm, command) ) {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchWidgetTimerHandler(cm) {
|
||||||
|
var state = getSearchState(cm);
|
||||||
|
state.queryTimer = null;
|
||||||
|
findCommit(cm);
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchWidgetInputHandler(cm) {
|
||||||
|
var state = getSearchState(cm);
|
||||||
|
if ( queryTextFromSearchWidget(cm) !== state.queryText ) {
|
||||||
|
if ( state.queryTimer !== null ) {
|
||||||
|
clearTimeout(state.queryTimer);
|
||||||
|
}
|
||||||
|
state.queryTimer = setTimeout(
|
||||||
|
searchWidgetTimerHandler.bind(null, cm),
|
||||||
|
350
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchWidgetClickHandler(cm, ev) {
|
||||||
|
var tcl = ev.target.classList;
|
||||||
|
if ( tcl.contains('cm-search-widget-up') ) {
|
||||||
|
findNext(cm, true);
|
||||||
|
} else if ( tcl.contains('cm-search-widget-down') ) {
|
||||||
|
findNext(cm, false);
|
||||||
|
} else if ( tcl.contains('cm-search-widget-close') ) {
|
||||||
|
clearSearch(cm, true);
|
||||||
|
cm.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function queryTextFromSearchWidget(cm) {
|
||||||
|
return getSearchState(cm).widget.querySelector('input[type="text"]').value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function queryTextToSearchWidget(cm, q) {
|
||||||
|
var input = getSearchState(cm).widget.querySelector('input[type="text"]');
|
||||||
|
if ( typeof q === 'string' && q !== input.value ) {
|
||||||
|
input.value = q;
|
||||||
|
}
|
||||||
|
input.setSelectionRange(0, input.value.length);
|
||||||
|
input.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function SearchState(cm) {
|
||||||
|
this.posFrom = this.posTo = null;
|
||||||
|
this.query = null;
|
||||||
|
this.overlay = null;
|
||||||
|
this.panel = null;
|
||||||
|
this.widget = null;
|
||||||
|
var domParser = new DOMParser();
|
||||||
|
var doc = domParser.parseFromString(searchWidgetHtml, 'text/html');
|
||||||
|
this.widget = document.adoptNode(doc.body.firstElementChild);
|
||||||
|
this.widget.addEventListener('keydown', searchWidgetKeydownHandler.bind(null, cm));
|
||||||
|
this.widget.addEventListener('input', searchWidgetInputHandler.bind(null, cm));
|
||||||
|
this.widget.addEventListener('click', searchWidgetClickHandler.bind(null, cm));
|
||||||
|
if ( typeof cm.addPanel === 'function' ) {
|
||||||
|
this.panel = cm.addPanel(this.widget);
|
||||||
|
}
|
||||||
|
this.queryText = '';
|
||||||
|
this.queryTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We want the search widget to behave as if the focus was on the
|
||||||
|
// CodeMirror editor.
|
||||||
|
|
||||||
|
var reSearchCommands = /^(?:find|findNext|findPrev|newlineAndIndent)$/;
|
||||||
|
|
||||||
|
function widgetCommandHandler(cm, command) {
|
||||||
|
if ( reSearchCommands.test(command) === false ) { return false; }
|
||||||
|
var queryText = queryTextFromSearchWidget(cm);
|
||||||
|
if ( command === 'find' ) {
|
||||||
|
queryTextToSearchWidget(cm);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ( queryText.length !== 0 ) {
|
||||||
|
findNext(cm, command === 'findPrev');
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSearchState(cm) {
|
||||||
|
return cm.state.search || (cm.state.search = new SearchState(cm));
|
||||||
|
}
|
||||||
|
|
||||||
|
function queryCaseInsensitive(query) {
|
||||||
|
return typeof query === "string" && query === query.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSearchCursor(cm, query, pos) {
|
||||||
|
// Heuristic: if the query string is all lowercase, do a case insensitive search.
|
||||||
|
return cm.getSearchCursor(query, pos, {caseFold: queryCaseInsensitive(query), multiline: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseString(string) {
|
||||||
|
return string.replace(/\\(.)/g, function(_, ch) {
|
||||||
|
if (ch === "n") return "\n";
|
||||||
|
if (ch === "r") return "\r";
|
||||||
|
return ch;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseQuery(query) {
|
||||||
|
var isRE = query.match(/^\/(.*)\/([a-z]*)$/);
|
||||||
|
if (isRE) {
|
||||||
|
try { query = new RegExp(isRE[1], isRE[2].indexOf("i") === -1 ? "" : "i"); }
|
||||||
|
catch(e) {} // Not a regular expression after all, do a string search
|
||||||
|
} else {
|
||||||
|
query = parseString(query);
|
||||||
|
}
|
||||||
|
if (typeof query === "string" ? query === "" : query.test(""))
|
||||||
|
query = /x^/;
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
function startSearch(cm, state) {
|
||||||
|
state.query = parseQuery(state.queryText);
|
||||||
|
if ( state.overlay ) {
|
||||||
|
cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query));
|
||||||
|
}
|
||||||
|
state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query));
|
||||||
|
cm.addOverlay(state.overlay);
|
||||||
|
if ( cm.showMatchesOnScrollbar ) {
|
||||||
|
if ( state.annotate ) {
|
||||||
|
state.annotate.clear();
|
||||||
|
state.annotate = null;
|
||||||
|
}
|
||||||
|
state.annotate = cm.showMatchesOnScrollbar(
|
||||||
|
state.query,
|
||||||
|
queryCaseInsensitive(state.query)
|
||||||
|
);
|
||||||
|
state.widget
|
||||||
|
.querySelector('.cm-search-widget-count > span:nth-of-type(2)')
|
||||||
|
.textContent = state.annotate.matches.length;
|
||||||
|
state.widget.setAttribute('data-query', state.queryText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function findNext(cm, rev, callback) {
|
||||||
|
cm.operation(function() {
|
||||||
|
var state = getSearchState(cm);
|
||||||
|
var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo);
|
||||||
|
if (!cursor.find(rev)) {
|
||||||
|
cursor = getSearchCursor(
|
||||||
|
cm,
|
||||||
|
state.query,
|
||||||
|
rev ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0)
|
||||||
|
);
|
||||||
|
if (!cursor.find(rev)) return;
|
||||||
|
}
|
||||||
|
cm.setSelection(cursor.from(), cursor.to());
|
||||||
|
cm.scrollIntoView({from: cursor.from(), to: cursor.to()}, 20);
|
||||||
|
state.posFrom = cursor.from(); state.posTo = cursor.to();
|
||||||
|
if (callback) callback(cursor.from(), cursor.to());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearSearch(cm, hard) {
|
||||||
|
cm.operation(function() {
|
||||||
|
var state = getSearchState(cm);
|
||||||
|
if ( state.query ) {
|
||||||
|
state.query = state.queryText = null;
|
||||||
|
}
|
||||||
|
if ( state.overlay ) {
|
||||||
|
cm.removeOverlay(state.overlay);
|
||||||
|
state.overlay = null;
|
||||||
|
}
|
||||||
|
if ( state.annotate ) {
|
||||||
|
state.annotate.clear();
|
||||||
|
state.annotate = null;
|
||||||
|
}
|
||||||
|
state.widget.removeAttribute('data-query');
|
||||||
|
if ( hard ) {
|
||||||
|
state.panel.clear();
|
||||||
|
state.panel = null;
|
||||||
|
state.widget = null;
|
||||||
|
cm.state.search = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function findCommit(cm) {
|
||||||
|
var state = getSearchState(cm);
|
||||||
|
if ( state.queryTimer !== null ) {
|
||||||
|
clearTimeout(state.queryTimer);
|
||||||
|
state.queryTimer = null;
|
||||||
|
}
|
||||||
|
var queryText = queryTextFromSearchWidget(cm);
|
||||||
|
if ( queryText === state.queryText ) { return; }
|
||||||
|
state.queryText = queryText;
|
||||||
|
if ( state.queryText === '' ) {
|
||||||
|
clearSearch(cm);
|
||||||
|
} else {
|
||||||
|
cm.operation(function() {
|
||||||
|
startSearch(cm, state);
|
||||||
|
state.posFrom = state.posTo = cm.getCursor();
|
||||||
|
findNext(cm, false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function findCommand(cm) {
|
||||||
|
var queryText = cm.getSelection() || undefined;
|
||||||
|
if ( !queryText ) {
|
||||||
|
var word = cm.findWordAt(cm.getCursor());
|
||||||
|
queryText = cm.getRange(word.anchor, word.head);
|
||||||
|
if ( /^\W|\W$/.test(queryText) ) {
|
||||||
|
queryText = undefined;
|
||||||
|
}
|
||||||
|
cm.setCursor(word.anchor);
|
||||||
|
}
|
||||||
|
queryTextToSearchWidget(cm, queryText);
|
||||||
|
findCommit(cm);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findNextCommand(cm) {
|
||||||
|
var state = getSearchState(cm);
|
||||||
|
if ( state.query ) { return findNext(cm, false); }
|
||||||
|
}
|
||||||
|
|
||||||
|
function findPrevCommand(cm) {
|
||||||
|
var state = getSearchState(cm);
|
||||||
|
if ( state.query ) { return findNext(cm, true); }
|
||||||
|
}
|
||||||
|
|
||||||
|
CodeMirror.commands.find = findCommand;
|
||||||
|
CodeMirror.commands.findNext = findNextCommand;
|
||||||
|
CodeMirror.commands.findPrev = findPrevCommand;
|
||||||
|
});
|
|
@ -1,32 +0,0 @@
|
||||||
.CodeMirror-dialog {
|
|
||||||
position: absolute;
|
|
||||||
left: 0; right: 0;
|
|
||||||
background: inherit;
|
|
||||||
z-index: 15;
|
|
||||||
padding: .1em .8em;
|
|
||||||
overflow: hidden;
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-dialog-top {
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-dialog-bottom {
|
|
||||||
border-top: 1px solid #eee;
|
|
||||||
bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-dialog input {
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
background: transparent;
|
|
||||||
width: 20em;
|
|
||||||
color: inherit;
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-dialog button {
|
|
||||||
font-size: 70%;
|
|
||||||
}
|
|
|
@ -1,157 +0,0 @@
|
||||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
|
||||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
|
||||||
|
|
||||||
// Open simple dialogs on top of an editor. Relies on dialog.css.
|
|
||||||
|
|
||||||
(function(mod) {
|
|
||||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
|
||||||
mod(require("../../lib/codemirror"));
|
|
||||||
else if (typeof define == "function" && define.amd) // AMD
|
|
||||||
define(["../../lib/codemirror"], mod);
|
|
||||||
else // Plain browser env
|
|
||||||
mod(CodeMirror);
|
|
||||||
})(function(CodeMirror) {
|
|
||||||
function dialogDiv(cm, template, bottom) {
|
|
||||||
var wrap = cm.getWrapperElement();
|
|
||||||
var dialog;
|
|
||||||
dialog = wrap.appendChild(document.createElement("div"));
|
|
||||||
if (bottom)
|
|
||||||
dialog.className = "CodeMirror-dialog CodeMirror-dialog-bottom";
|
|
||||||
else
|
|
||||||
dialog.className = "CodeMirror-dialog CodeMirror-dialog-top";
|
|
||||||
|
|
||||||
if (typeof template == "string") {
|
|
||||||
dialog.innerHTML = template;
|
|
||||||
} else { // Assuming it's a detached DOM element.
|
|
||||||
dialog.appendChild(template);
|
|
||||||
}
|
|
||||||
return dialog;
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeNotification(cm, newVal) {
|
|
||||||
if (cm.state.currentNotificationClose)
|
|
||||||
cm.state.currentNotificationClose();
|
|
||||||
cm.state.currentNotificationClose = newVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
CodeMirror.defineExtension("openDialog", function(template, callback, options) {
|
|
||||||
if (!options) options = {};
|
|
||||||
|
|
||||||
closeNotification(this, null);
|
|
||||||
|
|
||||||
var dialog = dialogDiv(this, template, options.bottom);
|
|
||||||
var closed = false, me = this;
|
|
||||||
function close(newVal) {
|
|
||||||
if (typeof newVal == 'string') {
|
|
||||||
inp.value = newVal;
|
|
||||||
} else {
|
|
||||||
if (closed) return;
|
|
||||||
closed = true;
|
|
||||||
dialog.parentNode.removeChild(dialog);
|
|
||||||
me.focus();
|
|
||||||
|
|
||||||
if (options.onClose) options.onClose(dialog);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var inp = dialog.getElementsByTagName("input")[0], button;
|
|
||||||
if (inp) {
|
|
||||||
inp.focus();
|
|
||||||
|
|
||||||
if (options.value) {
|
|
||||||
inp.value = options.value;
|
|
||||||
if (options.selectValueOnOpen !== false) {
|
|
||||||
inp.select();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.onInput)
|
|
||||||
CodeMirror.on(inp, "input", function(e) { options.onInput(e, inp.value, close);});
|
|
||||||
if (options.onKeyUp)
|
|
||||||
CodeMirror.on(inp, "keyup", function(e) {options.onKeyUp(e, inp.value, close);});
|
|
||||||
|
|
||||||
CodeMirror.on(inp, "keydown", function(e) {
|
|
||||||
if (options && options.onKeyDown && options.onKeyDown(e, inp.value, close)) { return; }
|
|
||||||
if (e.keyCode == 27 || (options.closeOnEnter !== false && e.keyCode == 13)) {
|
|
||||||
inp.blur();
|
|
||||||
CodeMirror.e_stop(e);
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
if (e.keyCode == 13) callback(inp.value, e);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (options.closeOnBlur !== false) CodeMirror.on(inp, "blur", close);
|
|
||||||
} else if (button = dialog.getElementsByTagName("button")[0]) {
|
|
||||||
CodeMirror.on(button, "click", function() {
|
|
||||||
close();
|
|
||||||
me.focus();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (options.closeOnBlur !== false) CodeMirror.on(button, "blur", close);
|
|
||||||
|
|
||||||
button.focus();
|
|
||||||
}
|
|
||||||
return close;
|
|
||||||
});
|
|
||||||
|
|
||||||
CodeMirror.defineExtension("openConfirm", function(template, callbacks, options) {
|
|
||||||
closeNotification(this, null);
|
|
||||||
var dialog = dialogDiv(this, template, options && options.bottom);
|
|
||||||
var buttons = dialog.getElementsByTagName("button");
|
|
||||||
var closed = false, me = this, blurring = 1;
|
|
||||||
function close() {
|
|
||||||
if (closed) return;
|
|
||||||
closed = true;
|
|
||||||
dialog.parentNode.removeChild(dialog);
|
|
||||||
me.focus();
|
|
||||||
}
|
|
||||||
buttons[0].focus();
|
|
||||||
for (var i = 0; i < buttons.length; ++i) {
|
|
||||||
var b = buttons[i];
|
|
||||||
(function(callback) {
|
|
||||||
CodeMirror.on(b, "click", function(e) {
|
|
||||||
CodeMirror.e_preventDefault(e);
|
|
||||||
close();
|
|
||||||
if (callback) callback(me);
|
|
||||||
});
|
|
||||||
})(callbacks[i]);
|
|
||||||
CodeMirror.on(b, "blur", function() {
|
|
||||||
--blurring;
|
|
||||||
setTimeout(function() { if (blurring <= 0) close(); }, 200);
|
|
||||||
});
|
|
||||||
CodeMirror.on(b, "focus", function() { ++blurring; });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
|
||||||
* openNotification
|
|
||||||
* Opens a notification, that can be closed with an optional timer
|
|
||||||
* (default 5000ms timer) and always closes on click.
|
|
||||||
*
|
|
||||||
* If a notification is opened while another is opened, it will close the
|
|
||||||
* currently opened one and open the new one immediately.
|
|
||||||
*/
|
|
||||||
CodeMirror.defineExtension("openNotification", function(template, options) {
|
|
||||||
closeNotification(this, close);
|
|
||||||
var dialog = dialogDiv(this, template, options && options.bottom);
|
|
||||||
var closed = false, doneTimer;
|
|
||||||
var duration = options && typeof options.duration !== "undefined" ? options.duration : 5000;
|
|
||||||
|
|
||||||
function close() {
|
|
||||||
if (closed) return;
|
|
||||||
closed = true;
|
|
||||||
clearTimeout(doneTimer);
|
|
||||||
dialog.parentNode.removeChild(dialog);
|
|
||||||
}
|
|
||||||
|
|
||||||
CodeMirror.on(dialog, 'click', function(e) {
|
|
||||||
CodeMirror.e_preventDefault(e);
|
|
||||||
close();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (duration)
|
|
||||||
doneTimer = setTimeout(close, duration);
|
|
||||||
|
|
||||||
return close;
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"));
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror"], mod);
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
CodeMirror.defineExtension("addPanel", function(node, options) {
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
|
if (!this.state.panels) initPanels(this);
|
||||||
|
|
||||||
|
var info = this.state.panels;
|
||||||
|
var wrapper = info.wrapper;
|
||||||
|
var cmWrapper = this.getWrapperElement();
|
||||||
|
|
||||||
|
if (options.after instanceof Panel && !options.after.cleared) {
|
||||||
|
wrapper.insertBefore(node, options.before.node.nextSibling);
|
||||||
|
} else if (options.before instanceof Panel && !options.before.cleared) {
|
||||||
|
wrapper.insertBefore(node, options.before.node);
|
||||||
|
} else if (options.replace instanceof Panel && !options.replace.cleared) {
|
||||||
|
wrapper.insertBefore(node, options.replace.node);
|
||||||
|
options.replace.clear();
|
||||||
|
} else if (options.position == "bottom") {
|
||||||
|
wrapper.appendChild(node);
|
||||||
|
} else if (options.position == "before-bottom") {
|
||||||
|
wrapper.insertBefore(node, cmWrapper.nextSibling);
|
||||||
|
} else if (options.position == "after-top") {
|
||||||
|
wrapper.insertBefore(node, cmWrapper);
|
||||||
|
} else {
|
||||||
|
wrapper.insertBefore(node, wrapper.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
var height = (options && options.height) || node.offsetHeight;
|
||||||
|
this._setSize(null, info.heightLeft -= height);
|
||||||
|
info.panels++;
|
||||||
|
if (options.stable && isAtTop(this, node))
|
||||||
|
this.scrollTo(null, this.getScrollInfo().top + height)
|
||||||
|
|
||||||
|
return new Panel(this, node, options, height);
|
||||||
|
});
|
||||||
|
|
||||||
|
function Panel(cm, node, options, height) {
|
||||||
|
this.cm = cm;
|
||||||
|
this.node = node;
|
||||||
|
this.options = options;
|
||||||
|
this.height = height;
|
||||||
|
this.cleared = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Panel.prototype.clear = function() {
|
||||||
|
if (this.cleared) return;
|
||||||
|
this.cleared = true;
|
||||||
|
var info = this.cm.state.panels;
|
||||||
|
this.cm._setSize(null, info.heightLeft += this.height);
|
||||||
|
if (this.options.stable && isAtTop(this.cm, this.node))
|
||||||
|
this.cm.scrollTo(null, this.cm.getScrollInfo().top - this.height)
|
||||||
|
info.wrapper.removeChild(this.node);
|
||||||
|
if (--info.panels == 0) removePanels(this.cm);
|
||||||
|
};
|
||||||
|
|
||||||
|
Panel.prototype.changed = function(height) {
|
||||||
|
var newHeight = height == null ? this.node.offsetHeight : height;
|
||||||
|
var info = this.cm.state.panels;
|
||||||
|
this.cm._setSize(null, info.heightLeft -= (newHeight - this.height));
|
||||||
|
this.height = newHeight;
|
||||||
|
};
|
||||||
|
|
||||||
|
function initPanels(cm) {
|
||||||
|
var wrap = cm.getWrapperElement();
|
||||||
|
var style = window.getComputedStyle ? window.getComputedStyle(wrap) : wrap.currentStyle;
|
||||||
|
var height = parseInt(style.height);
|
||||||
|
var info = cm.state.panels = {
|
||||||
|
setHeight: wrap.style.height,
|
||||||
|
heightLeft: height,
|
||||||
|
panels: 0,
|
||||||
|
wrapper: document.createElement("div")
|
||||||
|
};
|
||||||
|
wrap.parentNode.insertBefore(info.wrapper, wrap);
|
||||||
|
var hasFocus = cm.hasFocus();
|
||||||
|
info.wrapper.appendChild(wrap);
|
||||||
|
if (hasFocus) cm.focus();
|
||||||
|
|
||||||
|
cm._setSize = cm.setSize;
|
||||||
|
if (height != null) cm.setSize = function(width, newHeight) {
|
||||||
|
if (newHeight == null) return this._setSize(width, newHeight);
|
||||||
|
info.setHeight = newHeight;
|
||||||
|
if (typeof newHeight != "number") {
|
||||||
|
var px = /^(\d+\.?\d*)px$/.exec(newHeight);
|
||||||
|
if (px) {
|
||||||
|
newHeight = Number(px[1]);
|
||||||
|
} else {
|
||||||
|
info.wrapper.style.height = newHeight;
|
||||||
|
newHeight = info.wrapper.offsetHeight;
|
||||||
|
info.wrapper.style.height = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cm._setSize(width, info.heightLeft += (newHeight - height));
|
||||||
|
height = newHeight;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function removePanels(cm) {
|
||||||
|
var info = cm.state.panels;
|
||||||
|
cm.state.panels = null;
|
||||||
|
|
||||||
|
var wrap = cm.getWrapperElement();
|
||||||
|
info.wrapper.parentNode.replaceChild(wrap, info.wrapper);
|
||||||
|
wrap.style.height = info.setHeight;
|
||||||
|
cm.setSize = cm._setSize;
|
||||||
|
cm.setSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAtTop(cm, dom) {
|
||||||
|
for (var sibling = dom.nextSibling; sibling; sibling = sibling.nextSibling)
|
||||||
|
if (sibling == cm.getWrapperElement()) return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,122 @@
|
||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"));
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror"], mod);
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
CodeMirror.defineExtension("annotateScrollbar", function(options) {
|
||||||
|
if (typeof options == "string") options = {className: options};
|
||||||
|
return new Annotation(this, options);
|
||||||
|
});
|
||||||
|
|
||||||
|
CodeMirror.defineOption("scrollButtonHeight", 0);
|
||||||
|
|
||||||
|
function Annotation(cm, options) {
|
||||||
|
this.cm = cm;
|
||||||
|
this.options = options;
|
||||||
|
this.buttonHeight = options.scrollButtonHeight || cm.getOption("scrollButtonHeight");
|
||||||
|
this.annotations = [];
|
||||||
|
this.doRedraw = this.doUpdate = null;
|
||||||
|
this.div = cm.getWrapperElement().appendChild(document.createElement("div"));
|
||||||
|
this.div.style.cssText = "position: absolute; right: 0; top: 0; z-index: 7; pointer-events: none";
|
||||||
|
this.computeScale();
|
||||||
|
|
||||||
|
function scheduleRedraw(delay) {
|
||||||
|
clearTimeout(self.doRedraw);
|
||||||
|
self.doRedraw = setTimeout(function() { self.redraw(); }, delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
cm.on("refresh", this.resizeHandler = function() {
|
||||||
|
clearTimeout(self.doUpdate);
|
||||||
|
self.doUpdate = setTimeout(function() {
|
||||||
|
if (self.computeScale()) scheduleRedraw(20);
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
cm.on("markerAdded", this.resizeHandler);
|
||||||
|
cm.on("markerCleared", this.resizeHandler);
|
||||||
|
if (options.listenForChanges !== false)
|
||||||
|
cm.on("change", this.changeHandler = function() {
|
||||||
|
scheduleRedraw(250);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Annotation.prototype.computeScale = function() {
|
||||||
|
var cm = this.cm;
|
||||||
|
var hScale = (cm.getWrapperElement().clientHeight - cm.display.barHeight - this.buttonHeight * 2) /
|
||||||
|
cm.getScrollerElement().scrollHeight
|
||||||
|
if (hScale != this.hScale) {
|
||||||
|
this.hScale = hScale;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Annotation.prototype.update = function(annotations) {
|
||||||
|
this.annotations = annotations;
|
||||||
|
this.redraw();
|
||||||
|
};
|
||||||
|
|
||||||
|
Annotation.prototype.redraw = function(compute) {
|
||||||
|
if (compute !== false) this.computeScale();
|
||||||
|
var cm = this.cm, hScale = this.hScale;
|
||||||
|
|
||||||
|
var frag = document.createDocumentFragment(), anns = this.annotations;
|
||||||
|
|
||||||
|
var wrapping = cm.getOption("lineWrapping");
|
||||||
|
var singleLineH = wrapping && cm.defaultTextHeight() * 1.5;
|
||||||
|
var curLine = null, curLineObj = null;
|
||||||
|
function getY(pos, top) {
|
||||||
|
if (curLine != pos.line) {
|
||||||
|
curLine = pos.line;
|
||||||
|
curLineObj = cm.getLineHandle(curLine);
|
||||||
|
}
|
||||||
|
if ((curLineObj.widgets && curLineObj.widgets.length) ||
|
||||||
|
(wrapping && curLineObj.height > singleLineH))
|
||||||
|
return cm.charCoords(pos, "local")[top ? "top" : "bottom"];
|
||||||
|
var topY = cm.heightAtLine(curLineObj, "local");
|
||||||
|
return topY + (top ? 0 : curLineObj.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastLine = cm.lastLine()
|
||||||
|
if (cm.display.barWidth) for (var i = 0, nextTop; i < anns.length; i++) {
|
||||||
|
var ann = anns[i];
|
||||||
|
if (ann.to.line > lastLine) continue;
|
||||||
|
var top = nextTop || getY(ann.from, true) * hScale;
|
||||||
|
var bottom = getY(ann.to, false) * hScale;
|
||||||
|
while (i < anns.length - 1) {
|
||||||
|
if (anns[i + 1].to.line > lastLine) break;
|
||||||
|
nextTop = getY(anns[i + 1].from, true) * hScale;
|
||||||
|
if (nextTop > bottom + .9) break;
|
||||||
|
ann = anns[++i];
|
||||||
|
bottom = getY(ann.to, false) * hScale;
|
||||||
|
}
|
||||||
|
if (bottom == top) continue;
|
||||||
|
var height = Math.max(bottom - top, 3);
|
||||||
|
|
||||||
|
var elt = frag.appendChild(document.createElement("div"));
|
||||||
|
elt.style.cssText = "position: absolute; right: 0px; width: " + Math.max(cm.display.barWidth - 1, 2) + "px; top: "
|
||||||
|
+ (top + this.buttonHeight) + "px; height: " + height + "px";
|
||||||
|
elt.className = this.options.className;
|
||||||
|
if (ann.id) {
|
||||||
|
elt.setAttribute("annotation-id", ann.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.div.textContent = "";
|
||||||
|
this.div.appendChild(frag);
|
||||||
|
};
|
||||||
|
|
||||||
|
Annotation.prototype.clear = function() {
|
||||||
|
this.cm.off("refresh", this.resizeHandler);
|
||||||
|
this.cm.off("markerAdded", this.resizeHandler);
|
||||||
|
this.cm.off("markerCleared", this.resizeHandler);
|
||||||
|
if (this.changeHandler) this.cm.off("change", this.changeHandler);
|
||||||
|
this.div.parentNode.removeChild(this.div);
|
||||||
|
};
|
||||||
|
});
|
|
@ -0,0 +1,8 @@
|
||||||
|
.CodeMirror-search-match {
|
||||||
|
background: gold;
|
||||||
|
border-top: 1px solid orange;
|
||||||
|
border-bottom: 1px solid orange;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
opacity: .5;
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"), require("./searchcursor"), require("../scroll/annotatescrollbar"));
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror", "./searchcursor", "../scroll/annotatescrollbar"], mod);
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
CodeMirror.defineExtension("showMatchesOnScrollbar", function(query, caseFold, options) {
|
||||||
|
if (typeof options == "string") options = {className: options};
|
||||||
|
if (!options) options = {};
|
||||||
|
return new SearchAnnotation(this, query, caseFold, options);
|
||||||
|
});
|
||||||
|
|
||||||
|
function SearchAnnotation(cm, query, caseFold, options) {
|
||||||
|
this.cm = cm;
|
||||||
|
this.options = options;
|
||||||
|
var annotateOptions = {listenForChanges: false};
|
||||||
|
for (var prop in options) annotateOptions[prop] = options[prop];
|
||||||
|
if (!annotateOptions.className) annotateOptions.className = "CodeMirror-search-match";
|
||||||
|
this.annotation = cm.annotateScrollbar(annotateOptions);
|
||||||
|
this.query = query;
|
||||||
|
this.caseFold = caseFold;
|
||||||
|
this.gap = {from: cm.firstLine(), to: cm.lastLine() + 1};
|
||||||
|
this.matches = [];
|
||||||
|
this.update = null;
|
||||||
|
|
||||||
|
this.findMatches();
|
||||||
|
this.annotation.update(this.matches);
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
cm.on("change", this.changeHandler = function(_cm, change) { self.onChange(change); });
|
||||||
|
}
|
||||||
|
|
||||||
|
var MAX_MATCHES = 1000;
|
||||||
|
|
||||||
|
SearchAnnotation.prototype.findMatches = function() {
|
||||||
|
if (!this.gap) return;
|
||||||
|
for (var i = 0; i < this.matches.length; i++) {
|
||||||
|
var match = this.matches[i];
|
||||||
|
if (match.from.line >= this.gap.to) break;
|
||||||
|
if (match.to.line >= this.gap.from) this.matches.splice(i--, 1);
|
||||||
|
}
|
||||||
|
var cursor = this.cm.getSearchCursor(this.query, CodeMirror.Pos(this.gap.from, 0), this.caseFold);
|
||||||
|
var maxMatches = this.options && this.options.maxMatches || MAX_MATCHES;
|
||||||
|
while (cursor.findNext()) {
|
||||||
|
var match = {from: cursor.from(), to: cursor.to()};
|
||||||
|
if (match.from.line >= this.gap.to) break;
|
||||||
|
this.matches.splice(i++, 0, match);
|
||||||
|
if (this.matches.length > maxMatches) break;
|
||||||
|
}
|
||||||
|
this.gap = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
function offsetLine(line, changeStart, sizeChange) {
|
||||||
|
if (line <= changeStart) return line;
|
||||||
|
return Math.max(changeStart, line + sizeChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchAnnotation.prototype.onChange = function(change) {
|
||||||
|
var startLine = change.from.line;
|
||||||
|
var endLine = CodeMirror.changeEnd(change).line;
|
||||||
|
var sizeChange = endLine - change.to.line;
|
||||||
|
if (this.gap) {
|
||||||
|
this.gap.from = Math.min(offsetLine(this.gap.from, startLine, sizeChange), change.from.line);
|
||||||
|
this.gap.to = Math.max(offsetLine(this.gap.to, startLine, sizeChange), change.from.line);
|
||||||
|
} else {
|
||||||
|
this.gap = {from: change.from.line, to: endLine + 1};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sizeChange) for (var i = 0; i < this.matches.length; i++) {
|
||||||
|
var match = this.matches[i];
|
||||||
|
var newFrom = offsetLine(match.from.line, startLine, sizeChange);
|
||||||
|
if (newFrom != match.from.line) match.from = CodeMirror.Pos(newFrom, match.from.ch);
|
||||||
|
var newTo = offsetLine(match.to.line, startLine, sizeChange);
|
||||||
|
if (newTo != match.to.line) match.to = CodeMirror.Pos(newTo, match.to.ch);
|
||||||
|
}
|
||||||
|
clearTimeout(this.update);
|
||||||
|
var self = this;
|
||||||
|
this.update = setTimeout(function() { self.updateAfterChange(); }, 250);
|
||||||
|
};
|
||||||
|
|
||||||
|
SearchAnnotation.prototype.updateAfterChange = function() {
|
||||||
|
this.findMatches();
|
||||||
|
this.annotation.update(this.matches);
|
||||||
|
};
|
||||||
|
|
||||||
|
SearchAnnotation.prototype.clear = function() {
|
||||||
|
this.cm.off("change", this.changeHandler);
|
||||||
|
this.annotation.clear();
|
||||||
|
};
|
||||||
|
});
|
|
@ -1,252 +0,0 @@
|
||||||
// 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.
|
|
||||||
|
|
||||||
(function(mod) {
|
|
||||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
|
||||||
mod(require("../../lib/codemirror"), require("./searchcursor"), require("../dialog/dialog"));
|
|
||||||
else if (typeof define == "function" && define.amd) // AMD
|
|
||||||
define(["../../lib/codemirror", "./searchcursor", "../dialog/dialog"], mod);
|
|
||||||
else // Plain browser env
|
|
||||||
mod(CodeMirror);
|
|
||||||
})(function(CodeMirror) {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
function searchOverlay(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");
|
|
||||||
|
|
||||||
return {token: function(stream) {
|
|
||||||
query.lastIndex = stream.pos;
|
|
||||||
var match = query.exec(stream.string);
|
|
||||||
if (match && match.index == stream.pos) {
|
|
||||||
stream.pos += match[0].length || 1;
|
|
||||||
return "searching";
|
|
||||||
} else if (match) {
|
|
||||||
stream.pos = match.index;
|
|
||||||
} else {
|
|
||||||
stream.skipToEnd();
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
function SearchState() {
|
|
||||||
this.posFrom = this.posTo = this.lastQuery = this.query = null;
|
|
||||||
this.overlay = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSearchState(cm) {
|
|
||||||
return cm.state.search || (cm.state.search = new SearchState());
|
|
||||||
}
|
|
||||||
|
|
||||||
function queryCaseInsensitive(query) {
|
|
||||||
return typeof query == "string" && query == query.toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSearchCursor(cm, query, pos) {
|
|
||||||
// Heuristic: if the query string is all lowercase, do a case insensitive search.
|
|
||||||
return cm.getSearchCursor(query, pos, {caseFold: queryCaseInsensitive(query), multiline: true});
|
|
||||||
}
|
|
||||||
|
|
||||||
function persistentDialog(cm, text, deflt, onEnter, onKeyDown) {
|
|
||||||
cm.openDialog(text, onEnter, {
|
|
||||||
value: deflt,
|
|
||||||
selectValueOnOpen: true,
|
|
||||||
closeOnEnter: false,
|
|
||||||
onClose: function() { clearSearch(cm); },
|
|
||||||
onKeyDown: onKeyDown
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function dialog(cm, text, shortText, deflt, f) {
|
|
||||||
if (cm.openDialog) cm.openDialog(text, f, {value: deflt, selectValueOnOpen: true});
|
|
||||||
else f(prompt(shortText, deflt));
|
|
||||||
}
|
|
||||||
|
|
||||||
function confirmDialog(cm, text, shortText, fs) {
|
|
||||||
if (cm.openConfirm) cm.openConfirm(text, fs);
|
|
||||||
else if (confirm(shortText)) fs[0]();
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseString(string) {
|
|
||||||
return string.replace(/\\(.)/g, function(_, ch) {
|
|
||||||
if (ch == "n") return "\n"
|
|
||||||
if (ch == "r") return "\r"
|
|
||||||
return ch
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseQuery(query) {
|
|
||||||
var isRE = query.match(/^\/(.*)\/([a-z]*)$/);
|
|
||||||
if (isRE) {
|
|
||||||
try { query = new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i"); }
|
|
||||||
catch(e) {} // Not a regular expression after all, do a string search
|
|
||||||
} else {
|
|
||||||
query = parseString(query)
|
|
||||||
}
|
|
||||||
if (typeof query == "string" ? query == "" : query.test(""))
|
|
||||||
query = /x^/;
|
|
||||||
return query;
|
|
||||||
}
|
|
||||||
|
|
||||||
var queryDialog =
|
|
||||||
'<span class="CodeMirror-search-label">Search:</span> <input type="text" style="width: 10em" class="CodeMirror-search-field"/> <span style="color: #888" class="CodeMirror-search-hint">(Use /re/ syntax for regexp search)</span>';
|
|
||||||
|
|
||||||
function startSearch(cm, state, query) {
|
|
||||||
state.queryText = query;
|
|
||||||
state.query = parseQuery(query);
|
|
||||||
cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query));
|
|
||||||
state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query));
|
|
||||||
cm.addOverlay(state.overlay);
|
|
||||||
if (cm.showMatchesOnScrollbar) {
|
|
||||||
if (state.annotate) { state.annotate.clear(); state.annotate = null; }
|
|
||||||
state.annotate = cm.showMatchesOnScrollbar(state.query, queryCaseInsensitive(state.query));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function doSearch(cm, rev, persistent, immediate) {
|
|
||||||
var state = getSearchState(cm);
|
|
||||||
if (state.query) return findNext(cm, rev);
|
|
||||||
var q = cm.getSelection() || state.lastQuery;
|
|
||||||
if (q instanceof RegExp && q.source == "x^") q = null
|
|
||||||
if (persistent && cm.openDialog) {
|
|
||||||
var hiding = null
|
|
||||||
var searchNext = function(query, event) {
|
|
||||||
CodeMirror.e_stop(event);
|
|
||||||
if (!query) return;
|
|
||||||
if (query != state.queryText) {
|
|
||||||
startSearch(cm, state, query);
|
|
||||||
state.posFrom = state.posTo = cm.getCursor();
|
|
||||||
}
|
|
||||||
if (hiding) hiding.style.opacity = 1
|
|
||||||
findNext(cm, event.shiftKey, function(_, to) {
|
|
||||||
var dialog
|
|
||||||
if (to.line < 3 && document.querySelector &&
|
|
||||||
(dialog = cm.display.wrapper.querySelector(".CodeMirror-dialog")) &&
|
|
||||||
dialog.getBoundingClientRect().bottom - 4 > cm.cursorCoords(to, "window").top)
|
|
||||||
(hiding = dialog).style.opacity = .4
|
|
||||||
})
|
|
||||||
};
|
|
||||||
persistentDialog(cm, queryDialog, q, searchNext, function(event, query) {
|
|
||||||
var keyName = CodeMirror.keyName(event)
|
|
||||||
var extra = cm.getOption('extraKeys'), cmd = (extra && extra[keyName]) || CodeMirror.keyMap[cm.getOption("keyMap")][keyName]
|
|
||||||
if (cmd == "findNext" || cmd == "findPrev" ||
|
|
||||||
cmd == "findPersistentNext" || cmd == "findPersistentPrev") {
|
|
||||||
CodeMirror.e_stop(event);
|
|
||||||
startSearch(cm, getSearchState(cm), query);
|
|
||||||
cm.execCommand(cmd);
|
|
||||||
} else if (cmd == "find" || cmd == "findPersistent") {
|
|
||||||
CodeMirror.e_stop(event);
|
|
||||||
searchNext(query, event);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (immediate && q) {
|
|
||||||
startSearch(cm, state, q);
|
|
||||||
findNext(cm, rev);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dialog(cm, queryDialog, "Search for:", q, function(query) {
|
|
||||||
if (query && !state.query) cm.operation(function() {
|
|
||||||
startSearch(cm, state, query);
|
|
||||||
state.posFrom = state.posTo = cm.getCursor();
|
|
||||||
findNext(cm, rev);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function findNext(cm, rev, callback) {cm.operation(function() {
|
|
||||||
var state = getSearchState(cm);
|
|
||||||
var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo);
|
|
||||||
if (!cursor.find(rev)) {
|
|
||||||
cursor = getSearchCursor(cm, state.query, rev ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0));
|
|
||||||
if (!cursor.find(rev)) return;
|
|
||||||
}
|
|
||||||
cm.setSelection(cursor.from(), cursor.to());
|
|
||||||
cm.scrollIntoView({from: cursor.from(), to: cursor.to()}, 20);
|
|
||||||
state.posFrom = cursor.from(); state.posTo = cursor.to();
|
|
||||||
if (callback) callback(cursor.from(), cursor.to())
|
|
||||||
});}
|
|
||||||
|
|
||||||
function clearSearch(cm) {cm.operation(function() {
|
|
||||||
var state = getSearchState(cm);
|
|
||||||
state.lastQuery = state.query;
|
|
||||||
if (!state.query) return;
|
|
||||||
state.query = state.queryText = null;
|
|
||||||
cm.removeOverlay(state.overlay);
|
|
||||||
if (state.annotate) { state.annotate.clear(); state.annotate = null; }
|
|
||||||
});}
|
|
||||||
|
|
||||||
var replaceQueryDialog =
|
|
||||||
' <input type="text" style="width: 10em" class="CodeMirror-search-field"/> <span style="color: #888" class="CodeMirror-search-hint">(Use /re/ syntax for regexp search)</span>';
|
|
||||||
var replacementQueryDialog = '<span class="CodeMirror-search-label">With:</span> <input type="text" style="width: 10em" class="CodeMirror-search-field"/>';
|
|
||||||
var doReplaceConfirm = '<span class="CodeMirror-search-label">Replace?</span> <button>Yes</button> <button>No</button> <button>All</button> <button>Stop</button>';
|
|
||||||
|
|
||||||
function replaceAll(cm, query, text) {
|
|
||||||
cm.operation(function() {
|
|
||||||
for (var cursor = getSearchCursor(cm, query); cursor.findNext();) {
|
|
||||||
if (typeof query != "string") {
|
|
||||||
var match = cm.getRange(cursor.from(), cursor.to()).match(query);
|
|
||||||
cursor.replace(text.replace(/\$(\d)/g, function(_, i) {return match[i];}));
|
|
||||||
} else cursor.replace(text);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function replace(cm, all) {
|
|
||||||
if (cm.getOption("readOnly")) return;
|
|
||||||
var query = cm.getSelection() || getSearchState(cm).lastQuery;
|
|
||||||
var dialogText = '<span class="CodeMirror-search-label">' + (all ? 'Replace all:' : 'Replace:') + '</span>';
|
|
||||||
dialog(cm, dialogText + replaceQueryDialog, dialogText, query, function(query) {
|
|
||||||
if (!query) return;
|
|
||||||
query = parseQuery(query);
|
|
||||||
dialog(cm, replacementQueryDialog, "Replace with:", "", function(text) {
|
|
||||||
text = parseString(text)
|
|
||||||
if (all) {
|
|
||||||
replaceAll(cm, query, text)
|
|
||||||
} else {
|
|
||||||
clearSearch(cm);
|
|
||||||
var cursor = getSearchCursor(cm, query, cm.getCursor("from"));
|
|
||||||
var advance = function() {
|
|
||||||
var start = cursor.from(), match;
|
|
||||||
if (!(match = cursor.findNext())) {
|
|
||||||
cursor = getSearchCursor(cm, query);
|
|
||||||
if (!(match = cursor.findNext()) ||
|
|
||||||
(start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return;
|
|
||||||
}
|
|
||||||
cm.setSelection(cursor.from(), cursor.to());
|
|
||||||
cm.scrollIntoView({from: cursor.from(), to: cursor.to()});
|
|
||||||
confirmDialog(cm, doReplaceConfirm, "Replace?",
|
|
||||||
[function() {doReplace(match);}, advance,
|
|
||||||
function() {replaceAll(cm, query, text)}]);
|
|
||||||
};
|
|
||||||
var doReplace = function(match) {
|
|
||||||
cursor.replace(typeof query == "string" ? text :
|
|
||||||
text.replace(/\$(\d)/g, function(_, i) {return match[i];}));
|
|
||||||
advance();
|
|
||||||
};
|
|
||||||
advance();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);};
|
|
||||||
CodeMirror.commands.findPersistent = function(cm) {clearSearch(cm); doSearch(cm, false, true);};
|
|
||||||
CodeMirror.commands.findPersistentNext = function(cm) {doSearch(cm, false, true, true);};
|
|
||||||
CodeMirror.commands.findPersistentPrev = function(cm) {doSearch(cm, true, true, true);};
|
|
||||||
CodeMirror.commands.findNext = doSearch;
|
|
||||||
CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);};
|
|
||||||
CodeMirror.commands.clearSearch = clearSearch;
|
|
||||||
CodeMirror.commands.replace = replace;
|
|
||||||
CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);};
|
|
||||||
});
|
|
Loading…
Reference in New Issue