mirror of
https://github.com/atom/atom.git
synced 2026-04-28 03:01:47 -04:00
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:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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) |
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user