keybind.lua

Allows specifying a callback to be run when certain key combinations are either pressed or released.

local catnip = require('catnip')

local M = {}

local key_press_callbacks = {} ---@type table<string, fun()[]>
local key_release_callbacks = {} ---@type table<string, fun()[]>

-- -----------------------------------------------------------------------------
-- Types
-- -----------------------------------------------------------------------------

---@alias Modifier "ctrl" | "mod1" | "mod2" | "mod3" | "mod4" | "mod5"

-- -----------------------------------------------------------------------------
-- Helpers
-- -----------------------------------------------------------------------------

---@param modifiers Modifier[]
---@param key string
---@return string
local function get_key_binding_code(modifiers, key)
  local code = { 0, 0, 0, 0, 0, 0, key }

  for _, modifier in ipairs(modifiers) do
    if modifier == 'ctrl' then
      code[1] = 1
    elseif modifier == 'mod1' then
      code[2] = 1
    elseif modifier == 'mod2' then
      code[3] = 1
    elseif modifier == 'mod3' then
      code[4] = 1
    elseif modifier == 'mod4' then
      code[5] = 1
    elseif modifier == 'mod5' then
      code[6] = 1
    end
  end

  return table.concat(code)
end

---@param event CatnipKeyEvent
---@return string
local function get_key_event_code(event)
  local is_printable_ascii = 32 < event.code and event.code < 127

  return table.concat({
    event.ctrl and 1 or 0,
    event.mod1 and 1 or 0,
    event.mod2 and 1 or 0,
    event.mod3 and 1 or 0,
    event.mod4 and 1 or 0,
    event.mod5 and 1 or 0,
    is_printable_ascii and string.char(event.code) or event.name,
  })
end

-- -----------------------------------------------------------------------------
-- API
-- -----------------------------------------------------------------------------

---@param modifiers Modifier[]
---@param key string
---@param callback fun()
function M.press(modifiers, key, callback)
  local code = get_key_binding_code(modifiers, key)
  key_press_callbacks[code] = key_press_callbacks[code] or {}
  table.insert(key_press_callbacks[code], callback)
end

---@param modifiers Modifier[]
---@param key string
---@param callback fun()
function M.release(modifiers, key, callback)
  local code = get_key_binding_code(modifiers, key)
  key_release_callbacks[code] = key_release_callbacks[code] or {}
  table.insert(key_release_callbacks[code], callback)
end

-- -----------------------------------------------------------------------------
-- Subscriptions
-- -----------------------------------------------------------------------------

catnip.subscribe('keyboard::keypress', function(_, event)
  local code = get_key_event_code(event)

  event.prevent_notify = key_press_callbacks[code] ~= nil or key_release_callbacks[code] ~= nil

  if key_press_callbacks[code] ~= nil then
    for _, callback in ipairs(key_press_callbacks[code]) do
      callback()
    end
  end
end)

catnip.subscribe('keyboard::keyrelease', function(_, event)
  local code = get_key_event_code(event)

  event.prevent_notify = key_press_callbacks[code] ~= nil or key_release_callbacks[code] ~= nil

  if key_release_callbacks[code] ~= nil then
    for _, callback in ipairs(key_release_callbacks[code]) do
      callback()
    end
  end
end)

-- -----------------------------------------------------------------------------
-- Return
-- -----------------------------------------------------------------------------

return M