From 4b0ca55eb79e963523eb6c8197825e9e8ae904e2 Mon Sep 17 00:00:00 2001 From: Wu Cheng-Han Date: Mon, 4 May 2015 15:53:29 +0800 Subject: First commit, version 0.2.7 --- .../codemirror/addon/search/match-highlighter.js | 128 ++++++++++++++ .../codemirror/addon/search/matchesonscrollbar.css | 8 + .../codemirror/addon/search/matchesonscrollbar.js | 97 +++++++++++ public/vendor/codemirror/addon/search/search.js | 167 ++++++++++++++++++ .../vendor/codemirror/addon/search/searchcursor.js | 189 +++++++++++++++++++++ 5 files changed, 589 insertions(+) create mode 100755 public/vendor/codemirror/addon/search/match-highlighter.js create mode 100755 public/vendor/codemirror/addon/search/matchesonscrollbar.css create mode 100755 public/vendor/codemirror/addon/search/matchesonscrollbar.js create mode 100755 public/vendor/codemirror/addon/search/search.js create mode 100755 public/vendor/codemirror/addon/search/searchcursor.js (limited to 'public/vendor/codemirror/addon/search') diff --git a/public/vendor/codemirror/addon/search/match-highlighter.js b/public/vendor/codemirror/addon/search/match-highlighter.js new file mode 100755 index 00000000..e9a22721 --- /dev/null +++ b/public/vendor/codemirror/addon/search/match-highlighter.js @@ -0,0 +1,128 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +// Highlighting text that matches the selection +// +// Defines an option highlightSelectionMatches, which, when enabled, +// will style strings that match the selection throughout the +// document. +// +// The option can be set to true to simply enable it, or to a +// {minChars, style, wordsOnly, showToken, delay} object to explicitly +// configure it. minChars is the minimum amount of characters that should be +// selected for the behavior to occur, and style is the token style to +// apply to the matches. This will be prefixed by "cm-" to create an +// actual CSS class name. If wordsOnly is enabled, the matches will be +// highlighted only if the selected text is a word. showToken, when enabled, +// will cause the current token to be highlighted when nothing is selected. +// delay is used to specify how much time to wait, in milliseconds, before +// highlighting the matches. + +(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"; + + var DEFAULT_MIN_CHARS = 2; + var DEFAULT_TOKEN_STYLE = "matchhighlight"; + var DEFAULT_DELAY = 100; + var DEFAULT_WORDS_ONLY = false; + + function State(options) { + if (typeof options == "object") { + this.minChars = options.minChars; + this.style = options.style; + this.showToken = options.showToken; + this.delay = options.delay; + this.wordsOnly = options.wordsOnly; + } + if (this.style == null) this.style = DEFAULT_TOKEN_STYLE; + if (this.minChars == null) this.minChars = DEFAULT_MIN_CHARS; + if (this.delay == null) this.delay = DEFAULT_DELAY; + if (this.wordsOnly == null) this.wordsOnly = DEFAULT_WORDS_ONLY; + this.overlay = this.timeout = null; + } + + CodeMirror.defineOption("highlightSelectionMatches", false, function(cm, val, old) { + if (old && old != CodeMirror.Init) { + var over = cm.state.matchHighlighter.overlay; + if (over) cm.removeOverlay(over); + clearTimeout(cm.state.matchHighlighter.timeout); + cm.state.matchHighlighter = null; + cm.off("cursorActivity", cursorActivity); + } + if (val) { + cm.state.matchHighlighter = new State(val); + highlightMatches(cm); + cm.on("cursorActivity", cursorActivity); + } + }); + + function cursorActivity(cm) { + var state = cm.state.matchHighlighter; + clearTimeout(state.timeout); + state.timeout = setTimeout(function() {highlightMatches(cm);}, state.delay); + } + + function highlightMatches(cm) { + cm.operation(function() { + var state = cm.state.matchHighlighter; + if (state.overlay) { + cm.removeOverlay(state.overlay); + state.overlay = null; + } + if (!cm.somethingSelected() && state.showToken) { + var re = state.showToken === true ? /[\w$]/ : state.showToken; + var cur = cm.getCursor(), line = cm.getLine(cur.line), start = cur.ch, end = start; + while (start && re.test(line.charAt(start - 1))) --start; + while (end < line.length && re.test(line.charAt(end))) ++end; + if (start < end) + cm.addOverlay(state.overlay = makeOverlay(line.slice(start, end), re, state.style)); + return; + } + var from = cm.getCursor("from"), to = cm.getCursor("to"); + if (from.line != to.line) return; + if (state.wordsOnly && !isWord(cm, from, to)) return; + var selection = cm.getRange(from, to).replace(/^\s+|\s+$/g, ""); + if (selection.length >= state.minChars) + cm.addOverlay(state.overlay = makeOverlay(selection, false, state.style)); + }); + } + + function isWord(cm, from, to) { + var str = cm.getRange(from, to); + if (str.match(/^\w+$/) !== null) { + if (from.ch > 0) { + var pos = {line: from.line, ch: from.ch - 1}; + var chr = cm.getRange(pos, from); + if (chr.match(/\W/) === null) return false; + } + if (to.ch < cm.getLine(from.line).length) { + var pos = {line: to.line, ch: to.ch + 1}; + var chr = cm.getRange(to, pos); + if (chr.match(/\W/) === null) return false; + } + return true; + } else return false; + } + + function boundariesAround(stream, re) { + return (!stream.start || !re.test(stream.string.charAt(stream.start - 1))) && + (stream.pos == stream.string.length || !re.test(stream.string.charAt(stream.pos))); + } + + function makeOverlay(query, hasBoundary, style) { + return {token: function(stream) { + if (stream.match(query) && + (!hasBoundary || boundariesAround(stream, hasBoundary))) + return style; + stream.next(); + stream.skipTo(query.charAt(0)) || stream.skipToEnd(); + }}; + } +}); diff --git a/public/vendor/codemirror/addon/search/matchesonscrollbar.css b/public/vendor/codemirror/addon/search/matchesonscrollbar.css new file mode 100755 index 00000000..77932cc9 --- /dev/null +++ b/public/vendor/codemirror/addon/search/matchesonscrollbar.css @@ -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; +} diff --git a/public/vendor/codemirror/addon/search/matchesonscrollbar.js b/public/vendor/codemirror/addon/search/matchesonscrollbar.js new file mode 100755 index 00000000..8d192289 --- /dev/null +++ b/public/vendor/codemirror/addon/search/matchesonscrollbar.js @@ -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(); + }; +}); diff --git a/public/vendor/codemirror/addon/search/search.js b/public/vendor/codemirror/addon/search/search.js new file mode 100755 index 00000000..1c8ca3c2 --- /dev/null +++ b/public/vendor/codemirror/addon/search/search.js @@ -0,0 +1,167 @@ +// 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; + 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, queryCaseInsensitive(query)); + } + 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 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 + } + if (typeof query == "string" ? query == "" : query.test("")) + query = /x^/; + return query; + } + var queryDialog = + 'Search: (Use /re/ syntax for regexp search)'; + function doSearch(cm, rev) { + var state = getSearchState(cm); + if (state.query) return findNext(cm, rev); + var q = cm.getSelection() || state.lastQuery; + dialog(cm, queryDialog, "Search for:", q, function(query) { + cm.operation(function() { + if (!query || state.query) return; + 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)); + } + state.posFrom = state.posTo = cm.getCursor(); + findNext(cm, rev); + }); + }); + } + function findNext(cm, rev) {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()}); + state.posFrom = cursor.from(); state.posTo = cursor.to(); + });} + function clearSearch(cm) {cm.operation(function() { + var state = getSearchState(cm); + state.lastQuery = state.query; + if (!state.query) return; + state.query = null; + cm.removeOverlay(state.overlay); + if (state.annotate) { state.annotate.clear(); state.annotate = null; } + });} + + var replaceQueryDialog = + 'Replace: (Use /re/ syntax for regexp search)'; + var replacementQueryDialog = 'With: '; + var doReplaceConfirm = "Replace? "; + function replace(cm, all) { + if (cm.getOption("readOnly")) return; + var query = cm.getSelection() || getSearchState().lastQuery; + dialog(cm, replaceQueryDialog, "Replace:", query, function(query) { + if (!query) return; + query = parseQuery(query); + dialog(cm, replacementQueryDialog, "Replace with:", "", function(text) { + if (all) { + 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); + } + }); + } else { + clearSearch(cm); + var cursor = getSearchCursor(cm, query, cm.getCursor()); + 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]); + }; + 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.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);}; +}); diff --git a/public/vendor/codemirror/addon/search/searchcursor.js b/public/vendor/codemirror/addon/search/searchcursor.js new file mode 100755 index 00000000..97088bf1 --- /dev/null +++ b/public/vendor/codemirror/addon/search/searchcursor.js @@ -0,0 +1,189 @@ +// 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"; + var Pos = CodeMirror.Pos; + + function SearchCursor(doc, query, pos, caseFold) { + this.atOccurrence = false; this.doc = doc; + if (caseFold == null && typeof query == "string") caseFold = false; + + pos = pos ? doc.clipPos(pos) : Pos(0, 0); + this.pos = {from: pos, to: pos}; + + // The matches method is filled in based on the type of query. + // It takes a position and a direction, and returns an object + // describing the next occurrence of the query, or null if no + // more matches were found. + if (typeof query != "string") { // Regexp match + if (!query.global) query = new RegExp(query.source, query.ignoreCase ? "ig" : "g"); + this.matches = function(reverse, pos) { + if (reverse) { + query.lastIndex = 0; + var line = doc.getLine(pos.line).slice(0, pos.ch), cutOff = 0, match, start; + for (;;) { + query.lastIndex = cutOff; + var newMatch = query.exec(line); + if (!newMatch) break; + match = newMatch; + start = match.index; + cutOff = match.index + (match[0].length || 1); + if (cutOff == line.length) break; + } + var matchLen = (match && match[0].length) || 0; + if (!matchLen) { + if (start == 0 && line.length == 0) {match = undefined;} + else if (start != doc.getLine(pos.line).length) { + matchLen++; + } + } + } else { + query.lastIndex = pos.ch; + var line = doc.getLine(pos.line), match = query.exec(line); + var matchLen = (match && match[0].length) || 0; + var start = match && match.index; + if (start + matchLen != line.length && !matchLen) matchLen = 1; + } + if (match && matchLen) + return {from: Pos(pos.line, start), + to: Pos(pos.line, start + matchLen), + match: match}; + }; + } else { // String query + var origQuery = query; + if (caseFold) query = query.toLowerCase(); + var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;}; + var target = query.split("\n"); + // Different methods for single-line and multi-line queries + if (target.length == 1) { + if (!query.length) { + // Empty string would match anything and never progress, so + // we define it to match nothing instead. + this.matches = function() {}; + } else { + this.matches = function(reverse, pos) { + if (reverse) { + var orig = doc.getLine(pos.line).slice(0, pos.ch), line = fold(orig); + var match = line.lastIndexOf(query); + if (match > -1) { + match = adjustPos(orig, line, match); + return {from: Pos(pos.line, match), to: Pos(pos.line, match + origQuery.length)}; + } + } else { + var orig = doc.getLine(pos.line).slice(pos.ch), line = fold(orig); + var match = line.indexOf(query); + if (match > -1) { + match = adjustPos(orig, line, match) + pos.ch; + return {from: Pos(pos.line, match), to: Pos(pos.line, match + origQuery.length)}; + } + } + }; + } + } else { + var origTarget = origQuery.split("\n"); + this.matches = function(reverse, pos) { + var last = target.length - 1; + if (reverse) { + if (pos.line - (target.length - 1) < doc.firstLine()) return; + if (fold(doc.getLine(pos.line).slice(0, origTarget[last].length)) != target[target.length - 1]) return; + var to = Pos(pos.line, origTarget[last].length); + for (var ln = pos.line - 1, i = last - 1; i >= 1; --i, --ln) + if (target[i] != fold(doc.getLine(ln))) return; + var line = doc.getLine(ln), cut = line.length - origTarget[0].length; + if (fold(line.slice(cut)) != target[0]) return; + return {from: Pos(ln, cut), to: to}; + } else { + if (pos.line + (target.length - 1) > doc.lastLine()) return; + var line = doc.getLine(pos.line), cut = line.length - origTarget[0].length; + if (fold(line.slice(cut)) != target[0]) return; + var from = Pos(pos.line, cut); + for (var ln = pos.line + 1, i = 1; i < last; ++i, ++ln) + if (target[i] != fold(doc.getLine(ln))) return; + if (fold(doc.getLine(ln).slice(0, origTarget[last].length)) != target[last]) return; + return {from: from, to: Pos(ln, origTarget[last].length)}; + } + }; + } + } + } + + SearchCursor.prototype = { + findNext: function() {return this.find(false);}, + findPrevious: function() {return this.find(true);}, + + find: function(reverse) { + var self = this, pos = this.doc.clipPos(reverse ? this.pos.from : this.pos.to); + function savePosAndFail(line) { + var pos = Pos(line, 0); + self.pos = {from: pos, to: pos}; + self.atOccurrence = false; + return false; + } + + for (;;) { + if (this.pos = this.matches(reverse, pos)) { + this.atOccurrence = true; + return this.pos.match || true; + } + if (reverse) { + if (!pos.line) return savePosAndFail(0); + pos = Pos(pos.line-1, this.doc.getLine(pos.line-1).length); + } + else { + var maxLine = this.doc.lineCount(); + if (pos.line == maxLine - 1) return savePosAndFail(maxLine); + pos = Pos(pos.line + 1, 0); + } + } + }, + + from: function() {if (this.atOccurrence) return this.pos.from;}, + to: function() {if (this.atOccurrence) return this.pos.to;}, + + replace: function(newText, origin) { + if (!this.atOccurrence) return; + var lines = CodeMirror.splitLines(newText); + this.doc.replaceRange(lines, this.pos.from, this.pos.to, origin); + this.pos.to = Pos(this.pos.from.line + lines.length - 1, + lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0)); + } + }; + + // Maps a position in a case-folded line back to a position in the original line + // (compensating for codepoints increasing in number during folding) + function adjustPos(orig, folded, pos) { + if (orig.length == folded.length) return pos; + for (var pos1 = Math.min(pos, orig.length);;) { + var len1 = orig.slice(0, pos1).toLowerCase().length; + if (len1 < pos) ++pos1; + else if (len1 > pos) --pos1; + else return pos1; + } + } + + CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) { + return new SearchCursor(this.doc, query, pos, caseFold); + }); + CodeMirror.defineDocExtension("getSearchCursor", function(query, pos, caseFold) { + return new SearchCursor(this, query, pos, caseFold); + }); + + CodeMirror.defineExtension("selectMatches", function(query, caseFold) { + var ranges = [], next; + var cur = this.getSearchCursor(query, this.getCursor("from"), caseFold); + while (next = cur.findNext()) { + if (CodeMirror.cmpPos(cur.to(), this.getCursor("to")) > 0) break; + ranges.push({anchor: cur.from(), head: cur.to()}); + } + if (ranges.length) + this.setSelections(ranges, 0); + }); +}); -- cgit v1.2.3