feat: add Keymap plugin (#68)
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
local function read(path)
|
||||
local file = assert(io.open(path, "rb"))
|
||||
local value = file:read("*a")
|
||||
file:close()
|
||||
return value
|
||||
end
|
||||
|
||||
local function stateMock()
|
||||
local values, watchers = {}, {}
|
||||
return values, {
|
||||
get = function(key) return values[key] end,
|
||||
set = function(key, value)
|
||||
values[key] = value
|
||||
if watchers[key] ~= nil then watchers[key](value) end
|
||||
end,
|
||||
watch = function(key, callback) watchers[key] = callback end,
|
||||
}
|
||||
end
|
||||
|
||||
local function assertExample(snapshot, compositor, expectedTotal)
|
||||
assert(type(snapshot) == "table" and snapshot.status == "ready", compositor .. ": snapshot not ready")
|
||||
assert(snapshot.compositor == compositor, compositor .. ": wrong compositor")
|
||||
assert(snapshot.total == expectedTotal, compositor .. ": unexpected bind count " .. tostring(snapshot.total))
|
||||
local expected = {
|
||||
["Applications"] = true,
|
||||
["Windows"] = true,
|
||||
["Workspaces"] = true,
|
||||
["Screenshots"] = true,
|
||||
["Noctalia"] = true,
|
||||
["Media"] = true,
|
||||
["Utilities"] = true,
|
||||
}
|
||||
local found = {}
|
||||
for _, category in ipairs(snapshot.categories or {}) do
|
||||
found[category.name] = true
|
||||
for _, bind in ipairs(category.binds or {}) do
|
||||
assert(type(bind.id) == "string" and bind.id ~= "", compositor .. ": bind id missing")
|
||||
assert(#bind.id <= 249, compositor .. ": bind id exceeds DnD target budget")
|
||||
assert(#("before|" .. bind.id) <= 256, compositor .. ": reorder target exceeds core limit")
|
||||
end
|
||||
end
|
||||
for name, _ in pairs(expected) do
|
||||
assert(found[name], compositor .. ": missing category " .. name)
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
local values, state = stateMock()
|
||||
local source = read("examples/hyprland.lua")
|
||||
local descriptions = {}
|
||||
local modifierMasks = { SUPER = 64, SHIFT = 1, CTRL = 4, ALT = 8 }
|
||||
for line in source:gmatch("[^\r\n]+") do
|
||||
local combo = line:match('hl%.bind%s*%(%s*"([^"]+)"')
|
||||
local description = line:match('description%s*=%s*"([^"]+)"')
|
||||
if combo ~= nil and description ~= nil then
|
||||
local modmask, key = 0, nil
|
||||
for token in combo:gmatch("[^+]+") do
|
||||
local normalized = token:match("^%s*(.-)%s*$")
|
||||
local mask = modifierMasks[normalized:upper()]
|
||||
if mask ~= nil then modmask = modmask + mask else key = normalized end
|
||||
end
|
||||
assert(key ~= nil, "Hyprland example bind has no non-modifier key: " .. combo)
|
||||
descriptions[#descriptions + 1] = {
|
||||
modmask, key, description, line:match("release%s*=%s*true") ~= nil,
|
||||
}
|
||||
end
|
||||
end
|
||||
local plainBlocks = {}
|
||||
for index, item in ipairs(descriptions) do
|
||||
plainBlocks[#plainBlocks + 1] = table.concat({
|
||||
item[4] == true and "bindrd" or "bindd",
|
||||
"\tmodmask: " .. tostring(item[1]),
|
||||
"\tsubmap: ",
|
||||
"\tkey: " .. item[2],
|
||||
"\tkeycode: 0",
|
||||
"\tcatchall: false",
|
||||
"\tdescription: " .. item[3],
|
||||
"\tdispatcher: __lua",
|
||||
"\targ: " .. tostring(index),
|
||||
}, "\n")
|
||||
end
|
||||
local plain = table.concat(plainBlocks, "\n\n") .. "\n"
|
||||
noctalia = {
|
||||
state = state,
|
||||
getConfig = function(key)
|
||||
local config = {
|
||||
compositor = "hyprland", hyprland_config = "/missing/hyprland.lua",
|
||||
merge_sequential = false, show_undescribed = true,
|
||||
}
|
||||
return config[key]
|
||||
end,
|
||||
getenv = function(key)
|
||||
if key == "HYPRLAND_INSTANCE_SIGNATURE" then return "test" end
|
||||
if key == "HOME" then return "/example-home" end
|
||||
return ""
|
||||
end,
|
||||
expandPath = function(path) return path end,
|
||||
fileExists = function(path) return path == "/example-home/.config/hypr/shortcuts.lua" end,
|
||||
listDir = function(path)
|
||||
return path == "/example-home/.config/hypr"
|
||||
and { "colors.lua", "keymap.lua", "old.lua.backup", "shortcuts.lua" } or nil
|
||||
end,
|
||||
readFile = function(path) return path == "/example-home/.config/hypr/shortcuts.lua" and source or nil end,
|
||||
commandExists = function(command) return command == "hyprctl" end,
|
||||
tr = function(key)
|
||||
return ({ ["category.other"] = "Other", ["category.undescribed"] = "Without description" })[key] or key
|
||||
end,
|
||||
runAsync = function(command, callback)
|
||||
callback({ exitCode = 0, timedOut = false, stdout = command == "hyprctl binds" and plain or "invalid-json" })
|
||||
return true
|
||||
end,
|
||||
json = { decode = function() error("malformed JSON fixture") end },
|
||||
}
|
||||
assert(loadfile("service.luau"))()
|
||||
assertExample(values["keymap.snapshot"], "Hyprland", 40)
|
||||
assert(values["keymap.snapshot"].source == "/example-home/.config/hypr/shortcuts.lua")
|
||||
end
|
||||
|
||||
do
|
||||
local values, state = stateMock()
|
||||
local source = read("examples/niri.kdl")
|
||||
noctalia = {
|
||||
state = state,
|
||||
getConfig = function(key)
|
||||
local config = { compositor = "niri", niri_config = "/missing/niri.kdl", merge_sequential = false }
|
||||
return config[key]
|
||||
end,
|
||||
getenv = function(key)
|
||||
if key == "NIRI_SOCKET" then return "test" end
|
||||
if key == "HOME" then return "/example-home" end
|
||||
return ""
|
||||
end,
|
||||
fileExists = function(path) return path == "/example-home/.config/niri/config.kdl" end,
|
||||
readFile = function(path) return path == "/example-home/.config/niri/config.kdl" and source or nil end,
|
||||
tr = function(key) return key == "category.other" and "Other" or key end,
|
||||
}
|
||||
assert(loadfile("niri_service.luau"))()
|
||||
assertExample(values["keymap.snapshot"], "Niri", 39)
|
||||
assert(values["keymap.snapshot"].source == "/example-home/.config/niri/config.kdl")
|
||||
end
|
||||
|
||||
do
|
||||
local values, state = stateMock()
|
||||
local source = read("examples/mangowc.conf")
|
||||
noctalia = {
|
||||
state = state,
|
||||
getConfig = function(key)
|
||||
local config = { compositor = "mangowc", mangowc_config = "/missing/mangowc.conf", merge_sequential = false }
|
||||
return config[key]
|
||||
end,
|
||||
getenv = function(key)
|
||||
if key == "MANGO_INSTANCE_SIGNATURE" then return "test" end
|
||||
if key == "HOME" then return "/example-home" end
|
||||
return ""
|
||||
end,
|
||||
expandPath = function(path) return path end,
|
||||
fileExists = function(path) return path == "/example-home/.config/mango/config.conf" end,
|
||||
readFile = function(path) return path == "/example-home/.config/mango/config.conf" and source or nil end,
|
||||
tr = function(key) return key == "category.other" and "Other" or key end,
|
||||
}
|
||||
assert(loadfile("mangowc_service.luau"))()
|
||||
assertExample(values["keymap.snapshot"], "MangoWC", 40)
|
||||
assert(values["keymap.snapshot"].source == "/example-home/.config/mango/config.conf")
|
||||
end
|
||||
|
||||
print("example config tests: ok")
|
||||
Reference in New Issue
Block a user