Subversion Repositories wimsdev

Rev

Rev 16493 | Blame | Compare with Previous | Last modification | View Log | RSS feed

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