Quick Replies

@rebindv0.1.4

Six shortcut slots that type a saved reply, address, date or timestamp into the focused app.

Files

main.luau

-- Quick Replies: a chord types a saved line into whatever has focus.

local SLOTS = 6

local MOD_NAMES = {
  "Ctrl+Alt",
  "Ctrl+Shift",
  "Alt+Shift",
  "Ctrl+Alt+Shift",
  "Cmd/Win+Alt",
  "Cmd/Win+Shift",
}
local MODS = {
  ["Ctrl+Alt"] = { ctrl = true, alt = true },
  ["Ctrl+Shift"] = { ctrl = true, shift = true },
  ["Alt+Shift"] = { alt = true, shift = true },
  ["Ctrl+Alt+Shift"] = { ctrl = true, alt = true, shift = true },
  ["Cmd/Win+Alt"] = { win = true, alt = true },
  ["Cmd/Win+Shift"] = { win = true, shift = true },
}

local schema = {
  modifier = UI.Select(
    "Ctrl+Alt",
    MOD_NAMES,
    { label = "Modifier held with every slot key", tab = "Settings" }
  ),
  dateFormat = UI.Text("%Y-%m-%d", {
    label = "{date} format",
    placeholder = "%Y-%m-%d",
    tab = "Settings",
  }),
  timeFormat = UI.Text("%H:%M", {
    label = "{time} format",
    placeholder = "%H:%M",
    tab = "Settings",
  }),
}
for n = 1, SLOTS do
  schema[`key{n}`] =
    UI.Keybind(tostring(n), { label = "Key", tab = `Slot {n}` })
  schema[`text{n}`] = UI.Text("", {
    label = "Text",
    placeholder = "Thanks, {date}. Use \\n for a new line.",
    maxLength = 2000,
    tab = `Slot {n}`,
  })
end
local cfg = UI.Schema(schema)

local function chordHeld()
  local want = MODS[cfg.modifier] or {}
  local have = Input.GetModifiers()
  for _, m in { "ctrl", "alt", "shift", "win" } do
    if (want[m] == true) ~= have[m] then
      return false
    end
  end
  return true
end

local function expand(text)
  local tokens = {
    date = os.date(cfg.dateFormat),
    time = os.date(cfg.timeFormat),
    datetime = os.date(cfg.dateFormat .. " " .. cfg.timeFormat),
  }
  local out = text:gsub("{(%w+)}", function(name)
    return tokens[name]
  end)
  return (out:gsub("\\n", "\n"))
end

-- type only after the chord is fully released: a modifier still held turns
-- the inserted characters into shortcuts in the receiving app.
local function insert(n)
  Run(function()
    local waited = 0
    while waited < 1000 do
      local m = Input.GetModifiers()
      if not (m.ctrl or m.alt or m.shift or m.win) then
        break
      end
      Sleep(10)
      waited += 10
    end
    if waited >= 1000 then
      UI.Notify("Release the modifier keys to insert", "warning")
      return
    end
    local text = cfg[`text{n}`]
    if text == "" then
      UI.Notify(`Slot {n} has no text`, "warning")
      return
    end
    local ok, err = pcall(System.TypeText, expand(text))
    if not ok then
      Log.Error(`slot {n}: {err}`)
      UI.Notify(`Slot {n} could not be typed`, "error")
    end
  end)
end

for n = 1, SLOTS do
  Bind(cfg[`key{n}`], {
    when = chordHeld,
    action = function()
      return nil
    end,
    release = function()
      insert(n)
    end,
  })
end

function OnStart()
  Log.Info(
    `{cfg.modifier} + slot key types the slot. Change keys, then restart the script.`
  )
end