openrat-cms

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

methods.min.js (21398B)


      1 import { deleteNearSelection } from "./deleteNearSelection.js"
      2 import { commands } from "./commands.js"
      3 import { attachDoc } from "../model/document_data.js"
      4 import { activeElt, addClass, rmClass } from "../util/dom.js"
      5 import { eventMixin, signal } from "../util/event.js"
      6 import { getLineStyles, getContextBefore, takeToken } from "../line/highlight.js"
      7 import { indentLine } from "../input/indent.js"
      8 import { triggerElectric } from "../input/input.js"
      9 import { onKeyDown, onKeyPress, onKeyUp } from "./key_events.js"
     10 import { onMouseDown } from "./mouse_events.js"
     11 import { getKeyMap } from "../input/keymap.js"
     12 import { endOfLine, moveLogically, moveVisually } from "../input/movement.js"
     13 import { endOperation, methodOp, operation, runInOp, startOperation } from "../display/operations.js"
     14 import { clipLine, clipPos, equalCursorPos, Pos } from "../line/pos.js"
     15 import { charCoords, charWidth, clearCaches, clearLineMeasurementCache, coordsChar, cursorCoords, displayHeight, displayWidth, estimateLineHeights, fromCoordSystem, intoCoordSystem, scrollGap, textHeight } from "../measurement/position_measurement.js"
     16 import { Range } from "../model/selection.js"
     17 import { replaceOneSelection, skipAtomic } from "../model/selection_updates.js"
     18 import { addToScrollTop, ensureCursorVisible, scrollIntoView, scrollToCoords, scrollToCoordsRange, scrollToRange } from "../display/scrolling.js"
     19 import { heightAtLine } from "../line/spans.js"
     20 import { updateGutterSpace } from "../display/update_display.js"
     21 import { indexOf, insertSorted, isWordChar, sel_dontScroll, sel_move } from "../util/misc.js"
     22 import { signalLater } from "../util/operation_group.js"
     23 import { getLine, isLine, lineAtHeight } from "../line/utils_line.js"
     24 import { regChange, regLineChange } from "../display/view_tracking.js"
     25 
     26 // The publicly visible API. Note that methodOp(f) means
     27 // 'wrap f in an operation, performed on its `this` parameter'.
     28 
     29 // This is not the complete set of editor methods. Most of the
     30 // methods defined on the Doc type are also injected into
     31 // CodeMirror.prototype, for backwards compatibility and
     32 // convenience.
     33 
     34 export default function(CodeMirror) {
     35   let optionHandlers = CodeMirror.optionHandlers
     36 
     37   let helpers = CodeMirror.helpers = {}
     38 
     39   CodeMirror.prototype = {
     40     constructor: CodeMirror,
     41     focus: function(){window.focus(); this.display.input.focus()},
     42 
     43     setOption: function(option, value) {
     44       let options = this.options, old = options[option]
     45       if (options[option] == value && option != "mode") return
     46       options[option] = value
     47       if (optionHandlers.hasOwnProperty(option))
     48         operation(this, optionHandlers[option])(this, value, old)
     49       signal(this, "optionChange", this, option)
     50     },
     51 
     52     getOption: function(option) {return this.options[option]},
     53     getDoc: function() {return this.doc},
     54 
     55     addKeyMap: function(map, bottom) {
     56       this.state.keyMaps[bottom ? "push" : "unshift"](getKeyMap(map))
     57     },
     58     removeKeyMap: function(map) {
     59       let maps = this.state.keyMaps
     60       for (let i = 0; i < maps.length; ++i)
     61         if (maps[i] == map || maps[i].name == map) {
     62           maps.splice(i, 1)
     63           return true
     64         }
     65     },
     66 
     67     addOverlay: methodOp(function(spec, options) {
     68       let mode = spec.token ? spec : CodeMirror.getMode(this.options, spec)
     69       if (mode.startState) throw new Error("Overlays may not be stateful.")
     70       insertSorted(this.state.overlays,
     71                    {mode: mode, modeSpec: spec, opaque: options && options.opaque,
     72                     priority: (options && options.priority) || 0},
     73                    overlay => overlay.priority)
     74       this.state.modeGen++
     75       regChange(this)
     76     }),
     77     removeOverlay: methodOp(function(spec) {
     78       let overlays = this.state.overlays
     79       for (let i = 0; i < overlays.length; ++i) {
     80         let cur = overlays[i].modeSpec
     81         if (cur == spec || typeof spec == "string" && cur.name == spec) {
     82           overlays.splice(i, 1)
     83           this.state.modeGen++
     84           regChange(this)
     85           return
     86         }
     87       }
     88     }),
     89 
     90     indentLine: methodOp(function(n, dir, aggressive) {
     91       if (typeof dir != "string" && typeof dir != "number") {
     92         if (dir == null) dir = this.options.smartIndent ? "smart" : "prev"
     93         else dir = dir ? "add" : "subtract"
     94       }
     95       if (isLine(this.doc, n)) indentLine(this, n, dir, aggressive)
     96     }),
     97     indentSelection: methodOp(function(how) {
     98       let ranges = this.doc.sel.ranges, end = -1
     99       for (let i = 0; i < ranges.length; i++) {
    100         let range = ranges[i]
    101         if (!range.empty()) {
    102           let from = range.from(), to = range.to()
    103           let start = Math.max(end, from.line)
    104           end = Math.min(this.lastLine(), to.line - (to.ch ? 0 : 1)) + 1
    105           for (let j = start; j < end; ++j)
    106             indentLine(this, j, how)
    107           let newRanges = this.doc.sel.ranges
    108           if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0)
    109             replaceOneSelection(this.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll)
    110         } else if (range.head.line > end) {
    111           indentLine(this, range.head.line, how, true)
    112           end = range.head.line
    113           if (i == this.doc.sel.primIndex) ensureCursorVisible(this)
    114         }
    115       }
    116     }),
    117 
    118     // Fetch the parser token for a given character. Useful for hacks
    119     // that want to inspect the mode state (say, for completion).
    120     getTokenAt: function(pos, precise) {
    121       return takeToken(this, pos, precise)
    122     },
    123 
    124     getLineTokens: function(line, precise) {
    125       return takeToken(this, Pos(line), precise, true)
    126     },
    127 
    128     getTokenTypeAt: function(pos) {
    129       pos = clipPos(this.doc, pos)
    130       let styles = getLineStyles(this, getLine(this.doc, pos.line))
    131       let before = 0, after = (styles.length - 1) / 2, ch = pos.ch
    132       let type
    133       if (ch == 0) type = styles[2]
    134       else for (;;) {
    135         let mid = (before + after) >> 1
    136         if ((mid ? styles[mid * 2 - 1] : 0) >= ch) after = mid
    137         else if (styles[mid * 2 + 1] < ch) before = mid + 1
    138         else { type = styles[mid * 2 + 2]; break }
    139       }
    140       let cut = type ? type.indexOf("overlay ") : -1
    141       return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1)
    142     },
    143 
    144     getModeAt: function(pos) {
    145       let mode = this.doc.mode
    146       if (!mode.innerMode) return mode
    147       return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode
    148     },
    149 
    150     getHelper: function(pos, type) {
    151       return this.getHelpers(pos, type)[0]
    152     },
    153 
    154     getHelpers: function(pos, type) {
    155       let found = []
    156       if (!helpers.hasOwnProperty(type)) return found
    157       let help = helpers[type], mode = this.getModeAt(pos)
    158       if (typeof mode[type] == "string") {
    159         if (help[mode[type]]) found.push(help[mode[type]])
    160       } else if (mode[type]) {
    161         for (let i = 0; i < mode[type].length; i++) {
    162           let val = help[mode[type][i]]
    163           if (val) found.push(val)
    164         }
    165       } else if (mode.helperType && help[mode.helperType]) {
    166         found.push(help[mode.helperType])
    167       } else if (help[mode.name]) {
    168         found.push(help[mode.name])
    169       }
    170       for (let i = 0; i < help._global.length; i++) {
    171         let cur = help._global[i]
    172         if (cur.pred(mode, this) && indexOf(found, cur.val) == -1)
    173           found.push(cur.val)
    174       }
    175       return found
    176     },
    177 
    178     getStateAfter: function(line, precise) {
    179       let doc = this.doc
    180       line = clipLine(doc, line == null ? doc.first + doc.size - 1: line)
    181       return getContextBefore(this, line + 1, precise).state
    182     },
    183 
    184     cursorCoords: function(start, mode) {
    185       let pos, range = this.doc.sel.primary()
    186       if (start == null) pos = range.head
    187       else if (typeof start == "object") pos = clipPos(this.doc, start)
    188       else pos = start ? range.from() : range.to()
    189       return cursorCoords(this, pos, mode || "page")
    190     },
    191 
    192     charCoords: function(pos, mode) {
    193       return charCoords(this, clipPos(this.doc, pos), mode || "page")
    194     },
    195 
    196     coordsChar: function(coords, mode) {
    197       coords = fromCoordSystem(this, coords, mode || "page")
    198       return coordsChar(this, coords.left, coords.top)
    199     },
    200 
    201     lineAtHeight: function(height, mode) {
    202       height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top
    203       return lineAtHeight(this.doc, height + this.display.viewOffset)
    204     },
    205     heightAtLine: function(line, mode, includeWidgets) {
    206       let end = false, lineObj
    207       if (typeof line == "number") {
    208         let last = this.doc.first + this.doc.size - 1
    209         if (line < this.doc.first) line = this.doc.first
    210         else if (line > last) { line = last; end = true }
    211         lineObj = getLine(this.doc, line)
    212       } else {
    213         lineObj = line
    214       }
    215       return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page", includeWidgets || end).top +
    216         (end ? this.doc.height - heightAtLine(lineObj) : 0)
    217     },
    218 
    219     defaultTextHeight: function() { return textHeight(this.display) },
    220     defaultCharWidth: function() { return charWidth(this.display) },
    221 
    222     getViewport: function() { return {from: this.display.viewFrom, to: this.display.viewTo}},
    223 
    224     addWidget: function(pos, node, scroll, vert, horiz) {
    225       let display = this.display
    226       pos = cursorCoords(this, clipPos(this.doc, pos))
    227       let top = pos.bottom, left = pos.left
    228       node.style.position = "absolute"
    229       node.setAttribute("cm-ignore-events", "true")
    230       this.display.input.setUneditable(node)
    231       display.sizer.appendChild(node)
    232       if (vert == "over") {
    233         top = pos.top
    234       } else if (vert == "above" || vert == "near") {
    235         let vspace = Math.max(display.wrapper.clientHeight, this.doc.height),
    236         hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth)
    237         // Default to positioning above (if specified and possible); otherwise default to positioning below
    238         if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight)
    239           top = pos.top - node.offsetHeight
    240         else if (pos.bottom + node.offsetHeight <= vspace)
    241           top = pos.bottom
    242         if (left + node.offsetWidth > hspace)
    243           left = hspace - node.offsetWidth
    244       }
    245       node.style.top = top + "px"
    246       node.style.left = node.style.right = ""
    247       if (horiz == "right") {
    248         left = display.sizer.clientWidth - node.offsetWidth
    249         node.style.right = "0px"
    250       } else {
    251         if (horiz == "left") left = 0
    252         else if (horiz == "middle") left = (display.sizer.clientWidth - node.offsetWidth) / 2
    253         node.style.left = left + "px"
    254       }
    255       if (scroll)
    256         scrollIntoView(this, {left, top, right: left + node.offsetWidth, bottom: top + node.offsetHeight})
    257     },
    258 
    259     triggerOnKeyDown: methodOp(onKeyDown),
    260     triggerOnKeyPress: methodOp(onKeyPress),
    261     triggerOnKeyUp: onKeyUp,
    262     triggerOnMouseDown: methodOp(onMouseDown),
    263 
    264     execCommand: function(cmd) {
    265       if (commands.hasOwnProperty(cmd))
    266         return commands[cmd].call(null, this)
    267     },
    268 
    269     triggerElectric: methodOp(function(text) { triggerElectric(this, text) }),
    270 
    271     findPosH: function(from, amount, unit, visually) {
    272       let dir = 1
    273       if (amount < 0) { dir = -1; amount = -amount }
    274       let cur = clipPos(this.doc, from)
    275       for (let i = 0; i < amount; ++i) {
    276         cur = findPosH(this.doc, cur, dir, unit, visually)
    277         if (cur.hitSide) break
    278       }
    279       return cur
    280     },
    281 
    282     moveH: methodOp(function(dir, unit) {
    283       this.extendSelectionsBy(range => {
    284         if (this.display.shift || this.doc.extend || range.empty())
    285           return findPosH(this.doc, range.head, dir, unit, this.options.rtlMoveVisually)
    286         else
    287           return dir < 0 ? range.from() : range.to()
    288       }, sel_move)
    289     }),
    290 
    291     deleteH: methodOp(function(dir, unit) {
    292       let sel = this.doc.sel, doc = this.doc
    293       if (sel.somethingSelected())
    294         doc.replaceSelection("", null, "+delete")
    295       else
    296         deleteNearSelection(this, range => {
    297           let other = findPosH(doc, range.head, dir, unit, false)
    298           return dir < 0 ? {from: other, to: range.head} : {from: range.head, to: other}
    299         })
    300     }),
    301 
    302     findPosV: function(from, amount, unit, goalColumn) {
    303       let dir = 1, x = goalColumn
    304       if (amount < 0) { dir = -1; amount = -amount }
    305       let cur = clipPos(this.doc, from)
    306       for (let i = 0; i < amount; ++i) {
    307         let coords = cursorCoords(this, cur, "div")
    308         if (x == null) x = coords.left
    309         else coords.left = x
    310         cur = findPosV(this, coords, dir, unit)
    311         if (cur.hitSide) break
    312       }
    313       return cur
    314     },
    315 
    316     moveV: methodOp(function(dir, unit) {
    317       let doc = this.doc, goals = []
    318       let collapse = !this.display.shift && !doc.extend && doc.sel.somethingSelected()
    319       doc.extendSelectionsBy(range => {
    320         if (collapse)
    321           return dir < 0 ? range.from() : range.to()
    322         let headPos = cursorCoords(this, range.head, "div")
    323         if (range.goalColumn != null) headPos.left = range.goalColumn
    324         goals.push(headPos.left)
    325         let pos = findPosV(this, headPos, dir, unit)
    326         if (unit == "page" && range == doc.sel.primary())
    327           addToScrollTop(this, charCoords(this, pos, "div").top - headPos.top)
    328         return pos
    329       }, sel_move)
    330       if (goals.length) for (let i = 0; i < doc.sel.ranges.length; i++)
    331         doc.sel.ranges[i].goalColumn = goals[i]
    332     }),
    333 
    334     // Find the word at the given position (as returned by coordsChar).
    335     findWordAt: function(pos) {
    336       let doc = this.doc, line = getLine(doc, pos.line).text
    337       let start = pos.ch, end = pos.ch
    338       if (line) {
    339         let helper = this.getHelper(pos, "wordChars")
    340         if ((pos.sticky == "before" || end == line.length) && start) --start; else ++end
    341         let startChar = line.charAt(start)
    342         let check = isWordChar(startChar, helper)
    343           ? ch => isWordChar(ch, helper)
    344           : /\s/.test(startChar) ? ch => /\s/.test(ch)
    345           : ch => (!/\s/.test(ch) && !isWordChar(ch))
    346         while (start > 0 && check(line.charAt(start - 1))) --start
    347         while (end < line.length && check(line.charAt(end))) ++end
    348       }
    349       return new Range(Pos(pos.line, start), Pos(pos.line, end))
    350     },
    351 
    352     toggleOverwrite: function(value) {
    353       if (value != null && value == this.state.overwrite) return
    354       if (this.state.overwrite = !this.state.overwrite)
    355         addClass(this.display.cursorDiv, "CodeMirror-overwrite")
    356       else
    357         rmClass(this.display.cursorDiv, "CodeMirror-overwrite")
    358 
    359       signal(this, "overwriteToggle", this, this.state.overwrite)
    360     },
    361     hasFocus: function() { return this.display.input.getField() == activeElt() },
    362     isReadOnly: function() { return !!(this.options.readOnly || this.doc.cantEdit) },
    363 
    364     scrollTo: methodOp(function (x, y) { scrollToCoords(this, x, y) }),
    365     getScrollInfo: function() {
    366       let scroller = this.display.scroller
    367       return {left: scroller.scrollLeft, top: scroller.scrollTop,
    368               height: scroller.scrollHeight - scrollGap(this) - this.display.barHeight,
    369               width: scroller.scrollWidth - scrollGap(this) - this.display.barWidth,
    370               clientHeight: displayHeight(this), clientWidth: displayWidth(this)}
    371     },
    372 
    373     scrollIntoView: methodOp(function(range, margin) {
    374       if (range == null) {
    375         range = {from: this.doc.sel.primary().head, to: null}
    376         if (margin == null) margin = this.options.cursorScrollMargin
    377       } else if (typeof range == "number") {
    378         range = {from: Pos(range, 0), to: null}
    379       } else if (range.from == null) {
    380         range = {from: range, to: null}
    381       }
    382       if (!range.to) range.to = range.from
    383       range.margin = margin || 0
    384 
    385       if (range.from.line != null) {
    386         scrollToRange(this, range)
    387       } else {
    388         scrollToCoordsRange(this, range.from, range.to, range.margin)
    389       }
    390     }),
    391 
    392     setSize: methodOp(function(width, height) {
    393       let interpret = val => typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val
    394       if (width != null) this.display.wrapper.style.width = interpret(width)
    395       if (height != null) this.display.wrapper.style.height = interpret(height)
    396       if (this.options.lineWrapping) clearLineMeasurementCache(this)
    397       let lineNo = this.display.viewFrom
    398       this.doc.iter(lineNo, this.display.viewTo, line => {
    399         if (line.widgets) for (let i = 0; i < line.widgets.length; i++)
    400           if (line.widgets[i].noHScroll) { regLineChange(this, lineNo, "widget"); break }
    401         ++lineNo
    402       })
    403       this.curOp.forceUpdate = true
    404       signal(this, "refresh", this)
    405     }),
    406 
    407     operation: function(f){return runInOp(this, f)},
    408     startOperation: function(){return startOperation(this)},
    409     endOperation: function(){return endOperation(this)},
    410 
    411     refresh: methodOp(function() {
    412       let oldHeight = this.display.cachedTextHeight
    413       regChange(this)
    414       this.curOp.forceUpdate = true
    415       clearCaches(this)
    416       scrollToCoords(this, this.doc.scrollLeft, this.doc.scrollTop)
    417       updateGutterSpace(this)
    418       if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5)
    419         estimateLineHeights(this)
    420       signal(this, "refresh", this)
    421     }),
    422 
    423     swapDoc: methodOp(function(doc) {
    424       let old = this.doc
    425       old.cm = null
    426       attachDoc(this, doc)
    427       clearCaches(this)
    428       this.display.input.reset()
    429       scrollToCoords(this, doc.scrollLeft, doc.scrollTop)
    430       this.curOp.forceScroll = true
    431       signalLater(this, "swapDoc", this, old)
    432       return old
    433     }),
    434 
    435     getInputField: function(){return this.display.input.getField()},
    436     getWrapperElement: function(){return this.display.wrapper},
    437     getScrollerElement: function(){return this.display.scroller},
    438     getGutterElement: function(){return this.display.gutters}
    439   }
    440   eventMixin(CodeMirror)
    441 
    442   CodeMirror.registerHelper = function(type, name, value) {
    443     if (!helpers.hasOwnProperty(type)) helpers[type] = CodeMirror[type] = {_global: []}
    444     helpers[type][name] = value
    445   }
    446   CodeMirror.registerGlobalHelper = function(type, name, predicate, value) {
    447     CodeMirror.registerHelper(type, name, value)
    448     helpers[type]._global.push({pred: predicate, val: value})
    449   }
    450 }
    451 
    452 // Used for horizontal relative motion. Dir is -1 or 1 (left or
    453 // right), unit can be "char", "column" (like char, but doesn't
    454 // cross line boundaries), "word" (across next word), or "group" (to
    455 // the start of next group of word or non-word-non-whitespace
    456 // chars). The visually param controls whether, in right-to-left
    457 // text, direction 1 means to move towards the next index in the
    458 // string, or towards the character to the right of the current
    459 // position. The resulting position will have a hitSide=true
    460 // property if it reached the end of the document.
    461 function findPosH(doc, pos, dir, unit, visually) {
    462   let oldPos = pos
    463   let origDir = dir
    464   let lineObj = getLine(doc, pos.line)
    465   function findNextLine() {
    466     let l = pos.line + dir
    467     if (l < doc.first || l >= doc.first + doc.size) return false
    468     pos = new Pos(l, pos.ch, pos.sticky)
    469     return lineObj = getLine(doc, l)
    470   }
    471   function moveOnce(boundToLine) {
    472     let next
    473     if (visually) {
    474       next = moveVisually(doc.cm, lineObj, pos, dir)
    475     } else {
    476       next = moveLogically(lineObj, pos, dir)
    477     }
    478     if (next == null) {
    479       if (!boundToLine && findNextLine())
    480         pos = endOfLine(visually, doc.cm, lineObj, pos.line, dir)
    481       else
    482         return false
    483     } else {
    484       pos = next
    485     }
    486     return true
    487   }
    488 
    489   if (unit == "char") {
    490     moveOnce()
    491   } else if (unit == "column") {
    492     moveOnce(true)
    493   } else if (unit == "word" || unit == "group") {
    494     let sawType = null, group = unit == "group"
    495     let helper = doc.cm && doc.cm.getHelper(pos, "wordChars")
    496     for (let first = true;; first = false) {
    497       if (dir < 0 && !moveOnce(!first)) break
    498       let cur = lineObj.text.charAt(pos.ch) || "\n"
    499       let type = isWordChar(cur, helper) ? "w"
    500         : group && cur == "\n" ? "n"
    501         : !group || /\s/.test(cur) ? null
    502         : "p"
    503       if (group && !first && !type) type = "s"
    504       if (sawType && sawType != type) {
    505         if (dir < 0) {dir = 1; moveOnce(); pos.sticky = "after"}
    506         break
    507       }
    508 
    509       if (type) sawType = type
    510       if (dir > 0 && !moveOnce(!first)) break
    511     }
    512   }
    513   let result = skipAtomic(doc, pos, oldPos, origDir, true)
    514   if (equalCursorPos(oldPos, result)) result.hitSide = true
    515   return result
    516 }
    517 
    518 // For relative vertical movement. Dir may be -1 or 1. Unit can be
    519 // "page" or "line". The resulting position will have a hitSide=true
    520 // property if it reached the end of the document.
    521 function findPosV(cm, pos, dir, unit) {
    522   let doc = cm.doc, x = pos.left, y
    523   if (unit == "page") {
    524     let pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight)
    525     let moveAmount = Math.max(pageSize - .5 * textHeight(cm.display), 3)
    526     y = (dir > 0 ? pos.bottom : pos.top) + dir * moveAmount
    527 
    528   } else if (unit == "line") {
    529     y = dir > 0 ? pos.bottom + 3 : pos.top - 3
    530   }
    531   let target
    532   for (;;) {
    533     target = coordsChar(cm, x, y)
    534     if (!target.outside) break
    535     if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break }
    536     y += dir * 5
    537   }
    538   return target
    539 }