dotfiles

My PC Dotfiles & Website

files/noctalia/keyhelp/lib/model.luau (4541B)


  1local M = {}
  2local PAGE_SIZE = 8
  3
  4local function text(value)
  5  return type(value) == "string" and value:find("%S") ~= nil
  6end
  7
  8local function array(value)
  9  if type(value) ~= "table" then
 10    return false
 11  end
 12  local count = 0
 13  for key in pairs(value) do
 14    if type(key) ~= "number" or key < 1 or key % 1 ~= 0 then
 15      return false
 16    end
 17    count = count + 1
 18  end
 19  return count == #value
 20end
 21
 22-- Validate content and assign runtime IDs
 23function M.prepare(data)
 24  if type(data) ~= "table" or data.version ~= 1 or not array(data.pages) or #data.pages == 0 then
 25    return nil, "Expected version 1 and a nonempty pages array"
 26  end
 27  local aliases, desktops = {}, {}
 28  for pageIndex, page in ipairs(data.pages) do
 29    if
 30      type(page) ~= "table"
 31      or not text(page.name)
 32      or not array(page.groups)
 33      or #page.groups == 0
 34      or not array(page.aliases)
 35    then
 36      return nil, "Invalid page at position " .. pageIndex
 37    end
 38    page.id = tostring(pageIndex)
 39    if page.compositor ~= nil then
 40      if (page.compositor ~= "hyprland" and page.compositor ~= "niri") or desktops[page.compositor] then
 41        return nil, "Invalid or duplicate Desktop compositor"
 42      end
 43      desktops[page.compositor] = true
 44    end
 45    for _, alias in ipairs(page.aliases) do
 46      if not text(alias) or aliases[alias:lower()] then
 47        return nil, "Invalid or duplicate app alias"
 48      end
 49      aliases[alias:lower()] = true
 50    end
 51    for groupIndex, group in ipairs(page.groups) do
 52      if type(group) ~= "table" or not text(group.name) or not array(group.entries) or #group.entries == 0 then
 53        return nil, "Invalid group at position " .. groupIndex .. " in " .. page.name
 54      end
 55      group.id = tostring(groupIndex)
 56      for entryIndex, entry in ipairs(group.entries) do
 57        if
 58          type(entry) ~= "table"
 59          or not text(entry.keys)
 60          or not text(entry.description)
 61          or (entry.gap_before ~= nil and type(entry.gap_before) ~= "boolean")
 62        then
 63          return nil, "Invalid entry at position " .. entryIndex .. " in " .. page.name .. "/" .. group.name
 64        end
 65        entry.id = tostring(entryIndex)
 66      end
 67    end
 68  end
 69  if not desktops.hyprland or not desktops.niri then
 70    return nil, "Both Desktop pages are required"
 71  end
 72  return data
 73end
 74
 75function M.pages(data, compositor)
 76  local result = {}
 77  for _, page in ipairs(data.pages) do
 78    if page.compositor == nil or page.compositor == compositor then
 79      table.insert(result, page)
 80    end
 81  end
 82  return result
 83end
 84
 85function M.selectPage(pages, appIds)
 86  for _, appId in ipairs(appIds or {}) do
 87    for _, page in ipairs(pages) do
 88      for _, alias in ipairs(page.aliases) do
 89        if appId:lower() == alias:lower() then
 90          return page.id
 91        end
 92      end
 93    end
 94  end
 95  for _, page in ipairs(pages) do
 96    if page.compositor then
 97      return page.id
 98    end
 99  end
100  return pages[1].id
101end
102
103function M.appIds(compositor, window)
104  if type(window) ~= "table" then
105    return {}
106  end
107  local ids = {}
108  local fields = compositor == "hyprland" and { "class", "initialClass" } or { "app_id" }
109  for _, field in ipairs(fields) do
110    if text(window[field]) then
111      table.insert(ids, window[field])
112    end
113  end
114  return ids
115end
116
117function M.search(pages, selected, query)
118  local terms, result = {}, {}
119  for term in query:lower():gmatch("%S+") do
120    table.insert(terms, term)
121  end
122  for _, page in ipairs(pages) do
123    if page.id == selected then
124      for _, group in ipairs(page.groups) do
125        for _, entry in ipairs(group.entries) do
126          local haystack = table.concat({ page.name, group.name, entry.keys, entry.description }, " "):lower()
127          local matches = true
128          for _, term in ipairs(terms) do
129            if not haystack:find(term, 1, true) then
130              matches = false
131              break
132            end
133          end
134          if matches then
135            table.insert(
136              result,
137              { page = page, group = group, entry = entry, key = page.id .. "/" .. group.id .. "/" .. entry.id }
138            )
139          end
140        end
141      end
142    end
143  end
144  return result
145end
146
147function M.paginate(results, requested)
148  local count = math.max(1, math.ceil(#results / PAGE_SIZE))
149  local current = math.max(1, math.min(requested, count))
150  local rows = {}
151  for i = (current - 1) * PAGE_SIZE + 1, math.min(current * PAGE_SIZE, #results) do
152    table.insert(rows, results[i])
153  end
154  return rows, current, count
155end
156
157return M