### Run Setup Before Tests with Before Source: https://github.com/agzam/spacehammer/blob/master/docs/testing.org Use the `before` function to run a setup function once before all tests in a `describe` suite. Useful for preparing data or allocating resources. ```fennel (describe "Functional Tools" (fn [] (before (fn [] (print "Perform pre-test setup"))) (it "Should do math" (fn [] (is.eq? (+ 1 1) 2 "Did not result in 2"))))) ``` -------------------------------- ### Example of Override Advice Source: https://github.com/agzam/spacehammer/blob/master/docs/advice.org This example shows how to use defadvice with the :override type to replace the behavior of original-fn. ```fennel (import-macros {: defn : defadvice} :lib.advice.macros) (defn original-fn [x y z] "docstr" "Hi") (defadvice advice-fn [x y z] :override original-fn "Overrides original-fn" "over-it") ``` -------------------------------- ### Example of Around Advice Source: https://github.com/agzam/spacehammer/blob/master/docs/advice.org This example demonstrates using defadvice with the :around type to wrap an existing function, allowing modification of arguments or execution flow. ```fennel (import-macros {: defn : defadvice} :lib.advice.macros) (defn original-fn [x y z] "docstr" "Good job,") (defadvice advice-fn [orig-fn x y z] :around original-fn "Wraps original-fn" ;; May call orig-fn anytime, maybe even more than once ) ``` -------------------------------- ### Example :before-until Advice Execution (Conditional Skip) Source: https://github.com/agzam/spacehammer/blob/master/docs/advice.org Demonstrates :before-until where the advising function returns true (truthy), preventing the original function from executing. ```fennel (original-fn 1 2 3) ;; => true ;; advice-fn returned truthy value, original not called ``` -------------------------------- ### Example :before Advice Execution Source: https://github.com/agzam/spacehammer/blob/master/docs/advice.org Demonstrates calling an original function after a :before advice has been applied. The output shows the side effect of the advice function and the final return value of the original function. ```fennel (original-fn 1 2 3) ;; => "before: [1 2 3]" ;; Before hook printing args ;; => 6 ;; Original function sum ``` -------------------------------- ### Install Hammerspoon IPC CLI Source: https://github.com/agzam/spacehammer/blob/master/docs/testing.org Install the Hammerspoon IPC CLI using this Lua command. This is a prerequisite for testing. ```lua hs.ipc.cliInstall() ``` -------------------------------- ### Install Spacehammer via Homebrew Source: https://context7.com/agzam/spacehammer/llms.txt Install Spacehammer using Homebrew. Optionally, specify a custom configuration repository. ```bash # Install via Homebrew brew tap agzam/spacehammer brew install spacehammer # With custom config repository HOMEBREW_SPACEHAMMER_CONFIG=git@github.com:username/my-config.git \ brew install spacehammer ``` -------------------------------- ### Example :before-while Advice Execution (Conditional Skip) Source: https://github.com/agzam/spacehammer/blob/master/docs/advice.org Illustrates :before-while where the advising function returns nil (falsey), preventing the original function from executing. ```fennel (original-fn 1 2 3) ;; => nil ;; Original function was not called, advice fn returned nil ``` -------------------------------- ### Example of Around Advice Source: https://github.com/agzam/spacehammer/blob/master/docs/advice.org Demonstrates defining and using an 'around' advice macro to modify function arguments before calling the original function. ```fennel (import-macros {: defn : defadvice} :lib.advice.macros) (defn original-fn [x y z] "docstr" (+ x y z)) (original-fn 1 2 3) ;; => 6 (defadvice advice-fn [x y z] :filter-args original-fn "filter-args original-fn" [(* x 2) (* y 2) (* z 2)]) (original-fn 1 2 3) ;; => 10 ;; Values returned by advice-fn applied to original-fn ``` -------------------------------- ### Install nodemon Package Source: https://github.com/agzam/spacehammer/blob/master/docs/testing.org Install the nodemon npm package to enable auto-running of tests. This command requires Node.js to be installed. ```bash npm install nodemon ``` -------------------------------- ### Example of Defn Macro Source: https://github.com/agzam/spacehammer/blob/master/docs/advice.org Demonstrates defining an advisable function with 'defn' and then overriding it with 'defadvice'. ```fennel (import-macros {: defn : defadvice} :lib.advice.macros) (defn defn-func-1 [x y z] "docstr" "Hi") (defn-func-1) ;; => "Hi" (defadvice defn-func-override [x y z] :override defn-func-1 "Overrides defn-func-1" "over-it") (defn-func-1) ;; => "over-it" ``` -------------------------------- ### Example :after-while Advice Execution (Conditional Execution) Source: https://github.com/agzam/spacehammer/blob/master/docs/advice.org Illustrates :after-while where the original function returns a truthy value, triggering the execution of the advising function. ```fennel (original-fn 1 2 3) ;; => 6 ;; Original-fn returned truthy value, advice-fn called ``` -------------------------------- ### Example :after-until Advice Execution (Conditional Execution) Source: https://github.com/agzam/spacehammer/blob/master/docs/advice.org Demonstrates :after-until where the original function returns a truthy value, preventing the advising function from executing. ```fennel (original-fn 1 2 3) ;; => true ;; original-fn returned truthy vaue, advice-fn not called ``` -------------------------------- ### Example of Filter-Return Advice Source: https://github.com/agzam/spacehammer/blob/master/docs/advice.org Illustrates using a 'filter-return' advice macro to modify the return value of an original function. ```fennel (import-macros {: defn : defadvice} :lib.advice.macros) (defn original-fn [x y z] "docstr" (+ x y z)) (original-fn 1 2 3) ;; => 6 (defadvice advice-fn [sum] :filter-return original-fn "filter-return original-fn" (* sum 2)) (original-fn 1 2 3) ;; => 12 ;; Return value of original-fn passed to advice-fn ``` -------------------------------- ### Get Advice for an Advisable Function in Fennel Source: https://github.com/agzam/spacehammer/blob/master/docs/advice.org Illustrates how to retrieve a list of advice applied to an advisable function using `get-advice`. This is helpful for debugging. ```fennel (import-macros {: defn : defadvice} :lib.advice.macros) (local {: get-advice} :lib.advice) (defn original-fn [x y z] "docstr" "default") (defadvice advice-fn [x y z] :override original-fn "over-it") (pprint (get-advice original-fn)) ``` ```fennel [ {:f "advice-fn: 0x600000278c80" :type "override"} ] ``` -------------------------------- ### Install Hammerspoon IPC Utility Source: https://github.com/agzam/spacehammer/blob/master/docs/emacs.org Installs the Hammerspoon IPC utility. Ensure the provided path is accessible and in your system's $PATH. This is crucial for Spacehammer to function correctly. ```lua hs.ipc.cliInstall("/opt/homebrew") ``` -------------------------------- ### Example :after Advice Execution Source: https://github.com/agzam/spacehammer/blob/master/docs/advice.org Shows the execution of an :after advice. The original function's side effect (printing) occurs first, followed by the execution of the advice function, whose return value is then returned to the caller. ```fennel (original-fn 1 2 3) ;; => 6 ;; original fn prints the sum ;; => 4 ;; advice fn called after, its value returned ``` -------------------------------- ### Configure Hyper Key Bindings Source: https://context7.com/agzam/spacehammer/llms.txt Sets up bindings for the 'hyper' key, which is typically mapped to F18 using external tools like Karabiner. This example shows a simple alert action. ```fennel ;; Hyper key support (maps to F18, use with Karabiner) (local config {:hyper {:key :F18} :keys [{:mods [:hyper] :key :f :action (fn [] (alert "Hyper+F pressed!"))}]}) ``` -------------------------------- ### Install Spacehammer Package from GitHub (Elisp) Source: https://github.com/agzam/spacehammer/blob/master/docs/emacs.org Installs the Spacehammer Emacs package directly from GitHub. This is for macOS systems and requires Emacs to be running in daemon mode. ```elisp (when (eq system-type 'darwin) (package! spacehammer :recipe (:host github :repo "agzam/spacehammer" :files ("*.el")))) ``` -------------------------------- ### Window Management Actions in Fennel Source: https://context7.com/agzam/spacehammer/llms.txt Examples of window management actions available through the 'windows' module, callable via action strings in the Spacehammer configuration. ```fennel ;; Window module functions (called via action strings in config) ;; Move window to half of screen (hjkl directions) ;; "windows:resize-half-left" - Left half ;; "windows:resize-half-right" - Right half ;; "windows:resize-half-top" - Top half ;; "windows:resize-half-bottom" - Bottom half ;; Jump between windows spatially ;; "windows:jump-window-left" ;; "windows:jump-window-right" ;; "windows:jump-window-above" ;; "windows:jump-window-below" ;; Resize incrementally using grid ;; "windows:resize-inc-left" ;; "windows:resize-inc-right" ;; "windows:resize-inc-top" ;; "windows:resize-inc-bottom" ;; Move between monitors ;; "windows:move-north" ;; "windows:move-south" ;; "windows:move-east" ;; "windows:move-west" ;; Other window operations ;; "windows:maximize-window-frame" - Maximize current window ;; "windows:center-window-frame" - Center with configurable ratio ;; "windows:show-grid" - Show interactive grid UI ;; "windows:undo" - Undo last window operation ;; "windows:jump-to-last-window" - Toggle to previous window ;; "windows:jump" - Show window hints overlay ``` -------------------------------- ### Vim Mode Functions and App-Specific Control Source: https://context7.com/agzam/spacehammer/llms.txt Lists the core functions for enabling/disabling Vim mode and provides an example for disabling it for specific applications like Emacs. ```fennel ;; Vim mode functions: ;; vim.enable - Activate vim mode ;; vim.disable - Deactivate vim mode ``` ```fennel ;; App-specific vim mode control (disable for Emacs) (local emacs-config {:key "Emacs" :activate (fn [] (vim.disable)) :deactivate (fn [] (vim.enable))}) ``` -------------------------------- ### Customization: Direction-Aware Buffer Placement Source: https://github.com/agzam/spacehammer/blob/master/docs/edit-with-emacs-spec.org Example of a custom `display-buffer` action function for GUI Emacs to place the edit buffer. ```emacs-lisp do-applescript ``` -------------------------------- ### Get Advisable Function Key Source: https://github.com/agzam/spacehammer/blob/master/docs/advice.org Shows how to retrieve the unique key for an advisable function, which can be used to reference it in advice definitions. ```fennel (import-macros {: defn} :lib.advice.macros) (defn defn-func-2 [x y z] "docstr" "default") (print defn-func-2.key) ``` -------------------------------- ### Equivalent Advice Application Source: https://github.com/agzam/spacehammer/blob/master/docs/advice.org Demonstrates two equivalent ways to add advice to a function: using the function reference directly or using its unique key. ```fennel (add-advice defn-func-2 :override (fn [x y z] "over-it")) (add-advice :test/advice-test/defn-func-2 :override (fn [x y z] "over-it")) ``` -------------------------------- ### Configure Application Menu Bindings Source: https://context7.com/agzam/spacehammer/llms.txt Sets up key bindings for launching specific applications via a menu. Uses the 'activator' helper function. ```fennel ;; Apps menu configuration (local app-bindings [{:key :e :title "Emacs" :action (activator "Emacs")} {:key :g :title "Chrome" :action (activator "Google Chrome")} {:key :f :title "Firefox" :action (activator "Firefox")} {:key :i :title "iTerm" :action (activator "iterm")} {:key :s :title "Slack" :action (activator "Slack")} {:key :b :title "Brave" :action (activator "brave browser")}]) ``` -------------------------------- ### Run Before and After Hooks in a Test Suite Source: https://github.com/agzam/spacehammer/blob/master/docs/testing.org Demonstrates the usage of `before` and `after` hooks, which run once before and after all tests in a suite, respectively. ```fennel (describe "A Test Suite" (fn [] (before (fn [] (print "This only prints once. Before all tests in this suite."))) (after (fn [] (print "This only prints once. After all tests in this suite."))) (it "Addition" (fn [] (is.eq? (+ 1 1) 2 "Did not result in 2"))) (it "Subtraction" (fn [] (is.eq? (- 1 1) 0 "Did not result in 0"))))) ``` -------------------------------- ### Make a Function Advisable with make-advisable Source: https://github.com/agzam/spacehammer/blob/master/docs/advice.org Shows how to use the `make-advisable` function directly to create an advisable function. This is an alternative when macros are not preferred. ```fennel (local {: make-advisable} :lib.advice) (local advisable (make-advisable :advisable (fn [x y z] "default"))) (advisable) ;; => "default" ``` -------------------------------- ### Running Tests Source: https://github.com/agzam/spacehammer/blob/master/docs/testing.org Instructions on how to execute tests using the Spacehammer testing library. ```APIDOC ## Running Tests To run tests, navigate to the `~/.hammerspoon` directory in your terminal and execute the following command: ### Command ```bash ./run-test test/*.fnl ``` ### Example Output ``` Running tests for /Users/j/.hammerspoon/test/functional-test.fnl Running tests for /Users/j/.hammerspoon/test/statemachine-test.fnl Functional Call when calls function if it exists ... [ OK ] Compose combines functions together in reverse order ... [ OK ] Contains? returns true if list table contains a value ... [ OK ] State Machine Should create a new fsm in the closed state ... [ OK ] Should transition to opened on open event ... [ OK ] Should transition back to opened on close event ... [ OK ] Should not explode when dispatching an unhandled event ... 05:27:10 ** Warning: statemach: Could not fail from closed state [ OK ] Ran 7 tests 7 passed 0 failed in 0.038301000000047 seconds ``` ``` -------------------------------- ### Define Advice Before Function Source: https://github.com/agzam/spacehammer/blob/master/docs/advice.org Illustrates that advice can be defined before the target function exists, showcasing flexible advice ordering. ```fennel (import-macros {: defn : defadvice} :lib.advice.macros) (defadvice defn-func-override [x y z] :override defn-func-3 "Overrides defn-func-3" "over-it") (defn defn-func-3 [x y z] "docstr" "Hi") (defn-func-3) ;; => "over-it" ``` -------------------------------- ### Configure Multimedia Controls Source: https://context7.com/agzam/spacehammer/llms.txt Set up key bindings for controlling media playback and volume using home-row keys. This module eliminates the need for dedicated media keys. ```fennel ;; Media menu configuration (local media-bindings [{:key :s :title "Play/Pause" :action "multimedia:play-or-pause"} {:key :h :title "Prev Track" :action "multimedia:prev-track"} {:key :l :title "Next Track" :action "multimedia:next-track"} {:key :j :title "Volume Down" :action "multimedia:volume-down" :repeatable true} {:key :k :title "Volume Up" :action "multimedia:volume-up" :repeatable true} {:key :a :title "Launch Spotify" :action (activator "Spotify")}]) (local menu-items [{:key :m :title "Media" :items media-bindings}]) ``` -------------------------------- ### Log Advisable Function Keys in Fennel Source: https://github.com/agzam/spacehammer/blob/master/docs/advice.org Shows how to use `print-advisable-keys` to display a formatted list of all advisable function keys currently registered. ```fennel (local {: print-advisable-keys} :lib.advice) (print-advisable-keys) ``` -------------------------------- ### Configure Application Switching Hotkeys Source: https://context7.com/agzam/spacehammer/llms.txt Defines global hotkeys for navigating between applications. Uses the 'apps' module. ```fennel ;; Global hotkeys for app switching (local common-keys [{:mods [:cmd :alt] :key :n :action "apps:next-app"} {:mods [:cmd :alt] :key :p :action "apps:prev-app"}]) ``` -------------------------------- ### Enable Vim Mode Configuration Source: https://context7.com/agzam/spacehammer/llms.txt Enables Vim mode globally by setting the 'enabled' flag in the configuration. Requires the 'vim' module. ```fennel ;; Enable vim mode in config (local config {:vim {:enabled true}}) ``` -------------------------------- ### Configure Window Menu and Grid in Fennel Source: https://context7.com/agzam/spacehammer/llms.txt Define a 'Window' modal menu with specific actions and lifecycle hooks. Configure grid layout and centering ratio for window management. ```fennel ;; Window menu config with lifecycle hooks (local window-menu {:key :w :title "Window" :enter "windows:enter-window-menu" ;; Shows monitor numbers :exit "windows:exit-window-menu" ;; Hides monitor numbers :items [{:key :m :title "Maximize" :action "windows:maximize-window-frame"} {:key :c :title "Center" :action "windows:center-window-frame"} {:key :u :title "Undo" :action "windows:undo"} {:key :h :action "windows:resize-half-left" :repeatable true} {:key :l :action "windows:resize-half-right" :repeatable true}]) ;; Grid configuration in config (local config {:grid {:size "3x2" :margins [10 10]} :modules {:windows {:center-ratio "80:50"}}}) ``` -------------------------------- ### Implement State Machine Source: https://context7.com/agzam/spacehammer/llms.txt Define and manage states and transitions for modal menus, vim mode, and other stateful components. This implementation powers various state-dependent features. ```fennel ;; Create a state machine (local statemachine (require :lib.statemachine)) ;; Define states and transitions (local states {:idle {:activate ->active} :active {:deactivate ->idle :timeout ->idle}}) ;; Transition function returns new state and effect (fn ->active [state action extra] {:state {:current-state :active :context (merge state.context {:data extra})} :effect :show-ui}) ;; Create machine from template (local fsm (statemachine.new {:state {:current-state :idle :context {}} :states states})) ;; Send actions to trigger transitions (fsm.send :activate {:some "data"}) ;; Subscribe to transitions for side effects (local effect-handler (statemachine.effect-handler {:show-ui (fn [state] (display-something) ;; Return cleanup function (fn [] (hide-something)))}) (fsm.subscribe effect-handler) ``` -------------------------------- ### Configure Emacs Submenu Bindings Source: https://context7.com/agzam/spacehammer/llms.txt Sets up key bindings for an Emacs-specific submenu within the main modal. Requires the 'emacs' module. ```fennel ;; Emacs submenu in main modal (local emacs-bindings [{:key :c :title "Capture" :action (fn [] (emacs.capture))} {:key :z :title "Note" :action (fn [] (emacs.note))} {:key :v :title "Split" :action "emacs:vertical-split-with-emacs"} {:key :f :title "Full Screen" :action "emacs:full-screen"}]) ``` -------------------------------- ### Configure App-Specific Key Bindings and Menus Source: https://context7.com/agzam/spacehammer/llms.txt Define custom key bindings, menu items, and lifecycle hooks for specific applications. This configuration activates only when the target application is focused. ```fennel ;; App config with lifecycle hooks (local emacs-config {:key "Emacs" :activate (fn [] (vim.disable)) :deactivate (fn [] (vim.enable)) :launch "emacs:maximize" :items [] :keys []}) ;; Browser config with custom keys and extended menu (local browser-keys [{:mods [:cmd :shift] :key :l :action "chrome:open-location"} {:mods [:alt] :key :k :action "chrome:next-tab" :repeat true} {:mods [:alt] :key :j :action "chrome:prev-tab" :repeat true}]) (local chrome-config {:key "Google Chrome" :keys browser-keys :items (concat menu-items [{:key "'" :title "Edit with Emacs" :action "emacs:edit-with-emacs"}])}) ;; Slack-specific enhancements (local slack-config {:key "Slack" :keys [{:mods [:cmd] :key :g :action "slack:scroll-to-bottom"} {:mods [:ctrl] :key :r :action "slack:add-reaction"} {:mods [:ctrl] :key :h :action "slack:prev-element"} {:mods [:ctrl] :key :l :action "slack:next-element"} {:mods [:ctrl] :key :i :action "slack:next-history" :repeat true} {:mods [:ctrl] :key :o :action "slack:prev-history" :repeat true}]}) ;; Register apps in main config (local config {:apps [emacs-config chrome-config slack-config]}) ``` -------------------------------- ### Define Modal Menu Items in Fennel Source: https://context7.com/agzam/spacehammer/llms.txt Configure modal menus in `~/.spacehammer/config.fnl`. Define actions for launching applications, calling module functions, creating submenus, and repeatable actions. ```fennel ;; In ~/.spacehammer/config.fnl ;; Define a menu item that launches an application (local launch-alfred {:title "Alfred" :key :SPACE :action (fn [] (hs.application.launchOrFocus "Alfred 4"))}) ;; Define a menu item that calls a module function (local slack-jump {:title "Slack" :key :s :action "slack:quick-switcher"}) ;; Define a submenu with nested items (local submenu {:title "Submenu" :key :t :items [{:key :m :title "Show a message" :action (fn [] (alert "Hello from submenu!"))}]}) ;; Repeatable action (modal stays open after execution) (local window-resize {:title "Resize Right" :mods [:shift] :key :l :action "windows:resize-right" :repeatable true}) ;; Main config with menu items (local config {:title "Main Menu" :items [launch-alfred slack-jump submenu window-resize] :keys [{:mods [:alt] :key :space :action "lib.modal:activate-modal"}]}) ``` -------------------------------- ### Require Assertions Library Source: https://github.com/agzam/spacehammer/blob/master/docs/testing.org Import the assertions library in your test files. This makes assertion functions like `is.eq?` available. ```fennel (local is (require :lib.testing.assert)) ``` -------------------------------- ### Define :before-while Advice Source: https://github.com/agzam/spacehammer/blob/master/docs/advice.org Use :before-while to execute an advising function before the original. If the advising function returns a truthy value, the original function is also called; otherwise, it is skipped. ```fennel (import-macros {: defn : defadvice} :lib.advice.macros) (defn original-fn [x y z] "docstr" (+ x y z)) (original-fn 1 2 3) ;; => 6 (defadvice advice-fn [x y z] :before-while original-fn "Before-while original-fn" nil) ``` -------------------------------- ### Defn Macro Usage Source: https://github.com/agzam/spacehammer/blob/master/docs/advice.org Shows the basic syntax for defining an advisable function using the 'defn' macro, which requires a docstring. ```fennel (defn function-name [args] "docstr" body-1 ...body ;; Optional ) ``` -------------------------------- ### Define :before Advice Source: https://github.com/agzam/spacehammer/blob/master/docs/advice.org Use :before to execute an advising function with the same arguments before the original function. The return value of the advising function is discarded. ```fennel (import-macros {: defn : defadvice} :lib.advice.macros) (defn original-fn [x y z] "docstr" (+ x y z)) (defadvice advice-fn [x y z] :before original-fn "Before original-fn" (print "before:" (hs.inspect [x y z]))) ``` -------------------------------- ### defadvice Expansion to add-advice Source: https://github.com/agzam/spacehammer/blob/master/docs/advice.org This code shows how a defadvice call expands into an add-advice call, demonstrating the underlying mechanism for adding advice. ```fennel (local defn-func-override (let [adv_0_ (require "lib.advice") advice-fn_0_ (fn defn-func-override [x y z] "Overrides defn-func-4" "over-it")] (adv_0_.add-advice defn-func-4 "override" advice-fn_0_) advice-fn_0_)) ``` -------------------------------- ### Configure Emacs Integration Hotkeys Source: https://context7.com/agzam/spacehammer/llms.txt Defines global hotkeys for triggering Emacs-related actions. Ensure the 'emacs' module is enabled. ```fennel ;; Trigger edit-with-emacs globally (Cmd+Ctrl+O by default) (local common-keys [{:mods [:cmd :ctrl] :key :o :action "emacs:edit-with-emacs"}]) ``` -------------------------------- ### Add Advice using add-advice Function Source: https://github.com/agzam/spacehammer/blob/master/docs/advice.org Use the add-advice function for a more primitive way to add advice, which is what defadvice uses internally. This is useful for direct manipulation of advice chains. ```fennel (local {: add-advice} :lib.advice) (defn defn-func-5 [x y z] "docstr" "default") (add-advice defn-func-5 :override (fn [x y z] "over-it")) ``` -------------------------------- ### Around Advice Behavior Source: https://github.com/agzam/spacehammer/blob/master/docs/advice.org Illustrates the behavior of the 'around' advice type, which wraps the original function. The advice function receives the original function as its first argument. ```fennel (fn [...] (advice-fn original-function (table.unpack [...]))) ``` -------------------------------- ### Override Advice Behavior Source: https://github.com/agzam/spacehammer/blob/master/docs/advice.org Demonstrates the behavior of the 'override' advice type, where the advice function completely replaces the original function's execution. ```fennel (fn [...] (advice-fn (table.unpack [...]))) ``` -------------------------------- ### Run Spacehammer Tests Source: https://github.com/agzam/spacehammer/blob/master/docs/testing.org Execute tests using the provided shell command. Ensure you are in the ~/.hammerspoon directory. ```bash ./run-test test/*.fnl ``` -------------------------------- ### Run Cleanup After Tests with After Source: https://github.com/agzam/spacehammer/blob/master/docs/testing.org Use the `after` function to run a cleanup function once after all tests in a `describe` suite. Useful for resetting test state. ```fennel (describe "Functional Tools" (fn [] (after (fn [] (print "Perform post-test cleanup"))) (it "Should do math" (fn [] (is.eq? (+ 1 1) 2 "Did not result in 2"))))) ``` -------------------------------- ### Define :before-until Advice Source: https://github.com/agzam/spacehammer/blob/master/docs/advice.org Use :before-until to execute an advising function before the original. If the advising function returns a falsey value, the original function is called; otherwise, it is skipped. This is the inverse of :before-while. ```fennel (import-macros {: defn : defadvice} :lib.advice.macros) (defn original-fn [x y z] "docstr" (+ x y z)) (original-fn 1 2 3) ;; => 6 (defadvice advice-fn [x y z] :before-until original-fn "Before-until original-fn" true) ``` -------------------------------- ### Edit-with-Emacs Invocation Lifecycle Source: https://github.com/agzam/spacehammer/blob/master/docs/edit-with-emacs-spec.org This diagram outlines the steps involved when the Edit-with-Emacs hotkey is pressed, from capturing window information to building a session based on clipboard content. ```text User presses hotkey │ ├─ Capture: current window, app PID, title, screen ID ├─ Probe: get-focused-text-element (AX check) ├─ Generate: unique session-id ├─ Send: Cmd+C (copy) │ ├─ wait-for-clipboard-change (poll up to 500ms) │ │ │ ├─ Clipboard changed? → User had a selection │ │ └─ build-session(... had-selection=true) │ │ │ └─ No change? → Nothing was selected │ ├─ Send: Cmd+A, Cmd+C (select all, copy) │ ├─ wait-for-clipboard-change again │ │ ├─ Changed? → build-session(... had-selection=false) │ │ └─ Still nothing? → Last resort: read AXValue directly │ │ └─ Set clipboard from AXValue, build-session(... had-selection=false) ``` -------------------------------- ### Configure Global Key Bindings Source: https://context7.com/agzam/spacehammer/llms.txt Defines global hotkeys that are not part of modal menus, including activating modals and toggling the console. ```fennel ;; Global keys bound outside of modal menus (local common-keys [{:mods [:alt] :key :space :action "lib.modal:activate-modal"} {:mods [:cmd :alt] :key :n :action "apps:next-app"} {:mods [:cmd :ctrl] :key "`" :action hs.toggleConsole}]) ``` -------------------------------- ### Define a Test Suite with Describe Source: https://github.com/agzam/spacehammer/blob/master/docs/testing.org Use the `describe` function to label a test suite. The function body can contain other `describe` calls or `it` tests. ```fennel (describe "Functional Tools" (fn [] ;; Other describe calls or `it` tests can run here )) ``` -------------------------------- ### Testing API - Before Source: https://github.com/agzam/spacehammer/blob/master/docs/testing.org Runs a function once before all tests in a suite. ```APIDOC ## Before Runs a function once before all tests in a `describe` suite. ### Usage ```fennel (describe "Functional Tools" (fn [] (before (fn [] (print "Perform pre-test setup"))) (it "Should do math" (fn [] (is.eq? (+ 1 1) 2 "Did not result in 2"))))) ``` ### Description `before` is useful for setting up data or allocating resources needed by the tests. It runs only once per suite, not before each test. ``` -------------------------------- ### Define an Advisable Function Source: https://github.com/agzam/spacehammer/blob/master/docs/advice.org Use the defn macro to define a function that can be advised. This function serves as the base for customizations. ```fennel (defn defn-func-4 [x y z] "docstr" "default") ``` -------------------------------- ### Perform a Test with It Source: https://github.com/agzam/spacehammer/blob/master/docs/testing.org Use the `it` function to perform a test that can pass or fail. The body of `it` calls should run code and perform assertions. ```fennel (describe "Basic Fennel Tests" (fn [] (it "Should do math" (fn [] (is.eq? (+ 1 1) 2 "Did not result in 2"))))) ``` -------------------------------- ### Define and Override Function Advice in Fennel Source: https://github.com/agzam/spacehammer/blob/master/docs/advice.org Demonstrates using `afn` to define a scoped function and `defadvice` to override it. The `afn` macro transforms the call into `make-advisable`. ```fennel (import-macros {: afn : defadvice} :lib.advice.macros) (let [scoped-func (afn scoped-func [x y z] "default")] (scoped-func) ;; => "default" (defadvice scoped-func-advice [x y z] :override scoped-func "Overrides scoped-func" "over-it") (scoped-func) ;; => "over-it" ) ``` ```fennel (let [adv_0_ (require "lib.advice")] (adv_0_.make-advisable "priv-func" (fn [x y z] "default"))) ``` -------------------------------- ### Afn Macro Usage Source: https://github.com/agzam/spacehammer/blob/master/docs/advice.org Shows the syntax for defining an inline advisable function using the 'afn' macro, suitable for callbacks or 'let' forms. ```fennel (afn function-name [args] body-1 ...body ;; Optional ) ``` -------------------------------- ### Create App Activator Function Source: https://context7.com/agzam/spacehammer/llms.txt A higher-order function to create app activation actions for menus or hotkeys. It takes an app name and returns a function that activates that app. ```fennel ;; Quick app launcher using activator helper (fn activator [app-name] "Higher order function to activate a target app" (fn [] (windows.activate-app app-name))) ``` -------------------------------- ### Emacs Buffer Creation for Edit-with-Emacs Source: https://github.com/agzam/spacehammer/blob/master/docs/edit-with-emacs-spec.org This describes the Emacs-side process for creating and initializing an editing buffer when invoked by Hammerspoon. It includes buffer naming, setting local variables, pasting clipboard content, and enabling the minor mode. ```elisp 1. Create/reuse buffer named =* spacehammer-edit TITLE [SESSION-ID] *= 2. Set buffer-local variables: pid, title, session-id, selection-only 3. ~clipboard-yank~ — paste text from system clipboard into buffer 4. Enable ~spacehammer-edit-with-emacs-mode~ (minor mode) 5. ~pop-to-buffer~ — display the buffer 6. Run ~spacehammer-edit-with-emacs-hook~ (user customization point) ``` -------------------------------- ### Requirements Source: https://github.com/agzam/spacehammer/blob/master/docs/testing.org Prerequisites for using the Spacehammer testing library. ```APIDOC ## Requirements Before using the testing library, ensure you have installed the Hammerspoon IPC CLI by running the following command: ### Command ```lua hs.ipc.cliInstall() ``` This command can be pasted or evaluated directly in the Hammerspoon console. ``` -------------------------------- ### Configure Repeating Key Bindings Source: https://context7.com/agzam/spacehammer/llms.txt Defines key bindings that support repeating actions when held down, such as navigating browser tabs. ```fennel ;; Repeating key bindings (hold to repeat) (local chrome-keys [{:mods [:alt] :key :k :action "chrome:next-tab" :repeat true} {:mods [:alt] :key :j :action "chrome:prev-tab" :repeat true}]) ``` -------------------------------- ### Define :after-while Advice Source: https://github.com/agzam/spacehammer/blob/master/docs/advice.org Use :after-while to call the original function first. If its return value is truthy, the advising function is then called, and its return value is used. Otherwise, the advising function is not called. ```fennel (import-macros {: defn : defadvice} :lib.advice.macros) (defn original-fn [x y z] "docstr" true) (original-fn 1 2 3) ;; => true (defadvice advice-fn [x y z] :after-while original-fn "After-while original-fn" (+ x y z)) ``` -------------------------------- ### Emacs Module Functions and IPC Source: https://context7.com/agzam/spacehammer/llms.txt Lists available functions for the Emacs module and inter-process communication (IPC) functions callable from Emacs. ```fennel ;; Available emacs module functions: ;; "emacs:edit-with-emacs" - Edit current text field in Emacs ;; "emacs:capture" - Activate org-capture ;; "emacs:note" - Quick note capture ;; "emacs:full-screen" - Toggle Emacs fullscreen ;; "emacs:vertical-split-with-emacs" - Split screen with Emacs ;; "emacs:maximize" - Maximize Emacs window ``` ```fennel ;; IPC functions callable from Emacs via `hs -c`: ;; emacs.syncText(session-id, text) - Push text back without closing ;; emacs.finishSession(session-id, text) - Write final text and close ;; emacs.cancelSession(session-id) - Cancel without changes ;; emacs.getSessionInfo(session-id) - Get session metadata ``` -------------------------------- ### Auto-run Fennel Tests with nodemon Source: https://github.com/agzam/spacehammer/blob/master/docs/testing.org Use nodemon to automatically re-run Fennel tests when .fnl files change. A delay is included to allow Hammerspoon to restart. ```bash npx nodemon -e ".fnl" -x "./run-test" --delay 2 -- test/*.fnl ``` -------------------------------- ### Assert Equality with is.eq? Source: https://github.com/agzam/spacehammer/blob/master/docs/testing.org Asserts that the actual value is identical to the expected value. Throws an error with a custom message if they are not equal. ```fennel (is.eq? actual expected message) ``` ```fennel (is.eq? (+ 1 1) 2 "Math is wack") ``` -------------------------------- ### Define :after-until Advice Source: https://github.com/agzam/spacehammer/blob/master/docs/advice.org Use :after-until to call the original function first. If its return value is falsey, the advising function is then called, and its return value is used. This is the inverse of :after-while. ```fennel (import-macros {: defn : defadvice} :lib.advice.macros) (defn original-fn [x y z] "docstr" true) (original-fn 1 2 3) ;; => true (defadvice advice-fn [x y z] :after-until original-fn "After-until original-fn" (+ x y z)) ``` -------------------------------- ### Define :after Advice Source: https://github.com/agzam/spacehammer/blob/master/docs/advice.org Use :after to execute an advising function with the same arguments after the original function. The return value of the original function is discarded, and the return value of the advising function is returned. ```fennel (import-macros {: defn : defadvice} :lib.advice.macros) (defn original-fn [x y z] "docstr" (print (+ x y z))) (defadvice advice-fn [x y z] :after original-fn "After original-fn" (+ (- y x) z)) ``` -------------------------------- ### Defn Macro Transformation Source: https://github.com/agzam/spacehammer/blob/master/docs/advice.org Illustrates the internal transformation performed by the 'defn' macro when creating an advisable function. ```fennel (local defn-func-1 (let [adv_0_ (require "lib.advice")] (adv_0_.make-advisable "defn-func-2" (fn [x y z] "docstr" "hi")))) ``` -------------------------------- ### Configure Spacehammer Package in Doom Emacs (Elisp) Source: https://github.com/agzam/spacehammer/blob/master/docs/emacs.org Configures the Spacehammer Emacs package within Doom Emacs using a local repository. This method is preferred for maintaining compatibility. ```elisp (when (eq system-type 'darwin) (package! spacehammer :recipe (:local-repo "spacehammer" :files ("*.el")))) ``` -------------------------------- ### Override a Function with defadvice Source: https://github.com/agzam/spacehammer/blob/master/docs/advice.org Use defadvice to completely override an existing function. The advisor function replaces the target function's behavior. ```fennel (defadvice defn-func-override [x y z] :override defn-func-4 "Overrides defn-func-4" "over-it") ``` -------------------------------- ### Filter-Return Advice Behavior Source: https://github.com/agzam/spacehammer/blob/master/docs/advice.org Defines a 'filter-return' advice function that is called with the return value of the original function, allowing transformation of the result. ```fennel (fn [...] (advice-fn (original-fn (table.unpack [...])))) ``` -------------------------------- ### Advice Types Source: https://github.com/agzam/spacehammer/blob/master/docs/advice.org Details on different types of advice available, including override and around. ```APIDOC ## Advice Types ### override #### Description Replaces the target function entirely. The advice function receives the same arguments as the original function. #### Behavior ```fennel (fn [...] (advice-fn (table.unpack [...] ))) ``` #### Example ```fennel (defn original-fn [x y z] "docstr" "Hi") (defadvice advice-fn [x y z] :override original-fn "Overrides original-fn" "over-it") (original-fn) ;; => "over-it" ``` ### around #### Description Wraps the target function. The advice function receives the original function as the first argument, followed by the original function's arguments. This is the most versatile advice type. #### Behavior ```fennel (fn [...] (advice-fn original-function (table.unpack [...] ))) ``` #### Example ```fennel (defn original-fn [x y z] "docstr" "Good job,") (defadvice advice-fn [orig-fn x y z] :around original-fn "Wraps original-fn" ;; May call orig-fn anytime, maybe even more than once ``` ``` -------------------------------- ### Testing API - After Source: https://github.com/agzam/spacehammer/blob/master/docs/testing.org Runs a function once after all tests in a suite. ```APIDOC ## After Runs a function once after all tests in a `describe` suite. ### Usage ```fennel (describe "Functional Tools" (fn [] (after (fn [] (print "Perform post-test cleanup"))) (it "Should do math" (fn [] (is.eq? (+ 1 1) 2 "Did not result in 2"))))) ``` ### Description `after` is useful for cleaning up resources or resetting test state after all tests in a suite have completed. It runs only once per suite. ``` -------------------------------- ### Vim Mode Normal Mode Bindings Source: https://context7.com/agzam/spacehammer/llms.txt Lists common key bindings available in Vim mode's Normal mode, including movement, editing, and search commands. ```fennel ;; Normal mode bindings include: ;; h/j/k/l - Movement (left/down/up/right) ;; i/a - Enter insert mode (at cursor/after cursor) ;; I/A - Insert at line start/end ;; v/V - Visual mode (char/line) ;; 0/$ - Line start/end ;; w/b - Word forward/backward ;; x/s/c - Delete char / delete+insert / change ;; o/O - Open line below/above ;; u/Ctrl-r - Undo/redo ;; / - Search (Cmd+F) ;; Ctrl-hjkl - Jump between windows ``` -------------------------------- ### Functional Utilities for Collections and Strings Source: https://context7.com/agzam/spacehammer/llms.txt Utilize a collection of functional programming utilities for transforming and manipulating data. Includes map, filter, reduce, merge, concat, and string operations. ```fennel (local {:map map :filter filter :reduce reduce :merge merge :concat concat :get-in get-in :contains? contains? :find find :some some :join join :split split :compose compose} (require :lib.functional)) ;; Transform collections (map (fn [x] (* x 2)) [1 2 3]) ;; => [2 4 6] (filter (fn [x] (> x 2)) [1 2 3 4]) ;; => [3 4] (reduce (fn [acc v] (+ acc v)) 0 [1 2 3]) ;; => 6 ;; Merge tables (merge {:a 1} {:b 2} {:c 3}) ;; => {:a 1 :b 2 :c 3} ;; Concatenate lists (concat [1 2] [3 4] [5]) ;; => [1 2 3 4 5] ;; Deep property access (get-in [:modules :windows :center-ratio] config) ;; Check membership (contains? :foo [:foo :bar]) ;; => true ;; Find first match (find (fn [x] (= x.key :w)) items) ;; String utilities (split ":" "module:function") ;; => ["module" "function"] (join ", " ["a" "b" "c"]) ;; => "a, b, c" ``` -------------------------------- ### Clipboard Safety Net Source: https://github.com/agzam/spacehammer/blob/master/docs/edit-with-emacs-spec.org Ensures that every text transition is recorded in the system clipboard. Text is copied to the clipboard before being sent to the application, serving as a fallback. ```Emacs Lisp hs.pasteboard.setContents text ``` -------------------------------- ### Around Advice Behavior Source: https://github.com/agzam/spacehammer/blob/master/docs/advice.org Defines an 'around' advice function that wraps the original function, providing access to both the original function and its arguments. ```fennel (fn [...] (original-fn (table.unpack (advice-fn (table.unpack [...]))))) ``` -------------------------------- ### Testing API - Describe Source: https://github.com/agzam/spacehammer/blob/master/docs/testing.org Defines a test suite to group related tests. ```APIDOC ## Describe Labels a test suite. ### Usage ```fennel (describe "Functional Tools" (fn [] ;; Other describe calls or `it` tests can run here )) ``` ### Description `describe` organizes tests into suites. The function body can contain nested `describe` calls or `it` calls. Test results are indented under the describe label for clarity. ``` -------------------------------- ### Customize Spacehammer Editing Experience (Elisp) Source: https://github.com/agzam/spacehammer/blob/master/docs/emacs.org Customizes Spacehammer's editing behavior in Emacs, including setting up hooks and controlling the display of the edit buffer window. The edit buffer is configured to appear in a new window on the right, occupying 30% of the screen width. ```elisp (use-package! spacehammer :defer t :commands spacehammer-edit-with-emacs :config (add-hook! 'spacehammer-edit-with-emacs-hook #'spacehammer-edit-with-emacs-h) (add-hook! 'spacehammer-before-finish-edit-with-emacs-hook #'spacehammer-before-finish-edit-with-emacs-h) ;; control where the window for edit buffer appears (add-to-list 'display-buffer-alist '("\* spacehammer-edit.*" (display-buffer-reuse-window display-buffer-in-direction) (direction . right) (window . root) (window-width . 0.30)))) ```