1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
(local {: eventtap : execute : logger : pasteboard : uielement} hs)

(local log (logger.new :utils :info))

(fn chomp [s]
  "Returns the input without a trailing newline"
  (if (= (s:sub -1) "\n")
      (s:sub 1 -2)
      s))

;; paste content, preserving the previous pasteboard
(fn paste [content]
  (let [prev-pasteboard (pasteboard.getContents)]
    (pasteboard.setContents content)
    (eventtap.keyStroke [:cmd] :v)
    (pasteboard.setContents prev-pasteboard)))

(fn replace-selection [cb]
  "Replaces the current selection with the return value of the callback"
  (let [prev-pasteboard (pasteboard.getContents)
        e (uielement.focusedElement)
        text (if e (e:selectedText)
                 (do
                   (eventtap.keyStroke [:cmd] :c)
                   (pasteboard.getContents)))
        content (cb text prev-pasteboard)]
    (paste content)
    (pasteboard.setContents prev-pasteboard)))

(fn run [...]
  "Executes a list of commands"
  (each [_ cmd (ipairs [...])]
    (execute cmd)))

; https://github.com/Hammerspoon/hammerspoon/issues/3224#issuecomment-1294359070
(λ with-ax-hotfix [win cb]
  (let [app (win:application)
        ax-app (hs.axuielement.applicationElement app)
        prev-val ax-app.AXEnhancedUserInterface]
    (set ax-app.AXEnhancedUserInterface false)
    (cb)
    (set ax-app.AXEnhancedUserInterface prev-val)))

(fn debounce [delay f]
  (var timer nil)
  (fn []
    (when timer
      (timer:stop))
    (set timer (hs.timer.doAfter delay f))))

{: chomp : debounce : paste : replace-selection : run : with-ax-hotfix}