text: import { Display } from "../display/Display"
  import { onFocus, onBlur } from "../display/focus"
  import { setGuttersForLineNumbers, updateGutters } from "../display/gutters"
  import { maybeUpdateLineNumberWidth } from "../display/line_numbers"
  import { endOperation, operation, startOperation } from "../display/operations"
  import { initScrollbars } from "../display/scrollbars"
  import { onScrollWheel } from "../display/scroll_events"
  import { setScrollLeft, updateScrollTop } from "../display/scrolling"
  import { clipPos, Pos } from "../line/pos"
  import { posFromMouse } from "../measurement/position_measurement"
  import { eventInWidget } from "../measurement/widgets"
  import Doc from "../model/Doc"
  import { attachDoc } from "../model/document_data"
  import { Range } from "../model/selection"
  import { extendSelection } from "../model/selection_updates"
  import { captureRightClick, ie, ie_version, mobile, webkit } from "../util/browser"
  import { e_preventDefault, e_stop, on, signal, signalDOMEvent } from "../util/event"
  import { bind, copyObj, Delayed } from "../util/misc"
  
  import { clearDragCursor, onDragOver, onDragStart, onDrop } from "./drop_events"
  import { ensureGlobalHandlers } from "./global_events"
  import { onKeyDown, onKeyPress, onKeyUp } from "./key_events"
  import { clickInGutter, onContextMenu, onMouseDown } from "./mouse_events"
  import { themeChanged } from "./utils"
  import { defaults, optionHandlers, Init } from "./options"
  
  // A CodeMirror instance represents an editor. This is the object
  // that user code is usually dealing with.
  
  export function CodeMirror(place, options) {
    if (!(this instanceof CodeMirror)) return new CodeMirror(place, options)
  
    this.options = options = options ? copyObj(options) : {}
    // Determine effective options based on given values and defaults.
    copyObj(defaults, options, false)
    setGuttersForLineNumbers(options)
  
    let doc = options.value
    if (typeof doc == "string") doc = new Doc(doc, options.mode, null, options.lineSeparator, options.direction)
    this.doc = doc
  
    let input = new CodeMirror.inputStyles[options.inputStyle](this)
    let display = this.display = new Display(place, doc, input)
    display.wrapper.CodeMirror = this
    updateGutters(this)
    themeChanged(this)
    if (options.lineWrapping)
      this.display.wrapper.className += " CodeMirror-wrap"
    initScrollbars(this)
  
    this.state = {
      keyMaps: [],  // stores maps added by addKeyMap
      overlays: [], // highlighting overlays, as added by addOverlay
      modeGen: 0,   // bumped when mode/overlay changes, used to invalidate highlighting info
      overwrite: false,
      delayingBlurEvent: false,
      focused: false,
      suppressEdits: false, // used to disable editing during key handlers when in readOnly mode
      pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edits in input.poll
      selectingText: false,
      draggingText: false,
      highlight: new Delayed(), // stores highlight worker timeout
      keySeq: null,  // Unfinished key sequence
      specialChars: null
    }
  
    if (options.autofocus && !mobile) display.input.focus()
  
    // Override magic textarea content restore that IE sometimes does
    // on our hidden textarea on reload
    if (ie && ie_version < 11) setTimeout(() => this.display.input.reset(true), 20)
  
    registerEventHandlers(this)
    ensureGlobalHandlers()
  
    startOperation(this)
    this.curOp.forceUpdate = true
    attachDoc(this, doc)
  
    if ((options.autofocus && !mobile) || this.hasFocus())
      setTimeout(bind(onFocus, this), 20)
    else
      onBlur(this)
  
    for (let opt in optionHandlers) if (optionHandlers.hasOwnProperty(opt))
      optionHandlers[opt](this, options[opt], Init)
    maybeUpdateLineNumberWidth(this)
    if (options.finishInit) options.finishInit(this)
    for (let i = 0; i < initHooks.length; ++i) initHooks[i](this)
    endOperation(this)
    // Suppress optimizelegibility in Webkit, since it breaks text
    // measuring on line wrapping boundaries.
    if (webkit && options.lineWrapping &&
        getComputedStyle(display.lineDiv).textRendering == "optimizelegibility")
      display.lineDiv.style.textRendering = "auto"
  }
  
  // The default configuration options.
  CodeMirror.defaults = defaults
  // Functions to run when options are changed.
  CodeMirror.optionHandlers = optionHandlers
  
  export default CodeMirror
  
  // Attach the necessary event handlers when initializing the editor
  function registerEventHandlers(cm) {
    let d = cm.display
    on(d.scroller, "mousedown", operation(cm, onMouseDown))
    // Older IE's will not fire a second mousedown for a double click
    if (ie && ie_version < 11)
      on(d.scroller, "dblclick", operation(cm, e => {
        if (signalDOMEvent(cm, e)) return
        let pos = posFromMouse(cm, e)
        if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return
        e_preventDefault(e)
        let word = cm.findWordAt(pos)
        extendSelection(cm.doc, word.anchor, word.head)
      }))
    else
      on(d.scroller, "dblclick", e => signalDOMEvent(cm, e) || e_preventDefault(e))
    // Some browsers fire contextmenu *after* opening the menu, at
    // which point we can't mess with it anymore. Context menu is
    // handled in onMouseDown for these browsers.
    if (!captureRightClick) on(d.scroller, "contextmenu", e => onContextMenu(cm, e))
  
    // Used to suppress mouse event handling when a touch happens
    let touchFinished, prevTouch = {end: 0}
    function finishTouch() {
      if (d.activeTouch) {
        touchFinished = setTimeout(() => d.activeTouch = null, 1000)
        prevTouch = d.activeTouch
        prevTouch.end = +new Date
      }
    }
    function isMouseLikeTouchEvent(e) {
      if (e.touches.length != 1) return false
      let touch = e.touches[0]
      return touch.radiusX <= 1 && touch.radiusY <= 1
    }
    function farAway(touch, other) {
      if (other.left == null) return true
      let dx = other.left - touch.left, dy = other.top - touch.top
      return dx * dx + dy * dy > 20 * 20
    }
    on(d.scroller, "touchstart", e => {
      if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e) && !clickInGutter(cm, e)) {
        d.input.ensurePolled()
        clearTimeout(touchFinished)
        let now = +new Date
        d.activeTouch = {start: now, moved: false,
                         prev: now - prevTouch.end <= 300 ? prevTouch : null}
        if (e.touches.length == 1) {
          d.activeTouch.left = e.touches[0].pageX
          d.activeTouch.top = e.touches[0].pageY
        }
      }
    })
    on(d.scroller, "touchmove", () => {
      if (d.activeTouch) d.activeTouch.moved = true
    })
    on(d.scroller, "touchend", e => {
      let touch = d.activeTouch
      if (touch && !eventInWidget(d, e) && touch.left != null &&
          !touch.moved && new Date - touch.start < 300) {
        let pos = cm.coordsChar(d.activeTouch, "page"), range
        if (!touch.prev || farAway(touch, touch.prev)) // Single tap
          range = new Range(pos, pos)
        else if (!touch.prev.prev || farAway(touch, touch.prev.prev)) // Double tap
          range = cm.findWordAt(pos)
        else // Triple tap
          range = new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0)))
        cm.setSelection(range.anchor, range.head)
        cm.focus()
        e_preventDefault(e)
      }
      finishTouch()
    })
    on(d.scroller, "touchcancel", finishTouch)
  
    // Sync scrolling between fake scrollbars and real scrollable
    // area, ensure viewport is updated when scrolling.
    on(d.scroller, "scroll", () => {
      if (d.scroller.clientHeight) {
        updateScrollTop(cm, d.scroller.scrollTop)
        setScrollLeft(cm, d.scroller.scrollLeft, true)
        signal(cm, "scroll", cm)
      }
    })
  
    // Listen to wheel events in order to try and update the viewport on time.
    on(d.scroller, "mousewheel", e => onScrollWheel(cm, e))
    on(d.scroller, "DOMMouseScroll", e => onScrollWheel(cm, e))
  
    // Prevent wrapper from ever scrolling
    on(d.wrapper, "scroll", () => d.wrapper.scrollTop = d.wrapper.scrollLeft = 0)
  
    d.dragFunctions = {
      enter: e => {if (!signalDOMEvent(cm, e)) e_stop(e)},
      over: e => {if (!signalDOMEvent(cm, e)) { onDragOver(cm, e); e_stop(e) }},
      start: e => onDragStart(cm, e),
      drop: operation(cm, onDrop),
      leave: e => {if (!signalDOMEvent(cm, e)) { clearDragCursor(cm) }}
    }
  
    let inp = d.input.getField()
    on(inp, "keyup", e => onKeyUp.call(cm, e))
    on(inp, "keydown", operation(cm, onKeyDown))
    on(inp, "keypress", operation(cm, onKeyPress))
    on(inp, "focus", e => onFocus(cm, e))
    on(inp, "blur", e => onBlur(cm, e))
  }
  
  let initHooks = []
  CodeMirror.defineInitHook = f => initHooks.push(f)
  