(when firing the key-press signal)
passes the event, translations, source and parameters
event = character: 'A', key_name: 'a', key_code: 65
with_signal_handler 'key-press', nil, (handler) ->
pcall bindings.process, event, 'editor', {}, 'yowser'
assert.spy(handler).was.called_with {
:event
source: 'editor'
translations: { 'A', 'a', '65' }
parameters: { 'yowser' }
}
returns early with true if some handler says to abort
keymap = A: spy.new -> true
with_signal_handler 'key-press', signal.abort, (handler) ->
_, ret = pcall bindings.process, { character: 'A', key_name: 'A', key_code: 65 }, 'editor', { keymap }
assert.spy(handler).was.called!
assert.spy(keymap.A).was.not_called!
assert.is_true ret
continues processing keymaps unless aborted
keymap = A: spy.new -> true
with_signal_handler 'key-press', false, (handler) ->
pcall bindings.process, { character: 'A', key_name: 'A', key_code: 65 }, 'editor', { keymap }
assert.spy(keymap.A).was_called!
(when looking up handlers)
tries each translated key and .on_unhandled in order for a keymap, and optional source specific map
keymap = Spy!
bindings.process { character: 'A', key_name: 'a', key_code: 65 }, 'my_source', { keymap }
assert.same { 'my_source', 'binding_for', 'for_os', 'A', 'a', '65', 'on_unhandled' }, keymap.reads
prefers source specific bindings over generic ones
specific_map = A: spy.new -> nil
general_map = {
A: spy.new -> nil
my_source: specific_map
}
bindings.process { character: 'A', key_name: 'a', key_code: 65 }, 'my_source', { general_map }
assert.spy(specific_map.A).was_called(1)
assert.spy(general_map.A).was_not_called!
prefers OS specific bindings over generic ones
specific_map = A: spy.new -> nil
general_map = {
A: spy.new -> nil
for_os:
[sys.info.os]: specific_map
}
bindings.process { character: 'A', key_name: 'a', key_code: 65 }, 'my_source', { general_map }
assert.spy(specific_map.A).was_called(1)
assert.spy(general_map.A).was_not_called!
supports source specific bindings in OS bindings
specific_map = A: spy.new -> nil
general_map = {
for_os:
[sys.info.os]:
A: spy.new -> nil
my_source: specific_map
}
bindings.process { character: 'A', key_name: 'a', key_code: 65 }, 'my_source', { general_map }
assert.spy(specific_map.A).was_called(1)
assert.spy(general_map.for_os[sys.info.os].A).was_not_called!
searches all extra keymaps and the bindings in the stack
key_args = character: 'A', key_name: 'a', key_code: 65
extra_map = Spy!
stack_map = Spy!
bindings.push stack_map
bindings.process key_args, 'editor', { extra_map }
assert.equal 7, #stack_map.reads
assert.same stack_map.reads, extra_map.reads
(.. when .on_unhandled is defined and keys are not found in a keymap)
is called with the event, source, translations and extra parameters
keymap = on_unhandled: spy.new ->
event = character: 'A', key_name: 'a', key_code: 65
bindings.process event, 'editor', {keymap}, 'hello!'
assert.spy(keymap.on_unhandled).was.called_with(event, 'editor', { 'A', 'a', '65' }, 'hello!')
any return is used as the handler
handler = spy.new ->
keymap = on_unhandled: -> handler
bindings.process { character: 'A', key_name: 'A', key_code: 65 }, 'editor', { keymap }
assert.spy(handler).was.called!
(.. when .binding_for is defined and keys are not found in a keymap)
is looked up by keys bound to the commands in .binding_for
handler = spy.new ->
bindings.push a: 'my-command'
bindings.push binding_for: ['my-command']: handler
bindings.process {character: 'a', key_name: 'a', key_code: 97}, ''
assert.spy(handler).was.called!
(.. when a keymap was pushed with options.block set to true)
looks no further down the stack than that keymap
base = k: spy.new -> nil
blocking = {}
bindings.push base
bindings.push blocking, block: true
bindings.process { character: 'k', key_code: 65 }, 'editor'
assert.spy(base.k).was_not_called!
(.. when a keymap was pushed with options.pop set to true)
is automatically popped after the next dispatch
pop_me = k: spy.new -> nil
bindings.push pop_me, pop: true
bindings.process { character: 'k', key_code: 65 }, 'editor'
assert.spy(pop_me.k).was_called!
assert.not_includes bindings.keymaps, pop_me
is popped regardless of whether it contained a matching binding or not
pop_me = {}
bindings.push pop_me, pop: true
bindings.process { character: 'k', key_code: 65 }, 'editor'
assert.not_includes bindings.keymaps, pop_me
is always blocking
base = k: spy.new -> nil
pop_me = k: spy.new -> nil
bindings.push base
bindings.push pop_me, pop: true
bindings.process { character: 'k', key_code: 65 }, 'editor'
assert.spy(pop_me.k).was_called!
assert.spy(base.k).was_not_called!
(when invoking handlers)
invokes handlers in their own coroutines
coros = {}
coro_register = ->
co, main = coroutine.running!
coros[co] = true unless main
keymap = k: coro_register
for _ = 1,2
bindings.process { character: 'k', key_code: 65 }, 'editor', { keymap }
assert.equal 2, #[v for _, v in pairs coros]
returns false if no handlers are found
assert.is_false bindings.process { character: 'k', key_code: 65 }, 'editor'
invokes handlers in extra keymaps before the default keymap
bindings.keymap = k: spy.new -> nil
extra_map = k: spy.new -> nil
bindings.process { character: 'k', key_code: 65 }, 'editor', { extra_map }
assert.spy(extra_map.k).was_called(1)
assert.spy(bindings.keymap.k).was_not_called!
(.. when the handler is callable)
keymap = k: spy.new ->
bindings.process { character: 'k', key_code: 65 }, 'editor', { keymap }, 'reference'
assert.spy(keymap.k).was.called_with('reference')
returns early with true unless a handler explicitly returns false
first = k: spy.new ->
second = k: spy.new ->
assert.is_true bindings.process { character: 'k', key_code: 65 }, 'space', { first, second }
assert.spy(second.k).was.not_called!
(.. when the handler raises an error)
returns true
keymap = { k: -> error 'BOOM!' }
assert.is_true bindings.process { character: 'k', key_code: 65 }, 'mybad', { keymap }
logs an error to the log
keymap = { k: -> error 'a to the k log' }
bindings.process { character: 'k', key_code: 65 }, 'mybad', { keymap }
assert.is_not.equal #log.entries, 0
assert.equal log.entries[#log.entries].message, 'a to the k log'
(.. when the handler is a string)
runs the command with command.run() and returns true
cmd_run = spy.on(command, 'run')
keymap = k: 'spy'
assert.is_true bindings.process { character: 'k', key_code: 65 }, 'editor', { keymap }
command.run\revert!
assert.spy(cmd_run).was.called_with 'spy'
(.. when the handler is a non-callable table)
pushes the table as a new keymap and returns true
nr_bindings = #bindings.keymaps
submap = {}
keymap = k: submap
assert.is_true bindings.process { character: 'k', key_code: 65 }, 'editor', { keymap }
assert.equal nr_bindings + 1, #bindings.keymaps
assert.equal submap, bindings.keymaps[#bindings.keymaps]
pushes the table with the pop option
submap = {}
keymap = k: submap
bindings.process { character: 'k', key_code: 65 }, 'editor', { keymap }
bindings.process { character: 'k', key_code: 65 }, 'editor'
assert.not_includes bindings.keymaps, submap