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