CodeMirror.min.js (8581B)
1 import { Display } from "../display/Display.js" 2 import { onFocus, onBlur } from "../display/focus.js" 3 import { setGuttersForLineNumbers, updateGutters } from "../display/gutters.js" 4 import { maybeUpdateLineNumberWidth } from "../display/line_numbers.js" 5 import { endOperation, operation, startOperation } from "../display/operations.js" 6 import { initScrollbars } from "../display/scrollbars.js" 7 import { onScrollWheel } from "../display/scroll_events.js" 8 import { setScrollLeft, updateScrollTop } from "../display/scrolling.js" 9 import { clipPos, Pos } from "../line/pos.js" 10 import { posFromMouse } from "../measurement/position_measurement.js" 11 import { eventInWidget } from "../measurement/widgets.js" 12 import Doc from "../model/Doc.js" 13 import { attachDoc } from "../model/document_data.js" 14 import { Range } from "../model/selection.js" 15 import { extendSelection } from "../model/selection_updates.js" 16 import { captureRightClick, ie, ie_version, mobile, webkit } from "../util/browser.js" 17 import { e_preventDefault, e_stop, on, signal, signalDOMEvent } from "../util/event.js" 18 import { bind, copyObj, Delayed } from "../util/misc.js" 19 20 import { clearDragCursor, onDragOver, onDragStart, onDrop } from "./drop_events.js" 21 import { ensureGlobalHandlers } from "./global_events.js" 22 import { onKeyDown, onKeyPress, onKeyUp } from "./key_events.js" 23 import { clickInGutter, onContextMenu, onMouseDown } from "./mouse_events.js" 24 import { themeChanged } from "./utils.js" 25 import { defaults, optionHandlers, Init } from "./options.js" 26 27 // A CodeMirror instance represents an editor. This is the object 28 // that user code is usually dealing with. 29 30 export function CodeMirror(place, options) { 31 if (!(this instanceof CodeMirror)) return new CodeMirror(place, options) 32 33 this.options = options = options ? copyObj(options) : {} 34 // Determine effective options based on given values and defaults. 35 copyObj(defaults, options, false) 36 setGuttersForLineNumbers(options) 37 38 let doc = options.value 39 if (typeof doc == "string") doc = new Doc(doc, options.mode, null, options.lineSeparator, options.direction) 40 this.doc = doc 41 42 let input = new CodeMirror.inputStyles[options.inputStyle](this) 43 let display = this.display = new Display(place, doc, input) 44 display.wrapper.CodeMirror = this 45 updateGutters(this) 46 themeChanged(this) 47 if (options.lineWrapping) 48 this.display.wrapper.className += " CodeMirror-wrap" 49 initScrollbars(this) 50 51 this.state = { 52 keyMaps: [], // stores maps added by addKeyMap 53 overlays: [], // highlighting overlays, as added by addOverlay 54 modeGen: 0, // bumped when mode/overlay changes, used to invalidate highlighting info 55 overwrite: false, 56 delayingBlurEvent: false, 57 focused: false, 58 suppressEdits: false, // used to disable editing during key handlers when in readOnly mode 59 pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edits in input.poll 60 selectingText: false, 61 draggingText: false, 62 highlight: new Delayed(), // stores highlight worker timeout 63 keySeq: null, // Unfinished key sequence 64 specialChars: null 65 } 66 67 if (options.autofocus && !mobile) display.input.focus() 68 69 // Override magic textarea content restore that IE sometimes does 70 // on our hidden textarea on reload 71 if (ie && ie_version < 11) setTimeout(() => this.display.input.reset(true), 20) 72 73 registerEventHandlers(this) 74 ensureGlobalHandlers() 75 76 startOperation(this) 77 this.curOp.forceUpdate = true 78 attachDoc(this, doc) 79 80 if ((options.autofocus && !mobile) || this.hasFocus()) 81 setTimeout(bind(onFocus, this), 20) 82 else 83 onBlur(this) 84 85 for (let opt in optionHandlers) if (optionHandlers.hasOwnProperty(opt)) 86 optionHandlers[opt](this, options[opt], Init) 87 maybeUpdateLineNumberWidth(this) 88 if (options.finishInit) options.finishInit(this) 89 for (let i = 0; i < initHooks.length; ++i) initHooks[i](this) 90 endOperation(this) 91 // Suppress optimizelegibility in Webkit, since it breaks text 92 // measuring on line wrapping boundaries. 93 if (webkit && options.lineWrapping && 94 getComputedStyle(display.lineDiv).textRendering == "optimizelegibility") 95 display.lineDiv.style.textRendering = "auto" 96 } 97 98 // The default configuration options. 99 CodeMirror.defaults = defaults 100 // Functions to run when options are changed. 101 CodeMirror.optionHandlers = optionHandlers 102 103 export default CodeMirror 104 105 // Attach the necessary event handlers when initializing the editor 106 function registerEventHandlers(cm) { 107 let d = cm.display 108 on(d.scroller, "mousedown", operation(cm, onMouseDown)) 109 // Older IE's will not fire a second mousedown for a double click 110 if (ie && ie_version < 11) 111 on(d.scroller, "dblclick", operation(cm, e => { 112 if (signalDOMEvent(cm, e)) return 113 let pos = posFromMouse(cm, e) 114 if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return 115 e_preventDefault(e) 116 let word = cm.findWordAt(pos) 117 extendSelection(cm.doc, word.anchor, word.head) 118 })) 119 else 120 on(d.scroller, "dblclick", e => signalDOMEvent(cm, e) || e_preventDefault(e)) 121 // Some browsers fire contextmenu *after* opening the menu, at 122 // which point we can't mess with it anymore. Context menu is 123 // handled in onMouseDown for these browsers. 124 if (!captureRightClick) on(d.scroller, "contextmenu", e => onContextMenu(cm, e)) 125 126 // Used to suppress mouse event handling when a touch happens 127 let touchFinished, prevTouch = {end: 0} 128 function finishTouch() { 129 if (d.activeTouch) { 130 touchFinished = setTimeout(() => d.activeTouch = null, 1000) 131 prevTouch = d.activeTouch 132 prevTouch.end = +new Date 133 } 134 } 135 function isMouseLikeTouchEvent(e) { 136 if (e.touches.length != 1) return false 137 let touch = e.touches[0] 138 return touch.radiusX <= 1 && touch.radiusY <= 1 139 } 140 function farAway(touch, other) { 141 if (other.left == null) return true 142 let dx = other.left - touch.left, dy = other.top - touch.top 143 return dx * dx + dy * dy > 20 * 20 144 } 145 on(d.scroller, "touchstart", e => { 146 if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e) && !clickInGutter(cm, e)) { 147 d.input.ensurePolled() 148 clearTimeout(touchFinished) 149 let now = +new Date 150 d.activeTouch = {start: now, moved: false, 151 prev: now - prevTouch.end <= 300 ? prevTouch : null} 152 if (e.touches.length == 1) { 153 d.activeTouch.left = e.touches[0].pageX 154 d.activeTouch.top = e.touches[0].pageY 155 } 156 } 157 }) 158 on(d.scroller, "touchmove", () => { 159 if (d.activeTouch) d.activeTouch.moved = true 160 }) 161 on(d.scroller, "touchend", e => { 162 let touch = d.activeTouch 163 if (touch && !eventInWidget(d, e) && touch.left != null && 164 !touch.moved && new Date - touch.start < 300) { 165 let pos = cm.coordsChar(d.activeTouch, "page"), range 166 if (!touch.prev || farAway(touch, touch.prev)) // Single tap 167 range = new Range(pos, pos) 168 else if (!touch.prev.prev || farAway(touch, touch.prev.prev)) // Double tap 169 range = cm.findWordAt(pos) 170 else // Triple tap 171 range = new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))) 172 cm.setSelection(range.anchor, range.head) 173 cm.focus() 174 e_preventDefault(e) 175 } 176 finishTouch() 177 }) 178 on(d.scroller, "touchcancel", finishTouch) 179 180 // Sync scrolling between fake scrollbars and real scrollable 181 // area, ensure viewport is updated when scrolling. 182 on(d.scroller, "scroll", () => { 183 if (d.scroller.clientHeight) { 184 updateScrollTop(cm, d.scroller.scrollTop) 185 setScrollLeft(cm, d.scroller.scrollLeft, true) 186 signal(cm, "scroll", cm) 187 } 188 }) 189 190 // Listen to wheel events in order to try and update the viewport on time. 191 on(d.scroller, "mousewheel", e => onScrollWheel(cm, e)) 192 on(d.scroller, "DOMMouseScroll", e => onScrollWheel(cm, e)) 193 194 // Prevent wrapper from ever scrolling 195 on(d.wrapper, "scroll", () => d.wrapper.scrollTop = d.wrapper.scrollLeft = 0) 196 197 d.dragFunctions = { 198 enter: e => {if (!signalDOMEvent(cm, e)) e_stop(e)}, 199 over: e => {if (!signalDOMEvent(cm, e)) { onDragOver(cm, e); e_stop(e) }}, 200 start: e => onDragStart(cm, e), 201 drop: operation(cm, onDrop), 202 leave: e => {if (!signalDOMEvent(cm, e)) { clearDragCursor(cm) }} 203 } 204 205 let inp = d.input.getField() 206 on(inp, "keyup", e => onKeyUp.call(cm, e)) 207 on(inp, "keydown", operation(cm, onKeyDown)) 208 on(inp, "keypress", operation(cm, onKeyPress)) 209 on(inp, "focus", e => onFocus(cm, e)) 210 on(inp, "blur", e => onBlur(cm, e)) 211 } 212 213 let initHooks = [] 214 CodeMirror.defineInitHook = f => initHooks.push(f)