fe5eaa96a323397ba72af4f1a012132e53b40d0004fe79833acc2ed0ffdb5a852c22a5896b5cc602833b464c168b2be2ebc2bcbc262398ded32150df6f8e6e 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. import { flipCtrlCmd, mac, presto } from "../util/browser.js"
  2. import { map } from "../util/misc.js"
  3. import { keyNames } from "./keynames.js"
  4. export let keyMap = {}
  5. keyMap.basic = {
  6. "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown",
  7. "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown",
  8. "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "delCharBefore",
  9. "Tab": "defaultTab", "Shift-Tab": "indentAuto",
  10. "Enter": "newlineAndIndent", "Insert": "toggleOverwrite",
  11. "Esc": "singleSelection"
  12. }
  13. // Note that the save and find-related commands aren't defined by
  14. // default. User code or addons can define them. Unknown commands
  15. // are simply ignored.
  16. keyMap.pcDefault = {
  17. "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo",
  18. "Ctrl-Home": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Up": "goLineUp", "Ctrl-Down": "goLineDown",
  19. "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd",
  20. "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find",
  21. "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll",
  22. "Ctrl-[": "indentLess", "Ctrl-]": "indentMore",
  23. "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSelection",
  24. "fallthrough": "basic"
  25. }
  26. // Very basic readline/emacs-style bindings, which are standard on Mac.
  27. keyMap.emacsy = {
  28. "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown",
  29. "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp",
  30. "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine",
  31. "Ctrl-T": "transposeChars", "Ctrl-O": "openLine"
  32. }
  33. keyMap.macDefault = {
  34. "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo",
  35. "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft",
  36. "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineRight", "Alt-Backspace": "delGroupBefore",
  37. "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find",
  38. "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll",
  39. "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLineLeft", "Cmd-Delete": "delWrappedLineRight",
  40. "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocStart", "Ctrl-Down": "goDocEnd",
  41. "fallthrough": ["basic", "emacsy"]
  42. }
  43. keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault
  44. // KEYMAP DISPATCH
  45. function normalizeKeyName(name) {
  46. let parts = name.split(/-(?!$)/)
  47. name = parts[parts.length - 1]
  48. let alt, ctrl, shift, cmd
  49. for (let i = 0; i < parts.length - 1; i++) {
  50. let mod = parts[i]
  51. if (/^(cmd|meta|m)$/i.test(mod)) cmd = true
  52. else if (/^a(lt)?$/i.test(mod)) alt = true
  53. else if (/^(c|ctrl|control)$/i.test(mod)) ctrl = true
  54. else if (/^s(hift)?$/i.test(mod)) shift = true
  55. else throw new Error("Unrecognized modifier name: " + mod)
  56. }
  57. if (alt) name = "Alt-" + name
  58. if (ctrl) name = "Ctrl-" + name
  59. if (cmd) name = "Cmd-" + name
  60. if (shift) name = "Shift-" + name
  61. return name
  62. }
  63. // This is a kludge to keep keymaps mostly working as raw objects
  64. // (backwards compatibility) while at the same time support features
  65. // like normalization and multi-stroke key bindings. It compiles a
  66. // new normalized keymap, and then updates the old object to reflect
  67. // this.
  68. export function normalizeKeyMap(keymap) {
  69. let copy = {}
  70. for (let keyname in keymap) if (keymap.hasOwnProperty(keyname)) {
  71. let value = keymap[keyname]
  72. if (/^(name|fallthrough|(de|at)tach)$/.test(keyname)) continue
  73. if (value == "...") { delete keymap[keyname]; continue }
  74. let keys = map(keyname.split(" "), normalizeKeyName)
  75. for (let i = 0; i < keys.length; i++) {
  76. let val, name
  77. if (i == keys.length - 1) {
  78. name = keys.join(" ")
  79. val = value
  80. } else {
  81. name = keys.slice(0, i + 1).join(" ")
  82. val = "..."
  83. }
  84. let prev = copy[name]
  85. if (!prev) copy[name] = val
  86. else if (prev != val) throw new Error("Inconsistent bindings for " + name)
  87. }
  88. delete keymap[keyname]
  89. }
  90. for (let prop in copy) keymap[prop] = copy[prop]
  91. return keymap
  92. }
  93. export function lookupKey(key, map, handle, context) {
  94. map = getKeyMap(map)
  95. let found = map.call ? map.call(key, context) : map[key]
  96. if (found === false) return "nothing"
  97. if (found === "...") return "multi"
  98. if (found != null && handle(found)) return "handled"
  99. if (map.fallthrough) {
  100. if (Object.prototype.toString.call(map.fallthrough) != "[object Array]")
  101. return lookupKey(key, map.fallthrough, handle, context)
  102. for (let i = 0; i < map.fallthrough.length; i++) {
  103. let result = lookupKey(key, map.fallthrough[i], handle, context)
  104. if (result) return result
  105. }
  106. }
  107. }
  108. // Modifier key presses don't count as 'real' key presses for the
  109. // purpose of keymap fallthrough.
  110. export function isModifierKey(value) {
  111. let name = typeof value == "string" ? value : keyNames[value.keyCode]
  112. return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod"
  113. }
  114. export function addModifierNames(name, event, noShift) {
  115. let base = name
  116. if (event.altKey && base != "Alt") name = "Alt-" + name
  117. if ((flipCtrlCmd ? event.metaKey : event.ctrlKey) && base != "Ctrl") name = "Ctrl-" + name
  118. if ((flipCtrlCmd ? event.ctrlKey : event.metaKey) && base != "Mod") name = "Cmd-" + name
  119. if (!noShift && event.shiftKey && base != "Shift") name = "Shift-" + name
  120. return name
  121. }
  122. // Look up the name of a key as indicated by an event object.
  123. export function keyName(event, noShift) {
  124. if (presto && event.keyCode == 34 && event["char"]) return false
  125. let name = keyNames[event.keyCode]
  126. if (name == null || event.altGraphKey) return false
  127. // Ctrl-ScrollLock has keyCode 3, same as Ctrl-Pause,
  128. // so we'll use event.code when available (Chrome 48+, FF 38+, Safari 10.1+)
  129. if (event.keyCode == 3 && event.code) name = event.code
  130. return addModifierNames(name, event, noShift)
  131. }
  132. export function getKeyMap(val) {
  133. return typeof val == "string" ? keyMap[val] : val
  134. }