Skip to content

Instantly share code, notes, and snippets.

@BenziAhamed
Created February 8, 2023 16:18
Show Gist options
  • Save BenziAhamed/40b5ea9a8b84f704c1b48f46ef36d77e to your computer and use it in GitHub Desktop.
Save BenziAhamed/40b5ea9a8b84f704c1b48f46ef36d77e to your computer and use it in GitHub Desktop.
Parsing and rendering macOS shortcuts
import Cocoa
fileprivate let modifierMap = [
"cmd": "⌘",
"command": "⌘",
"shift": "⇧",
"control": "^",
"ctrl": "^",
"opt": "⌥",
"option": "⌥",
"alt": "⌥",
"⌘": "⌘",
"⇧": "⇧",
"⌥": "⌥",
"^": "^",
]
fileprivate let virtualKeyMap = [
"return": "↩", // kVK_Return
"enter": "↩", // kVK_Return
"ansi_enter": "⌤", // kVK_ANSI_KeypadEnter
"clear": "⌧", // kVK_ANSI_KeypadClear
"tab": "⇥", // kVK_Tab
"space": "␣", // kVK_Space
"delete": "⌫", // kVK_Delete
"del": "⌫", // kVK_Delete
"escape": "⎋", // kVK_Escape
"esc": "⎋", // kVK_Escape
"caps": "⇪", // kVK_CapsLock
"capslock": "⇪", // kVK_CapsLock
"function": "fn", // kVK_Function
"fn": "fn", // kVK_Function
"f1": "F1", // kVK_F1
"f2": "F2", // kVK_F2
"f3": "F3", // kVK_F3
"f4": "F4", // kVK_F4
"f5": "F5", // kVK_F5
"f6": "F6", // kVK_F6
"f7": "F7", // kVK_F7
"f8": "F8", // kVK_F8
"f9": "F9", // kVK_F9
"f10": "F10", // kVK_F10
"f11": "F11", // kVK_F11
"f12": "F12", // kVK_F12
"f13": "F13", // kVK_F13
"f14": "F14", // kVK_F14
"f15": "F15", // kVK_F15
"f16": "F16", // kVK_F16
"f17": "F17", // kVK_F17
"f18": "F18", // kVK_F18
"f19": "F19", // kVK_F19
"f20": "F20", // kVK_F20
"home": "↖", // kVK_Home
"pageup": "⇞", // kVK_PageUp
"forwarddelete": "⌦", // kVK_ForwardDelete
"fdel": "⌦", // kVK_ForwardDelete
"end": "↘", // kVK_End
"pagedown": "⇟", // kVK_PageDown
"left": "◀︎", // kVK_LeftArrow
"right": "▶︎", // kVK_RightArrow
"down": "▼", // kVK_DownArrow
"up": "▲", // kVK_UpArrow
"↩": "↩", // kVK_Return
"⌤": "⌤", // kVK_ANSI_KeypadEnter
"⌧": "⌧", // kVK_ANSI_KeypadClear
"⇥": "⇥", // kVK_Tab
"␣": "␣", // kVK_Space
"⌫": "⌫", // kVK_Delete
"⎋": "⎋", // kVK_Escape
"⇪": "⇪", // kVK_CapsLock
"↖": "↖", // kVK_Home
"⇞": "⇞", // kVK_PageUp
"⌦": "⌦", // kVK_ForwardDelete
"↘": "↘", // kVK_End
"⇟": "⇟", // kVK_PageDown
"◀︎": "◀︎", // kVK_LeftArrow
"▶︎": "▶︎", // kVK_RightArrow
"▼": "▼", // kVK_DownArrow
"▲": "▲", // kVK_UpArrow
]
fileprivate enum ShortcutToken {
case text(String)
case space
}
fileprivate struct ShortcutLexer {
static func genTokens(_ input: String) -> [ShortcutToken] {
var index = input.startIndex
var tokens = [ShortcutToken]()
func advance() {
input.formIndex(after: &index)
}
func rewind(count: Int) {
input.formIndex(&index, offsetBy: -count)
}
var current: Character? {
return index < input.endIndex ? input[index] : nil
}
func isSingleMatch(_ c: Character) -> Bool {
let text = "\(c)"
return modifierMap[text] == text || virtualKeyMap[text] == text
}
func nextToken() -> ShortcutToken? {
guard let c = current else { return nil }
switch c {
case let c where c == " ":
advance()
return .space
case let c where isSingleMatch(c):
advance()
return .text("\(c)")
default:
var text = ""
while let c = current, c != " ", !isSingleMatch(c) {
text.append(c)
advance()
}
return .text(text)
}
}
while let token = nextToken() {
tokens.append(token)
}
return tokens
}
}
ShortcutLexer.genTokens("cmd shift alt p")
struct Shortcut {
let modifiers: Set<String>
let virtualKey: String
let shortcut: String
private var modString: String {
var result = ""
for mod in ["^","⌥","⇧","⌘"] where modifiers.contains(mod) {
result.append(mod)
}
return result
}
var rendered: String {
var result = ""
result.append(modString)
if !virtualKey.isEmpty {
result.append(virtualKey)
}
else {
result.append(shortcut)
}
return result
}
static func from(_ text: String) -> Shortcut {
return ShortcutParser.genShortcut(text.lowercased())
}
}
extension Shortcut : CustomDebugStringConvertible {
var debugDescription: String {
var desc = ""
let mods = modString
if !mods.isEmpty {
desc.append("mods(\(mods))")
}
if !virtualKey.isEmpty {
desc.append(desc.isEmpty ? "" : " ")
desc.append("vkey(\(virtualKey))")
}
else if !shortcut.isEmpty {
desc.append(desc.isEmpty ? "" : " ")
desc.append("cmd(\(shortcut))")
}
return desc
}
}
fileprivate struct ShortcutParser {
static func genShortcut(_ text: String) -> Shortcut {
let tokens = ShortcutLexer.genTokens(text)
var modifiers = Set<String>()
var virtualKey = ""
var shortcut = ""
for token in tokens {
guard case let .text(t) = token else {
continue
}
if let mod = modifierMap[t] {
modifiers.insert(mod)
}
else if let vkey = virtualKeyMap[t] {
virtualKey = vkey
}
else {
shortcut = t.uppercased()
}
}
return .init(
modifiers: modifiers,
virtualKey: virtualKey,
shortcut: shortcut
)
}
}
Shortcut.from("cmd tab a").rendered
Shortcut.from("cmd fn q").rendered
Shortcut.from("cmd fn q l s").rendered
Shortcut.from("cmd del 1").rendered
Shortcut.from("cmd space").rendered
Shortcut.from("ctrl space")
Shortcut.from("ctrl up").rendered
Shortcut.from("ctrl shift down").rendered
Shortcut.from("opt cmd shift n").rendered
Shortcut.from("opt cmd shift /").rendered
Shortcut.from("opt cmd shift f1")
Shortcut.from("opt cmd shift f1 q")
enum ShortcutMatch: Int {
case exact = 2
case partial = 1
case none = 0
}
func match(_ shortcut1: Shortcut, _ shortcut2: Shortcut) -> ShortcutMatch {
let r1 = shortcut1.rendered
let r2 = shortcut2.rendered
if r1 == r2 {
return .exact
}
if !shortcut1.modifiers.intersection(shortcut2.modifiers).isEmpty {
return .partial
}
if shortcut1.virtualKey == shortcut2.virtualKey {
return .partial
}
if shortcut1.shortcut == shortcut2.shortcut {
return .partial
}
return .none
}
Shortcut.from("cmd shift q")
Shortcut.from("cmd shift q").rendered
Shortcut.from("alt shift cmd q").rendered
Shortcut.from("shift cmd del")
Shortcut.from("shift del").rendered
Shortcut.from("del").rendered
Shortcut.from("⌫").rendered
Shortcut.from("⌫").rendered
Shortcut.from("Q⌥⇧⌘").rendered == "⌥⇧⌘Q"
Shortcut.from("CONTROL HOME").rendered
Shortcut.from("CONTROL ALT SHIFT HOME").rendered
Shortcut.from("CONTROL ALT SHIFT CAPS").rendered
Shortcut.from("CONTROL ALT TAB").rendered
Shortcut.from("CONTROL ALT enter").rendered
match(Shortcut.from("cmd shift q"), Shortcut.from("cmd q"))
match(Shortcut.from("cmd shift q"), Shortcut.from("cmd q shift"))
match(Shortcut.from("cmd shift q"), Shortcut.from("q shift"))
match(Shortcut.from("⌫"), Shortcut.from("del"))
Shortcut.from("Q⌥⇧⌘")
Shortcut.from("⌫")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment