openrat-cms

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

searchcursor.js (11748B)


      1 // CodeMirror, copyright (c) by Marijn Haverbeke and others
      2 // Distributed under an MIT license: http://codemirror.net/LICENSE
      3 
      4 (function(mod) {
      5   if (typeof exports == "object" && typeof module == "object") // CommonJS
      6     mod(require("../../lib/codemirror"))
      7   else if (typeof define == "function" && define.amd) // AMD
      8     define(["../../lib/codemirror"], mod)
      9   else // Plain browser env
     10     mod(CodeMirror)
     11 })(function(CodeMirror) {
     12   "use strict"
     13   var Pos = CodeMirror.Pos
     14 
     15   function regexpFlags(regexp) {
     16     var flags = regexp.flags
     17     return flags != null ? flags : (regexp.ignoreCase ? "i" : "")
     18       + (regexp.global ? "g" : "")
     19       + (regexp.multiline ? "m" : "")
     20   }
     21 
     22   function ensureGlobal(regexp) {
     23     return regexp.global ? regexp : new RegExp(regexp.source, regexpFlags(regexp) + "g")
     24   }
     25 
     26   function maybeMultiline(regexp) {
     27     return /\\s|\\n|\n|\\W|\\D|\[\^/.test(regexp.source)
     28   }
     29 
     30   function searchRegexpForward(doc, regexp, start) {
     31     regexp = ensureGlobal(regexp)
     32     for (var line = start.line, ch = start.ch, last = doc.lastLine(); line <= last; line++, ch = 0) {
     33       regexp.lastIndex = ch
     34       var string = doc.getLine(line), match = regexp.exec(string)
     35       if (match)
     36         return {from: Pos(line, match.index),
     37                 to: Pos(line, match.index + match[0].length),
     38                 match: match}
     39     }
     40   }
     41 
     42   function searchRegexpForwardMultiline(doc, regexp, start) {
     43     if (!maybeMultiline(regexp)) return searchRegexpForward(doc, regexp, start)
     44 
     45     regexp = ensureGlobal(regexp)
     46     var string, chunk = 1
     47     for (var line = start.line, last = doc.lastLine(); line <= last;) {
     48       // This grows the search buffer in exponentially-sized chunks
     49       // between matches, so that nearby matches are fast and don't
     50       // require concatenating the whole document (in case we're
     51       // searching for something that has tons of matches), but at the
     52       // same time, the amount of retries is limited.
     53       for (var i = 0; i < chunk; i++) {
     54         var curLine = doc.getLine(line++)
     55         string = string == null ? curLine : string + "\n" + curLine
     56       }
     57       chunk = chunk * 2
     58       regexp.lastIndex = start.ch
     59       var match = regexp.exec(string)
     60       if (match) {
     61         var before = string.slice(0, match.index).split("\n"), inside = match[0].split("\n")
     62         var startLine = start.line + before.length - 1, startCh = before[before.length - 1].length
     63         return {from: Pos(startLine, startCh),
     64                 to: Pos(startLine + inside.length - 1,
     65                         inside.length == 1 ? startCh + inside[0].length : inside[inside.length - 1].length),
     66                 match: match}
     67       }
     68     }
     69   }
     70 
     71   function lastMatchIn(string, regexp) {
     72     var cutOff = 0, match
     73     for (;;) {
     74       regexp.lastIndex = cutOff
     75       var newMatch = regexp.exec(string)
     76       if (!newMatch) return match
     77       match = newMatch
     78       cutOff = match.index + (match[0].length || 1)
     79       if (cutOff == string.length) return match
     80     }
     81   }
     82 
     83   function searchRegexpBackward(doc, regexp, start) {
     84     regexp = ensureGlobal(regexp)
     85     for (var line = start.line, ch = start.ch, first = doc.firstLine(); line >= first; line--, ch = -1) {
     86       var string = doc.getLine(line)
     87       if (ch > -1) string = string.slice(0, ch)
     88       var match = lastMatchIn(string, regexp)
     89       if (match)
     90         return {from: Pos(line, match.index),
     91                 to: Pos(line, match.index + match[0].length),
     92                 match: match}
     93     }
     94   }
     95 
     96   function searchRegexpBackwardMultiline(doc, regexp, start) {
     97     regexp = ensureGlobal(regexp)
     98     var string, chunk = 1
     99     for (var line = start.line, first = doc.firstLine(); line >= first;) {
    100       for (var i = 0; i < chunk; i++) {
    101         var curLine = doc.getLine(line--)
    102         string = string == null ? curLine.slice(0, start.ch) : curLine + "\n" + string
    103       }
    104       chunk *= 2
    105 
    106       var match = lastMatchIn(string, regexp)
    107       if (match) {
    108         var before = string.slice(0, match.index).split("\n"), inside = match[0].split("\n")
    109         var startLine = line + before.length, startCh = before[before.length - 1].length
    110         return {from: Pos(startLine, startCh),
    111                 to: Pos(startLine + inside.length - 1,
    112                         inside.length == 1 ? startCh + inside[0].length : inside[inside.length - 1].length),
    113                 match: match}
    114       }
    115     }
    116   }
    117 
    118   var doFold, noFold
    119   if (String.prototype.normalize) {
    120     doFold = function(str) { return str.normalize("NFD").toLowerCase() }
    121     noFold = function(str) { return str.normalize("NFD") }
    122   } else {
    123     doFold = function(str) { return str.toLowerCase() }
    124     noFold = function(str) { return str }
    125   }
    126 
    127   // Maps a position in a case-folded line back to a position in the original line
    128   // (compensating for codepoints increasing in number during folding)
    129   function adjustPos(orig, folded, pos, foldFunc) {
    130     if (orig.length == folded.length) return pos
    131     for (var min = 0, max = pos + Math.max(0, orig.length - folded.length);;) {
    132       if (min == max) return min
    133       var mid = (min + max) >> 1
    134       var len = foldFunc(orig.slice(0, mid)).length
    135       if (len == pos) return mid
    136       else if (len > pos) max = mid
    137       else min = mid + 1
    138     }
    139   }
    140 
    141   function searchStringForward(doc, query, start, caseFold) {
    142     // Empty string would match anything and never progress, so we
    143     // define it to match nothing instead.
    144     if (!query.length) return null
    145     var fold = caseFold ? doFold : noFold
    146     var lines = fold(query).split(/\r|\n\r?/)
    147 
    148     search: for (var line = start.line, ch = start.ch, last = doc.lastLine() + 1 - lines.length; line <= last; line++, ch = 0) {
    149       var orig = doc.getLine(line).slice(ch), string = fold(orig)
    150       if (lines.length == 1) {
    151         var found = string.indexOf(lines[0])
    152         if (found == -1) continue search
    153         var start = adjustPos(orig, string, found, fold) + ch
    154         return {from: Pos(line, adjustPos(orig, string, found, fold) + ch),
    155                 to: Pos(line, adjustPos(orig, string, found + lines[0].length, fold) + ch)}
    156       } else {
    157         var cutFrom = string.length - lines[0].length
    158         if (string.slice(cutFrom) != lines[0]) continue search
    159         for (var i = 1; i < lines.length - 1; i++)
    160           if (fold(doc.getLine(line + i)) != lines[i]) continue search
    161         var end = doc.getLine(line + lines.length - 1), endString = fold(end), lastLine = lines[lines.length - 1]
    162         if (endString.slice(0, lastLine.length) != lastLine) continue search
    163         return {from: Pos(line, adjustPos(orig, string, cutFrom, fold) + ch),
    164                 to: Pos(line + lines.length - 1, adjustPos(end, endString, lastLine.length, fold))}
    165       }
    166     }
    167   }
    168 
    169   function searchStringBackward(doc, query, start, caseFold) {
    170     if (!query.length) return null
    171     var fold = caseFold ? doFold : noFold
    172     var lines = fold(query).split(/\r|\n\r?/)
    173 
    174     search: for (var line = start.line, ch = start.ch, first = doc.firstLine() - 1 + lines.length; line >= first; line--, ch = -1) {
    175       var orig = doc.getLine(line)
    176       if (ch > -1) orig = orig.slice(0, ch)
    177       var string = fold(orig)
    178       if (lines.length == 1) {
    179         var found = string.lastIndexOf(lines[0])
    180         if (found == -1) continue search
    181         return {from: Pos(line, adjustPos(orig, string, found, fold)),
    182                 to: Pos(line, adjustPos(orig, string, found + lines[0].length, fold))}
    183       } else {
    184         var lastLine = lines[lines.length - 1]
    185         if (string.slice(0, lastLine.length) != lastLine) continue search
    186         for (var i = 1, start = line - lines.length + 1; i < lines.length - 1; i++)
    187           if (fold(doc.getLine(start + i)) != lines[i]) continue search
    188         var top = doc.getLine(line + 1 - lines.length), topString = fold(top)
    189         if (topString.slice(topString.length - lines[0].length) != lines[0]) continue search
    190         return {from: Pos(line + 1 - lines.length, adjustPos(top, topString, top.length - lines[0].length, fold)),
    191                 to: Pos(line, adjustPos(orig, string, lastLine.length, fold))}
    192       }
    193     }
    194   }
    195 
    196   function SearchCursor(doc, query, pos, options) {
    197     this.atOccurrence = false
    198     this.doc = doc
    199     pos = pos ? doc.clipPos(pos) : Pos(0, 0)
    200     this.pos = {from: pos, to: pos}
    201 
    202     var caseFold
    203     if (typeof options == "object") {
    204       caseFold = options.caseFold
    205     } else { // Backwards compat for when caseFold was the 4th argument
    206       caseFold = options
    207       options = null
    208     }
    209 
    210     if (typeof query == "string") {
    211       if (caseFold == null) caseFold = false
    212       this.matches = function(reverse, pos) {
    213         return (reverse ? searchStringBackward : searchStringForward)(doc, query, pos, caseFold)
    214       }
    215     } else {
    216       query = ensureGlobal(query)
    217       if (!options || options.multiline !== false)
    218         this.matches = function(reverse, pos) {
    219           return (reverse ? searchRegexpBackwardMultiline : searchRegexpForwardMultiline)(doc, query, pos)
    220         }
    221       else
    222         this.matches = function(reverse, pos) {
    223           return (reverse ? searchRegexpBackward : searchRegexpForward)(doc, query, pos)
    224         }
    225     }
    226   }
    227 
    228   SearchCursor.prototype = {
    229     findNext: function() {return this.find(false)},
    230     findPrevious: function() {return this.find(true)},
    231 
    232     find: function(reverse) {
    233       var result = this.matches(reverse, this.doc.clipPos(reverse ? this.pos.from : this.pos.to))
    234 
    235       // Implements weird auto-growing behavior on null-matches for
    236       // backwards-compatiblity with the vim code (unfortunately)
    237       while (result && CodeMirror.cmpPos(result.from, result.to) == 0) {
    238         if (reverse) {
    239           if (result.from.ch) result.from = Pos(result.from.line, result.from.ch - 1)
    240           else if (result.from.line == this.doc.firstLine()) result = null
    241           else result = this.matches(reverse, this.doc.clipPos(Pos(result.from.line - 1)))
    242         } else {
    243           if (result.to.ch < this.doc.getLine(result.to.line).length) result.to = Pos(result.to.line, result.to.ch + 1)
    244           else if (result.to.line == this.doc.lastLine()) result = null
    245           else result = this.matches(reverse, Pos(result.to.line + 1, 0))
    246         }
    247       }
    248 
    249       if (result) {
    250         this.pos = result
    251         this.atOccurrence = true
    252         return this.pos.match || true
    253       } else {
    254         var end = Pos(reverse ? this.doc.firstLine() : this.doc.lastLine() + 1, 0)
    255         this.pos = {from: end, to: end}
    256         return this.atOccurrence = false
    257       }
    258     },
    259 
    260     from: function() {if (this.atOccurrence) return this.pos.from},
    261     to: function() {if (this.atOccurrence) return this.pos.to},
    262 
    263     replace: function(newText, origin) {
    264       if (!this.atOccurrence) return
    265       var lines = CodeMirror.splitLines(newText)
    266       this.doc.replaceRange(lines, this.pos.from, this.pos.to, origin)
    267       this.pos.to = Pos(this.pos.from.line + lines.length - 1,
    268                         lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0))
    269     }
    270   }
    271 
    272   CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) {
    273     return new SearchCursor(this.doc, query, pos, caseFold)
    274   })
    275   CodeMirror.defineDocExtension("getSearchCursor", function(query, pos, caseFold) {
    276     return new SearchCursor(this, query, pos, caseFold)
    277   })
    278 
    279   CodeMirror.defineExtension("selectMatches", function(query, caseFold) {
    280     var ranges = []
    281     var cur = this.getSearchCursor(query, this.getCursor("from"), caseFold)
    282     while (cur.findNext()) {
    283       if (CodeMirror.cmpPos(cur.to(), this.getCursor("to")) > 0) break
    284       ranges.push({anchor: cur.from(), head: cur.to()})
    285     }
    286     if (ranges.length)
    287       this.setSelections(ranges, 0)
    288   })
    289 });