Files
atom/src/stdlib/key-binder.coffee
2011-11-12 02:05:16 -08:00

191 lines
5.0 KiB
CoffeeScript

_ = require 'underscore'
fs = require 'fs'
Watcher = require 'watcher'
{CoffeeScript} = require 'coffee-script'
module.exports =
class KeyBinder
# keymaps are name => { binding: method } mappings
keymaps: {}
constructor: ->
@load require.resolve "key-bindings.coffee"
if fs.isFile "~/.atomicity/key-bindings.coffee"
@load "~/.atomicity/key-bindings.coffee"
register: (name, scope) ->
load: (path) ->
try
# Watcher.watch path, =>
# @load path
json = CoffeeScript.eval "return " + (fs.read path)
# Iterate in reverse order scopes are declared.
# Scope at the top of the file is checked last.
for name in _.keys(json).reverse()
bindings = json[name]
@keymaps[name] ?= {}
for binding, method of bindings
@keymaps[name][@bindingParser binding] = method
catch error
console.error "Can't load key bindings at `#{path}`."
console.error error
handleEvent: (event) ->
keys = []
if event.modifierFlags & OSX.NSCommandKeyMask
keys.push @modifierKeys.command
if event.modifierFlags & OSX.NSControlKeyMask
keys.push @modifierKeys.control
if event.modifierFlags & OSX.NSAlternateKeyMask
keys.push @modifierKeys.alt
if event.modifierFlags & OSX.NSShiftKeyMask
keys.push @modifierKeys.shift
keys.push event.charactersIgnoringModifiers.toLowerCase().charCodeAt 0
binding = keys.sort().join "-"
for scope, bindings of @keymaps
break if method = bindings[binding]
return false if not method
try
@triggerBinding scope, method
catch error
console.error "Failed to run binding #{@bindingFromAscii binding}."
console.error error
true
responders: ->
extensions = _.select (_.values atom.extensions), (extension) -> extension.running?
_.flatten [ extensions, window.resource.responder(), window, atom.app ]
triggerBinding: (scope, method) ->
responder = _.detect @responders(), (responder) =>
(scope is 'window' and responder is window) or
responder.constructor.name?.toLowerCase() is scope or
@inheritedKeymap responder, scope
if responder
if _.isFunction method
method responder
else
responder[method]()
# If you inherit from a class, you inherit its keymap.
#
# Example:
# class GistEditor extends Editor
#
# Will respond to these bindings:
# gisteditor:
# 'cmd+ctrl-g': 'createGist'
# And these:
# editor:
# 'cmd-n': 'new'
#
# Returns a Boolean
inheritedKeymap: (responder, scope) ->
parent = responder.constructor.__super__
while parent?.constructor?.name
if parent.constructor.name.toLowerCase() is scope
return true
else
parent = parent.constructor.__super__
false
bindingParser: (binding) ->
keys = binding.trim().split '-'
modifiers = []
key = null
for k in keys
if modifier = @modifierKeys[k.toLowerCase()]
modifiers.push modifier
else if key
throw "#{@name}: #{binding} specifies TWO keys, we don't handle that yet."
else if namedKey = @namedKeys[k.toLowerCase()]
key = namedKey
else if shiftedKey = @shiftedKeys[k.charCodeAt 0]
if not _.include modifiers, @modifierKeys.shift
modifiers.push @modifierKeys.shift
key = k.toLowerCase().charCodeAt 0
else if k.length > 1
throw "#{@name}: #{binding} uses an unknown key #{k}."
else
charCode = k.charCodeAt 0
key = k.charCodeAt 0
modifiers.concat(key).sort().join "-"
bindingFromAscii: (binding) ->
inverseModifierKeys = {}
inverseModifierKeys[number] = label for label, number of @modifierKeys
inverseNamedKeys = {}
inverseNamedKeys[number] = label for label, number of @namedKeys
asciiKeys = binding.split '-'
keys = []
for asciiKey in asciiKeys.reverse()
key = inverseModifierKeys[asciiKey]
key ?= inverseNamedKeys[asciiKey]
key ?= String.fromCharCode asciiKey
keys.push key or "?"
keys.join '-'
modifierKeys:
'': 16
'': 91
'': 18
shift: 16
alt: 18
option: 18
control: 17
ctrl: 17
command: 91
cmd: 91
namedKeys:
backspace: 8
tab: 9
clear: 12
enter: 13
return: 13
esc: 27
escape: 27
space: 32
left: 37
up: 38
right: 39
down: 40
del: 46
delete: 46
home: 36
end: 35
pageup: 33
pagedown: 34
',': 188
'.': 190
'/': 191
'`': 192
'-': 189
'=': 187
';': 186
'\'': 222
'[': 219
']': 221
'\\': 220
shiftedKeys:
48: ')', 49: '!', 50: '@', 51: '#', 52: '$', 53: '%', 54: '^'
55: '&', 56: '*', 57: '(', 65: 'A', 66: 'B', 67: 'C', 68: 'D'
69: 'E', 70: 'F', 71: 'G', 72: 'H', 73: 'I', 74: 'J', 75: 'K'
76: 'L', 77: 'M', 78: 'N', 79: 'O', 80: 'P', 81: 'Q', 82: 'R'
83: 'S', 84: 'T', 85: 'U', 86: 'V', 87: 'W', 88: 'X', 89: 'Y'
90: 'Z', 186: ':', 187: '+', 188: '<', 189: '_', 190: '>'
191: '?', 192: '~', 219: '{', 220: '|', 221: '}', 222: '"'