* fix(keymap): reduce Niri parser CPU usage * test(keymap): cover Niri parser scan budget * fix(keymap): keep Hyprland refreshes within CPU budget * chore(keymap): bump release to 1.3.4
1013 lines
44 KiB
Lua
1013 lines
44 KiB
Lua
local files = {}
|
|
local stateValues = {}
|
|
local watchers = {}
|
|
local failVerify = false
|
|
local failReload = false
|
|
local reloadAttempts = 0
|
|
local writeCount = 0
|
|
local failRenameAt = nil
|
|
local renameAttempts = 0
|
|
|
|
noctalia = {
|
|
state = {
|
|
get = function(key) return stateValues[key] end,
|
|
set = function(key, value)
|
|
stateValues[key] = value
|
|
if watchers[key] ~= nil then watchers[key](value) end
|
|
end,
|
|
watch = function(key, callback) watchers[key] = callback end,
|
|
},
|
|
readFile = function(path) return files[path] end,
|
|
fileExists = function(path) return files[path] ~= nil end,
|
|
writeFile = function(path, content)
|
|
writeCount = writeCount + 1
|
|
files[path] = content
|
|
return true
|
|
end,
|
|
renameFile = function(source, target)
|
|
renameAttempts = renameAttempts + 1
|
|
if failRenameAt ~= nil and renameAttempts == failRenameAt then return false end
|
|
if files[source] == nil then return false end
|
|
files[target] = files[source]
|
|
files[source] = nil
|
|
return true
|
|
end,
|
|
removeFile = function(path) files[path] = nil return true end,
|
|
runAsync = function(command, callback, _timeout)
|
|
local result = { exitCode = 0, timedOut = false }
|
|
if failVerify and command:find("verify", 1, true) ~= nil then result.exitCode = 1 end
|
|
if failReload and command == "hyprctl reload" then
|
|
reloadAttempts = reloadAttempts + 1
|
|
if reloadAttempts == 1 then result.exitCode = 1 end
|
|
end
|
|
callback(result)
|
|
return true
|
|
end,
|
|
json = { decode = function() return {} end },
|
|
}
|
|
|
|
local function xorByte(left, right)
|
|
local result, place = 0, 1
|
|
for _ = 1, 8 do
|
|
if left % 2 ~= right % 2 then result = result + place end
|
|
left = math.floor(left / 2)
|
|
right = math.floor(right / 2)
|
|
place = place * 2
|
|
end
|
|
return result
|
|
end
|
|
|
|
local function fingerprint(value)
|
|
local hash = 2166136261
|
|
for index = 1, #value do
|
|
local low = hash % 256
|
|
hash = hash - low + xorByte(low, value:byte(index))
|
|
hash = (hash * 403 + (hash % 256) * 16777216) % 4294967296
|
|
end
|
|
return string.format("%08x", hash)
|
|
end
|
|
|
|
local function hexEncode(value)
|
|
local output = {}
|
|
for index = 1, #value do output[#output + 1] = string.format("%02x", value:byte(index)) end
|
|
return table.concat(output)
|
|
end
|
|
|
|
local function hiddenSnippet(compositor, snippet)
|
|
local comments = { Hyprland = "--", Niri = "//", MangoWC = "#" }
|
|
local indent = snippet:match("^([\t ]*)") or ""
|
|
local marker = indent .. comments[compositor] .. " Keymap hidden v1"
|
|
local blockId = fingerprint(compositor .. "\0" .. snippet)
|
|
local output = { marker .. " begin " .. blockId .. " " .. fingerprint(snippet) }
|
|
for offset = 1, #snippet, 48 do
|
|
output[#output + 1] = marker .. " data " .. hexEncode(snippet:sub(offset, offset + 47))
|
|
end
|
|
output[#output + 1] = marker .. " end " .. blockId
|
|
return table.concat(output, "\n")
|
|
end
|
|
|
|
local function lineCount(value)
|
|
local count = 1
|
|
for _ in value:gmatch("\n") do count = count + 1 end
|
|
return count
|
|
end
|
|
|
|
local function target(id, source, snippet, capabilities, startLine, endLine)
|
|
return {
|
|
id = id, source = source, start_line = startLine or 2, end_line = endLine or 2,
|
|
raw_snippet = snippet, fingerprint = fingerprint(snippet),
|
|
capabilities = capabilities,
|
|
}
|
|
end
|
|
|
|
local function runCase(case)
|
|
files = {}
|
|
stateValues = {}
|
|
local root = "/tmp/keybind-test/" .. case.rootName
|
|
local source = case.separate and "/tmp/keybind-test/entries-" .. case.rootName or root
|
|
files[root] = case.root
|
|
files[source] = case.content
|
|
local bind = target(
|
|
case.id, source, case.snippet, case.capabilities,
|
|
case.startLine, case.endLine
|
|
)
|
|
-- Active Hyprland source entries use exact byte comparison to keep hashing
|
|
-- out of the service's tightly budgeted refresh callback.
|
|
if case.compositor == "Hyprland" then bind.fingerprint = "exact-v1" end
|
|
stateValues["keymap.snapshot"] = {
|
|
status = "ready", compositor = case.compositor, source = root,
|
|
categories = { { name = case.category, binds = { bind } } },
|
|
}
|
|
stateValues["keymap.update_request"] = nil
|
|
failVerify = case.failVerify == true
|
|
failReload = false
|
|
reloadAttempts = 0
|
|
noctalia.state.set("keymap.update_request", {
|
|
request_id = case.id .. "-request", target_id = case.id,
|
|
compositor = case.compositor, source = root,
|
|
modifiers = case.modifiers, keys = case.keys,
|
|
activation = case.activation, command = case.command,
|
|
command_kind = case.commandKind, library_entry_id = case.libraryEntryId,
|
|
description = case.description, category = case.newCategory or case.category,
|
|
})
|
|
local result = stateValues["keymap.update_result"]
|
|
assert(type(result) == "table", case.id .. ": missing update result")
|
|
if case.error ~= nil then
|
|
assert(result.ok == false and result.error == case.error,
|
|
case.id .. ": expected " .. case.error .. ", got " .. tostring(result.error))
|
|
assert(files[source] == case.content, case.id .. ": rejected update changed source")
|
|
elseif case.failVerify then
|
|
assert(result.ok == false and result.error == "verify_failed", case.id .. ": expected verify rollback")
|
|
assert(files[source] == case.content, case.id .. ": rollback did not restore source")
|
|
else
|
|
assert(result.ok == true, case.id .. ": " .. tostring(result.error))
|
|
assert(files[source] == case.expected, case.id .. ": unexpected updated content\n" .. tostring(files[source]))
|
|
end
|
|
end
|
|
|
|
local function runMutationCase(case)
|
|
files = {}
|
|
stateValues = {}
|
|
local root = "/tmp/keybind-test/" .. case.rootName
|
|
local source = case.separate and "/tmp/keybind-test/entries-" .. case.rootName or root
|
|
files[root] = case.root
|
|
files[source] = case.content
|
|
local bind = target(case.id, source, case.targetSnippet or case.snippet, case.capabilities or {
|
|
combo = true, description = true, command = true, activation = true,
|
|
}, case.startLine, case.endLine)
|
|
bind.hidden = case.hidden == true
|
|
bind.original_fingerprint = case.originalFingerprint
|
|
if case.badFingerprint then bind.fingerprint = "00000000" end
|
|
local categories = case.hidden and {} or { { name = case.category or "Windows", binds = { bind } } }
|
|
stateValues["keymap.snapshot"] = {
|
|
status = "ready", compositor = case.compositor, source = root,
|
|
categories = categories, hidden = case.hidden and { bind } or {},
|
|
}
|
|
stateValues["keymap.update_request"] = nil
|
|
failVerify = case.failVerify == true
|
|
failReload = case.failReload == true
|
|
reloadAttempts = 0
|
|
writeCount = 0
|
|
failRenameAt = case.failRenameAt
|
|
renameAttempts = 0
|
|
noctalia.state.set("keymap.update_request", {
|
|
request_id = case.id .. "-request", target_id = case.id,
|
|
operation = case.operation, compositor = case.compositor, source = root, hidden = case.hidden == true,
|
|
})
|
|
local result = stateValues["keymap.update_result"]
|
|
assert(type(result) == "table", case.id .. ": missing mutation result")
|
|
if case.error ~= nil then
|
|
assert(result.ok == false and result.error == case.error,
|
|
case.id .. ": expected " .. case.error .. ", got " .. tostring(result.error))
|
|
assert(files[source] == case.content, case.id .. ": rejected mutation changed source")
|
|
elseif case.failVerify or case.failReload then
|
|
local expectedError = case.failReload and "reload_failed" or "verify_failed"
|
|
assert(result.ok == false and result.error == expectedError,
|
|
case.id .. ": expected " .. expectedError .. ", got " .. tostring(result.error))
|
|
assert(files[source] == case.content, case.id .. ": mutation rollback did not restore source")
|
|
if case.failReload then
|
|
assert(reloadAttempts == 2, case.id .. ": restored config was not reloaded after rollback")
|
|
end
|
|
else
|
|
assert(result.ok == true, case.id .. ": " .. tostring(result.error))
|
|
assert(files[source] == case.expected, case.id .. ": unexpected mutated content\n" .. tostring(files[source]))
|
|
end
|
|
end
|
|
|
|
local function runMoveCase(case)
|
|
files = {}
|
|
stateValues = {}
|
|
local root = "/tmp/keybind-test/" .. case.rootName
|
|
local source = case.separate and "/tmp/keybind-test/entries-" .. case.rootName or root
|
|
files[root] = case.root
|
|
files[source] = case.content
|
|
local bind = target(
|
|
case.id, source, case.snippet, case.capabilities or { category = true },
|
|
case.startLine, case.endLine
|
|
)
|
|
if case.badFingerprint then bind.fingerprint = "00000000" end
|
|
local categories = { { name = case.category, binds = { bind } } }
|
|
if case.newCategory ~= case.category then
|
|
categories[#categories + 1] = { name = case.newCategory, binds = {} }
|
|
end
|
|
stateValues["keymap.snapshot"] = {
|
|
status = "ready", compositor = case.compositor, source = case.contextSource or root, categories = categories,
|
|
}
|
|
stateValues["keymap.update_request"] = nil
|
|
failVerify = case.failVerify == true
|
|
failReload = false
|
|
reloadAttempts = 0
|
|
writeCount = 0
|
|
noctalia.state.set("keymap.update_request", {
|
|
request_id = case.id .. "-request", target_id = case.id,
|
|
operation = "move", compositor = case.compositor, source = root,
|
|
category = case.newCategory,
|
|
})
|
|
local result = stateValues["keymap.update_result"]
|
|
assert(type(result) == "table", case.id .. ": missing move result")
|
|
if case.error ~= nil then
|
|
assert(result.ok == false and result.error == case.error,
|
|
case.id .. ": expected " .. case.error .. ", got " .. tostring(result.error))
|
|
assert(files[source] == case.content, case.id .. ": rejected move changed source")
|
|
elseif case.failVerify then
|
|
assert(result.ok == false and result.error == "verify_failed", case.id .. ": expected verify rollback")
|
|
assert(files[source] == case.content, case.id .. ": move rollback did not restore source")
|
|
else
|
|
assert(result.ok == true, case.id .. ": " .. tostring(result.error))
|
|
assert(files[source] == case.expected, case.id .. ": unexpected moved content\n" .. tostring(files[source]))
|
|
if case.expectedWrites ~= nil then
|
|
assert(writeCount == case.expectedWrites,
|
|
case.id .. ": expected " .. case.expectedWrites .. " writes, got " .. writeCount)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function runReorderCase(case)
|
|
files = {}
|
|
stateValues = {}
|
|
local root = "/tmp/keybind-test/" .. case.rootName
|
|
local targetSource = case.targetSource or root
|
|
local anchorSource = case.anchorSource or targetSource
|
|
files[root] = case.root or case.content
|
|
files[targetSource] = case.content
|
|
if anchorSource ~= targetSource then files[anchorSource] = case.anchorContent end
|
|
local targetBind = target(
|
|
case.targetId or "reorder-target", targetSource, case.targetSnippet,
|
|
case.targetCapabilities or {}, case.targetStart, case.targetEnd
|
|
)
|
|
local anchorBind = target(
|
|
case.anchorId or "reorder-anchor", anchorSource, case.anchorSnippet,
|
|
case.anchorCapabilities or {}, case.anchorStart, case.anchorEnd
|
|
)
|
|
if case.badTargetFingerprint then targetBind.fingerprint = "00000000" end
|
|
if case.badAnchorFingerprint then anchorBind.fingerprint = "00000000" end
|
|
if case.hiddenTarget then targetBind.hidden = true end
|
|
local targetCategory = case.targetCategory or "Test"
|
|
local anchorCategory = case.anchorCategory or targetCategory
|
|
local categories
|
|
if targetCategory == anchorCategory then
|
|
categories = { { name = targetCategory, binds = { targetBind, anchorBind } } }
|
|
else
|
|
categories = {
|
|
{ name = targetCategory, binds = { targetBind } },
|
|
{ name = anchorCategory, binds = { anchorBind } },
|
|
}
|
|
end
|
|
stateValues["keymap.snapshot"] = {
|
|
status = "ready", compositor = case.compositor or "Hyprland", source = root,
|
|
categories = categories, hidden = {},
|
|
}
|
|
stateValues["keymap.update_request"] = nil
|
|
failVerify = case.failVerify == true
|
|
failReload = case.failReload == true
|
|
reloadAttempts = 0
|
|
writeCount = 0
|
|
noctalia.state.set("keymap.update_request", {
|
|
request_id = (case.targetId or "reorder-target") .. "-request-" .. case.rootName,
|
|
target_id = case.targetId or "reorder-target",
|
|
anchor_id = case.anchorId or "reorder-anchor",
|
|
operation = "reorder", placement = case.placement,
|
|
compositor = case.compositor or "Hyprland", source = root,
|
|
})
|
|
local result = stateValues["keymap.update_result"]
|
|
assert(type(result) == "table", case.rootName .. ": missing reorder result")
|
|
if case.error ~= nil then
|
|
assert(result.ok == false and result.error == case.error,
|
|
case.rootName .. ": expected " .. case.error .. ", got " .. tostring(result.error))
|
|
assert(files[targetSource] == case.content, case.rootName .. ": rejected reorder changed source")
|
|
elseif case.failVerify or case.failReload then
|
|
local expectedError = case.failReload and "reload_failed" or "verify_failed"
|
|
assert(result.ok == false and result.error == expectedError,
|
|
case.rootName .. ": expected " .. expectedError .. ", got " .. tostring(result.error))
|
|
assert(files[targetSource] == case.content, case.rootName .. ": reorder rollback did not restore source")
|
|
if case.failReload then
|
|
assert(reloadAttempts == 2, case.rootName .. ": restored config was not reloaded after rollback")
|
|
end
|
|
else
|
|
assert(result.ok == true, case.rootName .. ": " .. tostring(result.error))
|
|
assert(files[targetSource] == case.expected,
|
|
case.rootName .. ": unexpected reordered content\n" .. tostring(files[targetSource]))
|
|
if case.expectedWrites ~= nil then
|
|
assert(writeCount == case.expectedWrites,
|
|
case.rootName .. ": expected " .. case.expectedWrites .. " writes, got " .. writeCount)
|
|
end
|
|
end
|
|
failReload = false
|
|
end
|
|
|
|
local function runRenameCategoryCase(case)
|
|
files = {}
|
|
stateValues = {}
|
|
failRenameAt = case.failRenameAt
|
|
renameAttempts = 0
|
|
local root = "/tmp/keybind-test/" .. case.rootName
|
|
for path, content in pairs(case.files or {}) do files[path] = content end
|
|
files[root] = case.root or files[root] or "-- root\n"
|
|
local binds = {}
|
|
for index, spec in ipairs(case.binds) do
|
|
local bind = target(
|
|
spec.id or ("rename-bind-" .. tostring(index)), spec.source or root, spec.snippet,
|
|
spec.capabilities or { category = true }, spec.startLine, spec.endLine
|
|
)
|
|
if spec.badFingerprint then bind.fingerprint = "00000000" end
|
|
if spec.hidden then bind.hidden = true end
|
|
binds[#binds + 1] = bind
|
|
end
|
|
local categoryId = case.categoryId or "windows"
|
|
local oldCategory = case.oldCategory or "Windows"
|
|
local categories = { { id = categoryId, name = oldCategory, binds = binds } }
|
|
if case.existingCategory ~= nil then
|
|
categories[#categories + 1] = { id = "existing", name = case.existingCategory, binds = {} }
|
|
end
|
|
stateValues["keymap.snapshot"] = {
|
|
status = "ready", compositor = case.compositor or "Hyprland",
|
|
source = case.contextSource or root, categories = categories, hidden = {},
|
|
}
|
|
stateValues["keymap.update_request"] = nil
|
|
failVerify = case.failVerify == true
|
|
failReload = case.failReload == true
|
|
reloadAttempts = 0
|
|
writeCount = 0
|
|
local originals = {}
|
|
for path, content in pairs(files) do originals[path] = content end
|
|
noctalia.state.set("keymap.update_request", {
|
|
request_id = "rename-request-" .. case.rootName,
|
|
operation = "rename_category", compositor = case.compositor or "Hyprland", source = root,
|
|
category_id = case.requestCategoryId or categoryId,
|
|
old_category = case.requestOldCategory or oldCategory,
|
|
new_category = case.newCategory,
|
|
})
|
|
local result = stateValues["keymap.update_result"]
|
|
assert(type(result) == "table", case.rootName .. ": missing category rename result")
|
|
if case.error ~= nil then
|
|
assert(result.ok == false and result.error == case.error,
|
|
case.rootName .. ": expected " .. case.error .. ", got " .. tostring(result.error))
|
|
for path, content in pairs(originals) do
|
|
assert(files[path] == content, case.rootName .. ": rejected rename changed " .. path)
|
|
end
|
|
elseif case.failVerify or case.failReload then
|
|
local expectedError = case.failReload and "reload_failed" or "verify_failed"
|
|
assert(result.ok == false and result.error == expectedError,
|
|
case.rootName .. ": expected " .. expectedError .. ", got " .. tostring(result.error))
|
|
for path, content in pairs(originals) do
|
|
assert(files[path] == content, case.rootName .. ": rollback did not restore " .. path)
|
|
end
|
|
if case.failReload then
|
|
assert(reloadAttempts == 2, case.rootName .. ": restored config was not reloaded")
|
|
end
|
|
else
|
|
assert(result.ok == true, case.rootName .. ": " .. tostring(result.error))
|
|
assert(result.target_path == root, case.rootName .. ": result did not identify root source")
|
|
for path, expected in pairs(case.expectedFiles or {}) do
|
|
assert(files[path] == expected,
|
|
case.rootName .. ": unexpected renamed content in " .. path .. "\n" .. tostring(files[path]))
|
|
end
|
|
if case.expectedWrites ~= nil then
|
|
assert(writeCount == case.expectedWrites,
|
|
case.rootName .. ": expected " .. case.expectedWrites .. " writes, got " .. writeCount)
|
|
end
|
|
end
|
|
failVerify = false
|
|
failReload = false
|
|
failRenameAt = nil
|
|
end
|
|
|
|
assert(loadfile("writer_service.luau"))()
|
|
|
|
local hyprSnippet = 'hl.bind("SUPER + G", hl.dsp.exec_cmd([[old-command]]), { description = "Old title" })'
|
|
local niriSnippet = ' Mod+G repeat=false hotkey-overlay-title="Old title" { spawn-sh "old-command"; }'
|
|
runCase({
|
|
id = "hypr", compositor = "Hyprland", rootName = "hyprland.lua", separate = true,
|
|
root = 'require("entries-hyprland")\n', content = "-- binds\n" .. hyprSnippet .. "\n",
|
|
snippet = hyprSnippet, category = "Windows",
|
|
capabilities = { combo = true, description = true, command = true, activation = true },
|
|
modifiers = { "SUPER", "SHIFT" }, keys = { "G" }, activation = "release",
|
|
command = "noctalia msg bar-toggle", description = "New title",
|
|
expected = '-- binds\nhl.bind("SUPER + SHIFT + G", hl.dsp.exec_cmd([[noctalia msg bar-toggle]]), { release = true, description = "New title" })\n',
|
|
})
|
|
|
|
runCase({
|
|
id = "hypr-native-conversion", compositor = "Hyprland", rootName = "hypr-native-conversion.lua", separate = true,
|
|
root = 'require("entries-hypr-native-conversion")\n', content = "-- binds\n" .. hyprSnippet .. "\n",
|
|
snippet = hyprSnippet, category = "Windows",
|
|
capabilities = { combo = true, description = true, command = true, activation = true },
|
|
modifiers = { "SUPER" }, keys = { "G" }, activation = "press",
|
|
command = "hl.dsp.window.close()", commandKind = "native",
|
|
libraryEntryId = "hyprland/window.close", description = "Close window",
|
|
error = "native_update_unsupported",
|
|
})
|
|
|
|
runCase({
|
|
id = "hypr-category", compositor = "Hyprland", rootName = "hypr-category.lua", separate = true,
|
|
root = 'require("entries-hypr-category")\n', content = "-- binds\n" .. hyprSnippet .. "\n",
|
|
snippet = hyprSnippet, category = "Windows", newCategory = "Media",
|
|
capabilities = { combo = true, category = true, description = true, command = true, activation = true },
|
|
modifiers = { "SUPER" }, keys = { "G" }, activation = "press",
|
|
command = "old-command", description = "Old title",
|
|
expected = '-- binds\n-- Keymap bind-category: Media\n'
|
|
.. hyprSnippet .. '\n',
|
|
})
|
|
|
|
local markedHyprSnippet = "-- Keymap bind-category: Media\n" .. hyprSnippet
|
|
runCase({
|
|
id = "hypr-category-again", compositor = "Hyprland", rootName = "hypr-category-again.lua", separate = true,
|
|
root = 'require("entries-hypr-category-again")\n', content = "-- binds\n" .. markedHyprSnippet .. "\n",
|
|
snippet = markedHyprSnippet, startLine = 2, endLine = 3,
|
|
category = "Media", newCategory = "System",
|
|
capabilities = { combo = true, category = true, description = true, command = true, activation = true },
|
|
modifiers = { "SUPER" }, keys = { "G" }, activation = "press",
|
|
command = "old-command", description = "Old title",
|
|
expected = '-- binds\n-- Keymap bind-category: System\n'
|
|
.. hyprSnippet .. '\n',
|
|
})
|
|
|
|
local hyprNativeSnippet = 'hl.bind("SUPER + G", hl.dsp.group.toggle(), { description = "Group Windows" })'
|
|
runCase({
|
|
id = "hypr-native", compositor = "Hyprland", rootName = "native.lua", separate = true,
|
|
root = 'require("entries-native")\n', content = "-- binds\n" .. hyprNativeSnippet .. "\n",
|
|
snippet = hyprNativeSnippet, category = "Windows",
|
|
capabilities = { combo = true, description = true, command = false, activation = true },
|
|
modifiers = { "SUPER", "SHIFT" }, keys = { "G" }, activation = "press",
|
|
command = "", description = "Toggle window group",
|
|
expected = '-- binds\nhl.bind("SUPER + SHIFT + G", hl.dsp.group.toggle(), { description = "Toggle window group" })\n',
|
|
})
|
|
|
|
runCase({
|
|
id = "niri-category", compositor = "Niri", rootName = "niri-category.kdl",
|
|
root = "binds {\n" .. niriSnippet .. "\n}\n",
|
|
content = "binds {\n" .. niriSnippet .. "\n}\n",
|
|
snippet = niriSnippet, category = "Windows", newCategory = "Media",
|
|
capabilities = { combo = true, category = true, description = true, command = true, activation = false },
|
|
modifiers = { "SUPER" }, keys = { "G" }, activation = "press",
|
|
command = "old-command", description = "Old title",
|
|
expected = 'binds {\n // Keymap bind-category: Media\n'
|
|
.. niriSnippet .. '\n}\n',
|
|
})
|
|
|
|
runCase({
|
|
id = "niri", compositor = "Niri", rootName = "config.kdl",
|
|
root = "binds {\n" .. niriSnippet .. "\n}\n", content = "binds {\n" .. niriSnippet .. "\n}\n",
|
|
snippet = niriSnippet, category = "Windows",
|
|
capabilities = { combo = true, description = true, command = true, activation = false },
|
|
modifiers = { "SUPER", "SHIFT" }, keys = { "G" }, activation = "press",
|
|
command = "new-command --flag", description = "New title",
|
|
expected = 'binds {\n Mod+Shift+G repeat=false hotkey-overlay-title="New title" { spawn-sh "new-command --flag"; }\n}\n',
|
|
})
|
|
|
|
local mangoSnippet = 'bind=SUPER,G,spawn_shell,old-command #"Old title"'
|
|
runCase({
|
|
id = "mango-category", compositor = "MangoWC", rootName = "mango-category.conf",
|
|
root = "# binds\n" .. mangoSnippet .. "\n", content = "# binds\n" .. mangoSnippet .. "\n",
|
|
snippet = mangoSnippet, category = "Windows", newCategory = "Media",
|
|
capabilities = { combo = true, category = true, description = true, command = true, activation = true },
|
|
modifiers = { "SUPER" }, keys = { "G" }, activation = "press",
|
|
command = "old-command", description = "Old title",
|
|
expected = '# binds\n# Keymap bind-category: Media\n' .. mangoSnippet .. '\n',
|
|
})
|
|
|
|
runCase({
|
|
id = "mango", compositor = "MangoWC", rootName = "config.conf",
|
|
root = "# binds\n" .. mangoSnippet .. "\n", content = "# binds\n" .. mangoSnippet .. "\n",
|
|
snippet = mangoSnippet, category = "Windows",
|
|
capabilities = { combo = true, description = true, command = true, activation = true },
|
|
modifiers = { "SUPER", "SHIFT" }, keys = { "G" }, activation = "release",
|
|
command = "new-command --flag", description = "New title",
|
|
expected = '# binds\nbindr=SUPER+SHIFT,G,spawn_shell,new-command --flag #"New title"\n',
|
|
})
|
|
|
|
local hyprMoveSnippet = " hl.bind( 'SUPER + G' , hl.dsp.group.toggle(), {description='Keep spacing'} ) -- trailing"
|
|
runMoveCase({
|
|
id = "hypr-move", compositor = "Hyprland", rootName = "hypr-move.lua", separate = true,
|
|
root = 'require("entries-hypr-move")\n', content = "-- binds\n" .. hyprMoveSnippet .. "\n",
|
|
snippet = hyprMoveSnippet, category = "Windows", newCategory = "Media",
|
|
expected = "-- binds\n -- Keymap bind-category: Media\n" .. hyprMoveSnippet .. "\n",
|
|
})
|
|
|
|
local niriMoveBind = ' Mod+G repeat=false { spawn-sh "keep exact --flag"; }'
|
|
local niriMoveSnippet = " // Keymap bind-category: Windows\n" .. niriMoveBind
|
|
runMoveCase({
|
|
id = "niri-move", compositor = "Niri", rootName = "niri-move.kdl",
|
|
root = "binds {\n" .. niriMoveSnippet .. "\n}\n",
|
|
content = "binds {\n" .. niriMoveSnippet .. "\n}\n",
|
|
snippet = niriMoveSnippet, startLine = 2, endLine = 3,
|
|
category = "Windows", newCategory = "Media",
|
|
expected = "binds {\n // Keymap bind-category: Media\n" .. niriMoveBind .. "\n}\n",
|
|
})
|
|
|
|
local mangoMoveSnippet = ' bind=SUPER,G,spawn_shell,printf "keep, exact" #"Title"'
|
|
runMoveCase({
|
|
id = "mango-move", compositor = "MangoWC", rootName = "mango-move.conf",
|
|
root = "# binds\n" .. mangoMoveSnippet .. "\n",
|
|
content = "# binds\n" .. mangoMoveSnippet .. "\n",
|
|
snippet = mangoMoveSnippet, category = "Windows", newCategory = "Media",
|
|
expected = "# binds\n # Keymap bind-category: Media\n" .. mangoMoveSnippet .. "\n",
|
|
})
|
|
|
|
runMoveCase({
|
|
id = "move-same-category", compositor = "Hyprland", rootName = "move-same.lua", separate = true,
|
|
root = 'require("entries-move-same")\n', content = "-- binds\n" .. hyprMoveSnippet .. "\n",
|
|
snippet = hyprMoveSnippet, category = "Windows", newCategory = "Windows",
|
|
expected = "-- binds\n" .. hyprMoveSnippet .. "\n",
|
|
expectedWrites = 0,
|
|
})
|
|
|
|
runMoveCase({
|
|
id = "move-from-legacy-category", compositor = "Hyprland", rootName = "move-legacy.lua", separate = true,
|
|
root = 'require("entries-move-legacy")\n', content = "-- binds\n" .. hyprMoveSnippet .. "\n",
|
|
snippet = hyprMoveSnippet, category = 'Legacy "quoted" category', newCategory = "Media",
|
|
expected = "-- binds\n -- Keymap bind-category: Media\n" .. hyprMoveSnippet .. "\n",
|
|
})
|
|
|
|
runMoveCase({
|
|
id = "move-no-capability", compositor = "Niri", rootName = "move-no-capability.kdl",
|
|
root = "binds {\n" .. niriMoveBind .. "\n}\n",
|
|
content = "binds {\n" .. niriMoveBind .. "\n}\n",
|
|
snippet = niriMoveBind, category = "Windows", newCategory = "Media",
|
|
capabilities = { category = false }, error = "target_not_editable",
|
|
})
|
|
|
|
runMoveCase({
|
|
id = "move-invalid-category", compositor = "MangoWC", rootName = "move-invalid-category.conf",
|
|
root = "# binds\n" .. mangoSnippet .. "\n", content = "# binds\n" .. mangoSnippet .. "\n",
|
|
snippet = mangoSnippet, category = "Windows", newCategory = "bad\ncategory",
|
|
error = "invalid_category",
|
|
})
|
|
|
|
runMoveCase({
|
|
id = "move-stale-context", compositor = "Hyprland", rootName = "move-stale-context.lua", separate = true,
|
|
root = 'require("entries-move-stale-context")\n', content = "-- binds\n" .. hyprSnippet .. "\n",
|
|
snippet = hyprSnippet, category = "Windows", newCategory = "Media",
|
|
contextSource = "/tmp/keybind-test/other.lua", error = "stale_context",
|
|
})
|
|
|
|
runMoveCase({
|
|
id = "move-stale-provenance", compositor = "Niri", rootName = "move-stale-provenance.kdl",
|
|
root = "binds {\n" .. niriMoveBind .. "\n}\n",
|
|
content = "binds {\n" .. niriMoveBind .. "\n}\n",
|
|
snippet = niriMoveBind, category = "Windows", newCategory = "Media",
|
|
badFingerprint = true, error = "target_changed",
|
|
})
|
|
|
|
runMoveCase({
|
|
id = "move-rollback", compositor = "Hyprland", rootName = "move-rollback.lua", separate = true,
|
|
root = 'require("entries-move-rollback")\n', content = "-- binds\n" .. hyprSnippet .. "\n",
|
|
snippet = hyprSnippet, category = "Windows", newCategory = "Media", failVerify = true,
|
|
})
|
|
|
|
local renameHyprA = 'hl.bind("SUPER + A", action_a, { description = "A" })'
|
|
local renameHyprB = 'hl.bind("SUPER + B", action_b, { description = "B" })'
|
|
local renameHyprPath = "/tmp/keybind-test/rename-hypr.lua"
|
|
local renameHyprContent = "-- 1. Windows\n" .. renameHyprA .. "\n" .. renameHyprB .. "\n"
|
|
runRenameCategoryCase({
|
|
rootName = "rename-hypr.lua", root = renameHyprContent, newCategory = "Applications",
|
|
binds = {
|
|
{ id = "rename-hypr-a", snippet = renameHyprA, startLine = 2, endLine = 2 },
|
|
{ id = "rename-hypr-b", snippet = renameHyprB, startLine = 3, endLine = 3 },
|
|
},
|
|
expectedFiles = { [renameHyprPath] = "-- 1. Windows\n"
|
|
.. "-- Keymap bind-category: Applications\n" .. renameHyprA .. "\n"
|
|
.. "-- Keymap bind-category: Applications\n" .. renameHyprB .. "\n" },
|
|
})
|
|
|
|
local renameNiriA = table.concat({
|
|
" Mod+A {", ' spawn "a";', " }",
|
|
}, "\n")
|
|
local renameNiriB = ' Mod+B { spawn "b"; }'
|
|
local renameNiriPath = "/tmp/keybind-test/rename-niri.kdl"
|
|
local renameNiriContent = "binds {\n // \"Windows\"\n" .. renameNiriA .. "\n" .. renameNiriB .. "\n}\n"
|
|
runRenameCategoryCase({
|
|
rootName = "rename-niri.kdl", compositor = "Niri", root = renameNiriContent,
|
|
newCategory = "Applications",
|
|
binds = {
|
|
{ id = "rename-niri-a", snippet = renameNiriA, startLine = 3, endLine = 5 },
|
|
{ id = "rename-niri-b", snippet = renameNiriB, startLine = 6, endLine = 6 },
|
|
},
|
|
expectedFiles = { [renameNiriPath] = "binds {\n // \"Windows\"\n"
|
|
.. " // Keymap bind-category: Applications\n" .. renameNiriA .. "\n"
|
|
.. " // Keymap bind-category: Applications\n" .. renameNiriB .. "\n}\n" },
|
|
})
|
|
|
|
local renameMangoA = 'bind=SUPER,A,spawn_shell,a #"A"'
|
|
local renameMangoB = 'bind=SUPER,B,spawn_shell,b #"B"'
|
|
local renameMangoPath = "/tmp/keybind-test/rename-mango.conf"
|
|
local renameMangoContent = "# Keymap category: Windows\n"
|
|
.. renameMangoA .. "\n" .. renameMangoB .. "\n"
|
|
runRenameCategoryCase({
|
|
rootName = "rename-mango.conf", compositor = "MangoWC", root = renameMangoContent,
|
|
newCategory = "Applications",
|
|
binds = {
|
|
{ id = "rename-mango-a", snippet = renameMangoA, startLine = 2, endLine = 2 },
|
|
{ id = "rename-mango-b", snippet = renameMangoB, startLine = 3, endLine = 3 },
|
|
},
|
|
expectedFiles = { [renameMangoPath] = "# Keymap category: Windows\n"
|
|
.. "# Keymap bind-category: Applications\n" .. renameMangoA .. "\n"
|
|
.. "# Keymap bind-category: Applications\n" .. renameMangoB .. "\n" },
|
|
})
|
|
|
|
local renameMultiRoot = "/tmp/keybind-test/rename-multi.lua"
|
|
local renameMultiOne = "/tmp/keybind-test/rename-multi-one.lua"
|
|
local renameMultiTwo = "/tmp/keybind-test/rename-multi-two.lua"
|
|
local renameMultiRootContent = 'require("rename-multi-one")\nrequire("rename-multi-two")\n'
|
|
local renameMultiOneContent = "-- 1. Windows\n" .. renameHyprA .. "\n"
|
|
local renameMultiTwoContent = "-- 1. Windows\n" .. renameHyprB .. "\n"
|
|
local renameMultiFiles = {
|
|
[renameMultiOne] = renameMultiOneContent,
|
|
[renameMultiTwo] = renameMultiTwoContent,
|
|
}
|
|
local renameMultiBinds = {
|
|
{ id = "rename-multi-a", source = renameMultiOne, snippet = renameHyprA, startLine = 2, endLine = 2 },
|
|
{ id = "rename-multi-b", source = renameMultiTwo, snippet = renameHyprB, startLine = 2, endLine = 2 },
|
|
}
|
|
local renameMultiExpected = {
|
|
[renameMultiRoot] = renameMultiRootContent,
|
|
[renameMultiOne] = "-- 1. Windows\n-- Keymap bind-category: Media\n" .. renameHyprA .. "\n",
|
|
[renameMultiTwo] = "-- 1. Windows\n-- Keymap bind-category: Media\n" .. renameHyprB .. "\n",
|
|
}
|
|
runRenameCategoryCase({
|
|
rootName = "rename-multi.lua", root = renameMultiRootContent, files = renameMultiFiles,
|
|
newCategory = "Media", binds = renameMultiBinds,
|
|
expectedFiles = renameMultiExpected, expectedWrites = 2,
|
|
})
|
|
|
|
runRenameCategoryCase({
|
|
rootName = "rename-write-rollback.lua", root = renameMultiRootContent, files = renameMultiFiles,
|
|
newCategory = "Media", binds = renameMultiBinds, failRenameAt = 2,
|
|
error = "source_write_failed",
|
|
})
|
|
|
|
runRenameCategoryCase({
|
|
rootName = "rename-no-op.lua", root = renameHyprContent,
|
|
newCategory = "Windows",
|
|
binds = {
|
|
{ id = "rename-no-op-a", snippet = renameHyprA, startLine = 2, endLine = 2 },
|
|
{ id = "rename-no-op-b", snippet = renameHyprB, startLine = 3, endLine = 3 },
|
|
},
|
|
expectedFiles = { ["/tmp/keybind-test/rename-no-op.lua"] = renameHyprContent }, expectedWrites = 0,
|
|
})
|
|
|
|
runRenameCategoryCase({
|
|
rootName = "rename-stale-fingerprint.lua", root = renameHyprContent, newCategory = "Media",
|
|
binds = {
|
|
{ id = "rename-stale-a", snippet = renameHyprA, startLine = 2, endLine = 2 },
|
|
{ id = "rename-stale-b", snippet = renameHyprB, startLine = 3, endLine = 3, badFingerprint = true },
|
|
},
|
|
error = "target_changed",
|
|
})
|
|
|
|
runRenameCategoryCase({
|
|
rootName = "rename-stale-context.lua", root = renameHyprContent, newCategory = "Media",
|
|
contextSource = "/tmp/keybind-test/different-root.lua",
|
|
binds = {
|
|
{ id = "rename-context-a", snippet = renameHyprA, startLine = 2, endLine = 2 },
|
|
},
|
|
error = "stale_context",
|
|
})
|
|
|
|
runRenameCategoryCase({
|
|
rootName = "rename-stale-category.lua", root = renameHyprContent, newCategory = "Media",
|
|
requestOldCategory = "Old Windows",
|
|
binds = {
|
|
{ id = "rename-category-a", snippet = renameHyprA, startLine = 2, endLine = 2 },
|
|
},
|
|
error = "stale_category",
|
|
})
|
|
|
|
runRenameCategoryCase({
|
|
rootName = "rename-existing-category.lua", root = renameHyprContent,
|
|
newCategory = "Media", existingCategory = "Media",
|
|
binds = {
|
|
{ id = "rename-existing-a", snippet = renameHyprA, startLine = 2, endLine = 2 },
|
|
},
|
|
error = "category_exists",
|
|
})
|
|
|
|
runRenameCategoryCase({
|
|
rootName = "rename-range.lua", root = renameHyprContent, newCategory = "Media",
|
|
binds = {
|
|
{ id = "range:a:b", snippet = renameHyprA, startLine = 2, endLine = 2 },
|
|
},
|
|
error = "category_not_editable",
|
|
})
|
|
|
|
runRenameCategoryCase({
|
|
rootName = "rename-dynamic.lua", root = renameHyprContent, newCategory = "Media",
|
|
binds = {
|
|
{ id = "rename-dynamic-a", snippet = renameHyprA, startLine = 2, endLine = 2,
|
|
capabilities = { category = false } },
|
|
},
|
|
error = "category_not_editable",
|
|
})
|
|
|
|
runRenameCategoryCase({
|
|
rootName = "rename-overlap.lua", root = renameHyprContent, newCategory = "Media",
|
|
binds = {
|
|
{ id = "rename-overlap-a", snippet = renameHyprA, startLine = 2, endLine = 2 },
|
|
{ id = "rename-overlap-b", snippet = renameHyprA, startLine = 2, endLine = 2 },
|
|
},
|
|
error = "category_ranges_overlap",
|
|
})
|
|
|
|
runRenameCategoryCase({
|
|
rootName = "rename-invalid-name.lua", root = renameHyprContent, newCategory = "bad\nname",
|
|
binds = {
|
|
{ id = "rename-invalid-a", snippet = renameHyprA, startLine = 2, endLine = 2 },
|
|
},
|
|
error = "invalid_category",
|
|
})
|
|
|
|
runRenameCategoryCase({
|
|
rootName = "rename-validator-rollback.lua", root = renameMultiRootContent, files = renameMultiFiles,
|
|
newCategory = "Media", binds = renameMultiBinds, failVerify = true,
|
|
})
|
|
|
|
runRenameCategoryCase({
|
|
rootName = "rename-reload-rollback.lua", root = renameMultiRootContent, files = renameMultiFiles,
|
|
newCategory = "Media", binds = renameMultiBinds, failReload = true,
|
|
})
|
|
|
|
local reorderA = 'hl.bind("SUPER + A", action_a, { description = "A" })'
|
|
local reorderB = 'hl.bind("SUPER + B", action_b, { description = "B" })'
|
|
local reorderC = 'hl.bind("SUPER + C", action_c, { description = "C" })'
|
|
local reorderContent = "-- header\n" .. reorderA .. "\n" .. reorderB .. "\n" .. reorderC .. "\n-- footer\n"
|
|
|
|
runReorderCase({
|
|
rootName = "reorder-before-up.lua", content = reorderContent,
|
|
targetSnippet = reorderC, targetStart = 4, targetEnd = 4,
|
|
anchorSnippet = reorderA, anchorStart = 2, anchorEnd = 2, placement = "before",
|
|
expected = "-- header\n" .. reorderC .. "\n" .. reorderA .. "\n" .. reorderB .. "\n-- footer\n",
|
|
})
|
|
|
|
runReorderCase({
|
|
rootName = "reorder-cross-category.lua", content = reorderContent,
|
|
targetSnippet = reorderA, targetStart = 2, targetEnd = 2,
|
|
anchorSnippet = reorderB, anchorStart = 3, anchorEnd = 3, placement = "after",
|
|
targetCategory = "Applications", anchorCategory = "Media",
|
|
targetCapabilities = { category = true },
|
|
expected = "-- header\n" .. reorderB
|
|
.. "\n-- Keymap bind-category: Media\n" .. reorderA
|
|
.. "\n" .. reorderC .. "\n-- footer\n",
|
|
})
|
|
|
|
runReorderCase({
|
|
rootName = "reorder-cross-category-no-capability.lua", content = reorderContent,
|
|
targetSnippet = reorderA, targetStart = 2, targetEnd = 2,
|
|
anchorSnippet = reorderB, anchorStart = 3, anchorEnd = 3, placement = "after",
|
|
targetCategory = "Applications", anchorCategory = "Media",
|
|
error = "target_not_editable",
|
|
})
|
|
|
|
runReorderCase({
|
|
rootName = "reorder-after-down.lua", content = reorderContent,
|
|
targetSnippet = reorderA, targetStart = 2, targetEnd = 2,
|
|
anchorSnippet = reorderC, anchorStart = 4, anchorEnd = 4, placement = "after",
|
|
expected = "-- header\n" .. reorderB .. "\n" .. reorderC .. "\n" .. reorderA .. "\n-- footer\n",
|
|
})
|
|
|
|
runReorderCase({
|
|
rootName = "reorder-after-adjacent.lua", content = reorderContent,
|
|
targetSnippet = reorderA, targetStart = 2, targetEnd = 2,
|
|
anchorSnippet = reorderB, anchorStart = 3, anchorEnd = 3, placement = "after",
|
|
expected = "-- header\n" .. reorderB .. "\n" .. reorderA .. "\n" .. reorderC .. "\n-- footer\n",
|
|
})
|
|
|
|
runReorderCase({
|
|
rootName = "reorder-before-adjacent.lua", content = reorderContent,
|
|
targetSnippet = reorderC, targetStart = 4, targetEnd = 4,
|
|
anchorSnippet = reorderB, anchorStart = 3, anchorEnd = 3, placement = "before",
|
|
expected = "-- header\n" .. reorderA .. "\n" .. reorderC .. "\n" .. reorderB .. "\n-- footer\n",
|
|
})
|
|
|
|
runReorderCase({
|
|
rootName = "reorder-no-op.lua", content = reorderContent,
|
|
targetSnippet = reorderA, targetStart = 2, targetEnd = 2,
|
|
anchorSnippet = reorderB, anchorStart = 3, anchorEnd = 3, placement = "before",
|
|
expected = reorderContent, expectedWrites = 0,
|
|
})
|
|
|
|
local reorderNiriA = ' Mod+A { spawn "a"; }\r'
|
|
local reorderNiriB = ' Mod+B { spawn "b"; }\r'
|
|
runReorderCase({
|
|
rootName = "reorder-crlf.kdl", compositor = "Niri",
|
|
content = "binds {\r\n" .. reorderNiriA .. "\n" .. reorderNiriB .. "\n}\r\n",
|
|
targetSnippet = reorderNiriB, targetStart = 3, targetEnd = 3,
|
|
anchorSnippet = reorderNiriA, anchorStart = 2, anchorEnd = 2, placement = "before",
|
|
expected = "binds {\r\n" .. reorderNiriB .. "\n" .. reorderNiriA .. "\n}\r\n",
|
|
})
|
|
|
|
runReorderCase({
|
|
rootName = "reorder-different-source.lua", content = "-- target\n" .. reorderA .. "\n",
|
|
targetSource = "/tmp/keybind-test/reorder-target.lua",
|
|
anchorSource = "/tmp/keybind-test/reorder-anchor.lua", anchorContent = "-- anchor\n" .. reorderB .. "\n",
|
|
targetSnippet = reorderA, targetStart = 2, targetEnd = 2,
|
|
anchorSnippet = reorderB, anchorStart = 2, anchorEnd = 2, placement = "before",
|
|
error = "different_source",
|
|
})
|
|
|
|
runReorderCase({
|
|
rootName = "reorder-stale-target.lua", content = reorderContent,
|
|
targetSnippet = reorderC, targetStart = 4, targetEnd = 4, badTargetFingerprint = true,
|
|
anchorSnippet = reorderA, anchorStart = 2, anchorEnd = 2, placement = "before",
|
|
error = "target_changed",
|
|
})
|
|
|
|
runReorderCase({
|
|
rootName = "reorder-stale-anchor.lua", content = reorderContent,
|
|
targetSnippet = reorderC, targetStart = 4, targetEnd = 4,
|
|
anchorSnippet = reorderA, anchorStart = 2, anchorEnd = 2, badAnchorFingerprint = true,
|
|
placement = "before", error = "anchor_changed",
|
|
})
|
|
|
|
runReorderCase({
|
|
rootName = "reorder-overlap.lua", content = reorderContent,
|
|
targetSnippet = reorderA, targetStart = 2, targetEnd = 2,
|
|
anchorSnippet = reorderA, anchorStart = 2, anchorEnd = 2, placement = "before",
|
|
error = "target_anchor_overlap",
|
|
})
|
|
|
|
runReorderCase({
|
|
rootName = "reorder-range-id.lua", content = reorderContent, targetId = "range:2-2",
|
|
targetSnippet = reorderA, targetStart = 2, targetEnd = 2,
|
|
anchorSnippet = reorderB, anchorStart = 3, anchorEnd = 3, placement = "before",
|
|
error = "target_not_editable",
|
|
})
|
|
|
|
runReorderCase({
|
|
rootName = "reorder-hidden.lua", content = reorderContent, hiddenTarget = true,
|
|
targetSnippet = reorderA, targetStart = 2, targetEnd = 2,
|
|
anchorSnippet = reorderB, anchorStart = 3, anchorEnd = 3, placement = "before",
|
|
error = "target_not_editable",
|
|
})
|
|
|
|
runReorderCase({
|
|
rootName = "reorder-validator-rollback.lua", content = reorderContent,
|
|
targetSnippet = reorderC, targetStart = 4, targetEnd = 4,
|
|
anchorSnippet = reorderA, anchorStart = 2, anchorEnd = 2, placement = "before",
|
|
failVerify = true,
|
|
})
|
|
|
|
runReorderCase({
|
|
rootName = "reorder-reload-rollback.lua", content = reorderContent,
|
|
targetSnippet = reorderC, targetStart = 4, targetEnd = 4,
|
|
anchorSnippet = reorderA, anchorStart = 2, anchorEnd = 2, placement = "before",
|
|
failReload = true,
|
|
})
|
|
|
|
runMutationCase({
|
|
id = "hypr-hide", operation = "hide", compositor = "Hyprland", rootName = "hide.lua", separate = true,
|
|
root = 'require("entries-hide")\n', content = "-- binds\n" .. hyprSnippet .. "\n",
|
|
snippet = hyprSnippet,
|
|
expected = "-- binds\n" .. hiddenSnippet("Hyprland", hyprSnippet) .. "\n",
|
|
})
|
|
|
|
local niriMultilineSnippet = table.concat({
|
|
' Mod+G repeat=false hotkey-overlay-title="Old title" {',
|
|
' spawn-sh "old-command --with-flag";',
|
|
' }',
|
|
}, "\n")
|
|
runMutationCase({
|
|
id = "niri-hide-multiline", operation = "hide", compositor = "Niri", rootName = "hide-multiline.kdl",
|
|
root = "binds {\n" .. niriMultilineSnippet .. "\n}\n",
|
|
content = "binds {\n" .. niriMultilineSnippet .. "\n}\n",
|
|
snippet = niriMultilineSnippet, startLine = 2, endLine = 4,
|
|
expected = "binds {\n" .. hiddenSnippet("Niri", niriMultilineSnippet) .. "\n}\n",
|
|
})
|
|
|
|
runMutationCase({
|
|
id = "niri-delete", operation = "delete", compositor = "Niri", rootName = "delete.kdl",
|
|
root = "binds {\n" .. niriSnippet .. "\n}\n", content = "binds {\n" .. niriSnippet .. "\n}\n",
|
|
snippet = niriSnippet,
|
|
expected = "binds {\n}\n",
|
|
})
|
|
|
|
runMutationCase({
|
|
id = "mango-hide", operation = "hide", compositor = "MangoWC", rootName = "hide.conf",
|
|
root = "# binds\n" .. mangoSnippet .. "\n", content = "# binds\n" .. mangoSnippet .. "\n",
|
|
snippet = mangoSnippet,
|
|
expected = "# binds\n" .. hiddenSnippet("MangoWC", mangoSnippet) .. "\n",
|
|
})
|
|
|
|
local hyprHidden = hiddenSnippet("Hyprland", hyprSnippet)
|
|
runMutationCase({
|
|
id = "hypr-restore", operation = "restore", compositor = "Hyprland", rootName = "restore.lua", separate = true,
|
|
root = 'require("entries-restore")\n', content = "-- binds\n" .. hyprHidden .. "\n",
|
|
snippet = hyprHidden, startLine = 2, endLine = 1 + lineCount(hyprHidden), hidden = true,
|
|
originalFingerprint = fingerprint(hyprSnippet),
|
|
expected = "-- binds\n" .. hyprSnippet .. "\n",
|
|
})
|
|
|
|
local niriHidden = hiddenSnippet("Niri", niriMultilineSnippet)
|
|
runMutationCase({
|
|
id = "niri-restore-multiline", operation = "restore", compositor = "Niri", rootName = "restore.kdl",
|
|
root = "binds {\n" .. niriHidden .. "\n}\n", content = "binds {\n" .. niriHidden .. "\n}\n",
|
|
snippet = niriHidden, startLine = 2, endLine = 1 + lineCount(niriHidden), hidden = true,
|
|
originalFingerprint = fingerprint(niriMultilineSnippet),
|
|
expected = "binds {\n" .. niriMultilineSnippet .. "\n}\n",
|
|
})
|
|
|
|
local niriCrlfSnippet = ' Mod+H repeat=false {\r\n spawn-sh "keep-crlf";\r\n }\r'
|
|
local niriCrlfHidden = hiddenSnippet("Niri", niriCrlfSnippet)
|
|
runMutationCase({
|
|
id = "niri-restore-crlf", operation = "restore", compositor = "Niri", rootName = "restore-crlf.kdl",
|
|
root = "binds {\r\n" .. niriCrlfHidden .. "\n}\r\n",
|
|
content = "binds {\r\n" .. niriCrlfHidden .. "\n}\r\n",
|
|
snippet = niriCrlfHidden, startLine = 2, endLine = 1 + lineCount(niriCrlfHidden), hidden = true,
|
|
originalFingerprint = fingerprint(niriCrlfSnippet),
|
|
expected = "binds {\r\n" .. niriCrlfSnippet .. "\n}\r\n",
|
|
})
|
|
|
|
local mangoHidden = hiddenSnippet("MangoWC", mangoSnippet)
|
|
runMutationCase({
|
|
id = "mango-restore", operation = "restore", compositor = "MangoWC", rootName = "restore.conf",
|
|
root = "# binds\n" .. mangoHidden .. "\n", content = "# binds\n" .. mangoHidden .. "\n",
|
|
snippet = mangoHidden, startLine = 2, endLine = 1 + lineCount(mangoHidden), hidden = true,
|
|
originalFingerprint = fingerprint(mangoSnippet),
|
|
expected = "# binds\n" .. mangoSnippet .. "\n",
|
|
})
|
|
|
|
runMutationCase({
|
|
id = "hidden-delete", operation = "delete", compositor = "MangoWC", rootName = "delete-hidden.conf",
|
|
root = "# binds\n" .. mangoHidden .. "\n", content = "# binds\n" .. mangoHidden .. "\n",
|
|
snippet = mangoHidden, startLine = 2, endLine = 1 + lineCount(mangoHidden), hidden = true,
|
|
expected = "# binds\n",
|
|
})
|
|
|
|
local malformedHidden = hyprHidden:gsub(" end ([0-9a-f]+)$", " end deadbeef")
|
|
runMutationCase({
|
|
id = "restore-malformed", operation = "restore", compositor = "Hyprland", rootName = "malformed.lua",
|
|
root = "-- binds\n" .. malformedHidden .. "\n", content = "-- binds\n" .. malformedHidden .. "\n",
|
|
snippet = malformedHidden, startLine = 2, endLine = 1 + lineCount(malformedHidden), hidden = true,
|
|
error = "hidden_block_invalid",
|
|
})
|
|
|
|
runMutationCase({
|
|
id = "delete-malformed", operation = "delete", compositor = "Hyprland", rootName = "delete-malformed.lua",
|
|
root = "-- binds\n" .. malformedHidden .. "\n", content = "-- binds\n" .. malformedHidden .. "\n",
|
|
snippet = malformedHidden, startLine = 2, endLine = 1 + lineCount(malformedHidden), hidden = true,
|
|
error = "hidden_block_invalid",
|
|
})
|
|
|
|
runMutationCase({
|
|
id = "restore-stale", operation = "restore", compositor = "Niri", rootName = "stale.kdl",
|
|
root = "binds {\n" .. niriHidden .. "\n}\n", content = "binds {\n" .. niriHidden .. "\n}\n",
|
|
snippet = niriHidden, startLine = 2, endLine = 1 + lineCount(niriHidden), hidden = true,
|
|
badFingerprint = true, error = "target_changed",
|
|
})
|
|
|
|
local changedNiriHidden = niriHidden:gsub(" data ([0-9a-f])", function(first)
|
|
return " data " .. (first == "0" and "1" or "0")
|
|
end, 1)
|
|
runMutationCase({
|
|
id = "restore-disk-changed", operation = "restore", compositor = "Niri", rootName = "disk-changed.kdl",
|
|
root = "binds {\n" .. changedNiriHidden .. "\n}\n",
|
|
content = "binds {\n" .. changedNiriHidden .. "\n}\n",
|
|
snippet = changedNiriHidden, targetSnippet = niriHidden,
|
|
startLine = 2, endLine = 1 + lineCount(niriHidden), hidden = true,
|
|
error = "target_changed",
|
|
})
|
|
|
|
runMutationCase({
|
|
id = "restore-rollback", operation = "restore", compositor = "Hyprland", rootName = "restore-rollback.lua",
|
|
root = "-- binds\n" .. hyprHidden .. "\n", content = "-- binds\n" .. hyprHidden .. "\n",
|
|
snippet = hyprHidden, startLine = 2, endLine = 1 + lineCount(hyprHidden), hidden = true,
|
|
failVerify = true,
|
|
})
|
|
|
|
runMutationCase({
|
|
id = "restore-reload-rollback", operation = "restore", compositor = "Hyprland",
|
|
rootName = "restore-reload-rollback.lua",
|
|
root = "-- binds\n" .. hyprHidden .. "\n", content = "-- binds\n" .. hyprHidden .. "\n",
|
|
snippet = hyprHidden, startLine = 2, endLine = 1 + lineCount(hyprHidden), hidden = true,
|
|
failReload = true,
|
|
})
|
|
|
|
runCase({
|
|
id = "rollback", compositor = "Hyprland", rootName = "rollback.lua", separate = true,
|
|
root = 'require("entries-rollback")\n', content = "-- binds\n" .. hyprSnippet .. "\n",
|
|
snippet = hyprSnippet, category = "Windows", failVerify = true,
|
|
capabilities = { combo = true, description = true, command = true, activation = true },
|
|
modifiers = { "SUPER" }, keys = { "H" }, activation = "press",
|
|
command = "will-not-stick", description = "Rollback title",
|
|
})
|
|
|
|
print("writer update tests: ok")
|