Centralize key binding logic in KeyBinder and jQuery.fn.bindKey extension.

This commit removes window.bindKey in favor of binding keys on dom elements. It also refactors pattern parsing in the test helper to use KeyBinder.parseKeyPattern.
This commit is contained in:
Nathan Sobo
2011-12-30 13:19:41 -06:00
parent 5273cb0638
commit a1e0039890
8 changed files with 82 additions and 284 deletions

View File

@@ -1,11 +1,14 @@
Native = require 'native'
KeyBinder = require 'key-binder'
module.exports =
class App
native: null
keyBinder: null
constructor: ->
@native = new Native
@keyBinder = new KeyBinder
open: (url) ->
OSX.NSApp.open url

View File

@@ -8,12 +8,10 @@ RootView = require 'root-view'
# the DOM window.
windowAdditions =
keyBindings: null
rootView: null
menuItemActions: null
startup: ->
@keyBindings = {}
@menuItemActions = {}
@rootView = RootView.attach()
@rootView.editor.open $atomController.url?.toString()
@@ -29,9 +27,9 @@ windowAdditions =
$(window).unbind('keydown')
bindKeys: ->
@bindKey 'meta+s', => @rootView.editor.save()
@bindKey 'meta+w', => @close()
@bindKey 'meta+t', => @rootView.toggleFileFinder()
$(document).bindKey 'meta+s', => @rootView.editor.save()
$(document).bindKey 'meta+w', => @close()
$(document).bindKey 'meta+t', => @rootView.toggleFileFinder()
bindMenuItems: ->
@bindMenuItem "File > Save", "meta+s", => @rootView.editor.save()
@@ -39,57 +37,10 @@ windowAdditions =
bindMenuItem: (path, pattern, action) ->
@menuItemActions[path] = {action: action, pattern: pattern}
bindKey: (pattern, action) ->
@keyBindings[pattern] = action
keyEventMatchesPattern: (event, pattern) ->
keys = @parseKeyPattern pattern
keys.ctrlKey == event.ctrlKey and
keys.altKey == event.altKey and
keys.shiftKey == event.shiftKey and
keys.metaKey == event.metaKey and
event.which == keys.charCode
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
parseKeyPattern: (pattern) ->
[modifiers..., key] = pattern.split '+'
if window.namedKeys[key]
charCode = window.namedKeys[key]
key = null
else
charCode = key.toUpperCase().charCodeAt 0
ctrlKey: 'ctrl' in modifiers
altKey: 'alt' in modifiers
shiftKey: 'shift' in modifiers
metaKey: 'meta' in modifiers
charCode: charCode
key: key
registerEventHandlers: ->
$(document).bind 'keydown', (event) =>
for pattern, action of @keyBindings
action() if @keyEventMatchesPattern(event, pattern)
$(window).focus => @registerMenuItems()
$(window).blur -> atom.native.resetMainMenu()
registerMenuItems: ->
for path, {pattern} of @menuItemActions
atom.native.addMenuItem(path, pattern)

View File

@@ -1,167 +1,40 @@
_ = 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"
load: (path) ->
path = require.resolve path
if not fs.isFile path
console.warn "Could not find keyBinding file '#{path}'"
return
try
# Watcher.watch path, =>
# @load path
json = (CoffeeScript.eval "return " + (fs.read path)) or {}
# 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 evaluate 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 "-"
try
@triggerBinding binding
catch error
console.error "Failed to run binding #{@bindingFromAscii binding}."
console.error error
# Given a keyboard combination, goes through the responder
# chain and checks if any object (or any of that object's super
# classes) respond to the binding.
#
# If so, it triggers the binding.
#
# binding - A String in the form of "#{charCode}-#{chardCode}"
#
# Returns true if we found and triggered the binding, false if not.
triggerBinding: (binding) ->
for responder in @responders()
name = responder.constructor.name?.toLowerCase()
name = 'window' if responder is window
if method = @keymaps[name]?[binding]
if _.isFunction method
method responder
else
responder[method]()
return true
false
responders: ->
extensions = _.select (_.values atom.extensions), (extension) ->
extension.running?
_.flatten [ extensions, window.resource.responder(), window, atom.app ]
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 k.toLowerCase() isnt k
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: OSX.NSLeftArrowFunctionKey
up: OSX.NSUpArrowFunctionKey
right: OSX.NSRightArrowFunctionKey
down: OSX.NSDownArrowFunctionKey
del: 46
delete: 46
home: 36
end: 35
pageup: 33
pagedown: 34
',': 188
'.': 190
'/': 191
'`': 192
'-': 189
'=': 187
';': 186
'\'': 222
'[': 219
']': 221
'\\': 220
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
keyEventMatchesPattern: (event, pattern) ->
pattern = @parseKeyPattern pattern
pattern.ctrlKey == event.ctrlKey and
pattern.altKey == event.altKey and
pattern.shiftKey == event.shiftKey and
pattern.metaKey == event.metaKey and
pattern.which == event.which
parseKeyPattern: (pattern) ->
[modifiers..., key] = pattern.split '+'
if @namedKeys[key]
charCode = @namedKeys[key]
key = null
else
charCode = key.toUpperCase().charCodeAt 0
ctrlKey: 'ctrl' in modifiers
altKey: 'alt' in modifiers
shiftKey: 'shift' in modifiers
metaKey: 'meta' in modifiers
which: charCode
key: key

View File

@@ -54,7 +54,7 @@ class Native
item.setKeyEquivalentModifierMask 0 # Because it Cocoa defaults it to NSCommandKeyMask
if keyPattern
keys = window.parseKeyPattern keyPattern
keys = atom.keyBinder.parseKeyPattern keyPattern
modifierMask = (keys.metaKey and OSX.NSCommandKeyMask ) |
(keys.shiftKey and OSX.NSShiftKeyMask) |

View File

@@ -53,7 +53,7 @@ $.fn.view = ->
$.fn.bindKey = (pattern, action) ->
@on 'keydown', (event) =>
if window.keyEventMatchesPattern(event, pattern)
if atom.keyBinder.keyEventMatchesPattern(event, pattern)
if _.isString(action)
this.view()[action]()
else