719febb627465bf77be0f852686ca728b88a1a16dd25f91be353172f9cd21b2b3e4b154e2b929b602d96d7b0d59d5beb7ee6aa57c0273b5dff05a5ec96bd3f 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. import { signalLater } from "../util/operation_group.js"
  2. import { restartBlink } from "../display/selection.js"
  3. import { isModifierKey, keyName, lookupKey } from "../input/keymap.js"
  4. import { eventInWidget } from "../measurement/widgets.js"
  5. import { ie, ie_version, mac, presto, gecko } from "../util/browser.js"
  6. import { activeElt, addClass, rmClass, root } from "../util/dom.js"
  7. import { e_preventDefault, off, on, signalDOMEvent } from "../util/event.js"
  8. import { hasCopyEvent } from "../util/feature_detection.js"
  9. import { Delayed, Pass } from "../util/misc.js"
  10. import { commands } from "./commands.js"
  11. // Run a handler that was bound to a key.
  12. function doHandleBinding(cm, bound, dropShift) {
  13. if (typeof bound == "string") {
  14. bound = commands[bound]
  15. if (!bound) return false
  16. }
  17. // Ensure previous input has been read, so that the handler sees a
  18. // consistent view of the document
  19. cm.display.input.ensurePolled()
  20. let prevShift = cm.display.shift, done = false
  21. try {
  22. if (cm.isReadOnly()) cm.state.suppressEdits = true
  23. if (dropShift) cm.display.shift = false
  24. done = bound(cm) != Pass
  25. } finally {
  26. cm.display.shift = prevShift
  27. cm.state.suppressEdits = false
  28. }
  29. return done
  30. }
  31. function lookupKeyForEditor(cm, name, handle) {
  32. for (let i = 0; i < cm.state.keyMaps.length; i++) {
  33. let result = lookupKey(name, cm.state.keyMaps[i], handle, cm)
  34. if (result) return result
  35. }
  36. return (cm.options.extraKeys && lookupKey(name, cm.options.extraKeys, handle, cm))
  37. || lookupKey(name, cm.options.keyMap, handle, cm)
  38. }
  39. // Note that, despite the name, this function is also used to check
  40. // for bound mouse clicks.
  41. let stopSeq = new Delayed
  42. export function dispatchKey(cm, name, e, handle) {
  43. let seq = cm.state.keySeq
  44. if (seq) {
  45. if (isModifierKey(name)) return "handled"
  46. if (/\'$/.test(name))
  47. cm.state.keySeq = null
  48. else
  49. stopSeq.set(50, () => {
  50. if (cm.state.keySeq == seq) {
  51. cm.state.keySeq = null
  52. cm.display.input.reset()
  53. }
  54. })
  55. if (dispatchKeyInner(cm, seq + " " + name, e, handle)) return true
  56. }
  57. return dispatchKeyInner(cm, name, e, handle)
  58. }
  59. function dispatchKeyInner(cm, name, e, handle) {
  60. let result = lookupKeyForEditor(cm, name, handle)
  61. if (result == "multi")
  62. cm.state.keySeq = name
  63. if (result == "handled")
  64. signalLater(cm, "keyHandled", cm, name, e)
  65. if (result == "handled" || result == "multi") {
  66. e_preventDefault(e)
  67. restartBlink(cm)
  68. }
  69. return !!result
  70. }
  71. // Handle a key from the keydown event.
  72. function handleKeyBinding(cm, e) {
  73. let name = keyName(e, true)
  74. if (!name) return false
  75. if (e.shiftKey && !cm.state.keySeq) {
  76. // First try to resolve full name (including 'Shift-'). Failing
  77. // that, see if there is a cursor-motion command (starting with
  78. // 'go') bound to the keyname without 'Shift-'.
  79. return dispatchKey(cm, "Shift-" + name, e, b => doHandleBinding(cm, b, true))
  80. || dispatchKey(cm, name, e, b => {
  81. if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion)
  82. return doHandleBinding(cm, b)
  83. })
  84. } else {
  85. return dispatchKey(cm, name, e, b => doHandleBinding(cm, b))
  86. }
  87. }
  88. // Handle a key from the keypress event
  89. function handleCharBinding(cm, e, ch) {
  90. return dispatchKey(cm, "'" + ch + "'", e, b => doHandleBinding(cm, b, true))
  91. }
  92. let lastStoppedKey = null
  93. export function onKeyDown(e) {
  94. let cm = this
  95. if (e.target && e.target != cm.display.input.getField()) return
  96. cm.curOp.focus = activeElt(root(cm))
  97. if (signalDOMEvent(cm, e)) return
  98. // IE does strange things with escape.
  99. if (ie && ie_version < 11 && e.keyCode == 27) e.returnValue = false
  100. let code = e.keyCode
  101. cm.display.shift = code == 16 || e.shiftKey
  102. let handled = handleKeyBinding(cm, e)
  103. if (presto) {
  104. lastStoppedKey = handled ? code : null
  105. // Opera has no cut event... we try to at least catch the key combo
  106. if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey))
  107. cm.replaceSelection("", null, "cut")
  108. }
  109. if (gecko && !mac && !handled && code == 46 && e.shiftKey && !e.ctrlKey && document.execCommand)
  110. document.execCommand("cut")
  111. // Turn mouse into crosshair when Alt is held on Mac.
  112. if (code == 18 && !/\bCodeMirror-crosshair\b/.test(cm.display.lineDiv.className))
  113. showCrossHair(cm)
  114. }
  115. function showCrossHair(cm) {
  116. let lineDiv = cm.display.lineDiv
  117. addClass(lineDiv, "CodeMirror-crosshair")
  118. function up(e) {
  119. if (e.keyCode == 18 || !e.altKey) {
  120. rmClass(lineDiv, "CodeMirror-crosshair")
  121. off(document, "keyup", up)
  122. off(document, "mouseover", up)
  123. }
  124. }
  125. on(document, "keyup", up)
  126. on(document, "mouseover", up)
  127. }
  128. export function onKeyUp(e) {
  129. if (e.keyCode == 16) this.doc.sel.shift = false
  130. signalDOMEvent(this, e)
  131. }
  132. export function onKeyPress(e) {
  133. let cm = this
  134. if (e.target && e.target != cm.display.input.getField()) return
  135. if (eventInWidget(cm.display, e) || signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) return
  136. let keyCode = e.keyCode, charCode = e.charCode
  137. if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return}
  138. if ((presto && (!e.which || e.which < 10)) && handleKeyBinding(cm, e)) return
  139. let ch = String.fromCharCode(charCode == null ? keyCode : charCode)
  140. // Some browsers fire keypress events for backspace
  141. if (ch == "\x08") return
  142. if (handleCharBinding(cm, e, ch)) return
  143. cm.display.input.onKeyPress(e)
  144. }