;;; Execute things in Org presentations ;; https://www.howardism.org/Technical/Emacs/demonstrations-part-two.html (defun demo-match (triggers state) "Return t if all elements of TRIGGERS are in STATE. Where TRIGGERS and STATE are lists of key/value tuple pairs, e.g. `((:a 1) (:b 2))'." ;; If difference returns anything, we've failed: (not (seq-difference triggers state))) (defvar demo-prev-state (make-hash-table :test 'equal) "Matched states in keys, and store number of matches as values.") (defun demo-state-match (triggers state) "Return non-nil if STATE contains all TRIGGERS. The state also includes the number of times the triggers matched during previous calls. We do this by keeping track of the number of successful calls, and incrementing the iteration... if this function returns non-nil." ;; If the first element is either parameter is NOT a list, ;; we group it into a list of tuples: (when (not (listp (car triggers))) (setq triggers (seq-partition triggers 2))) (when (not (listp (car state))) (setq state (seq-partition state 2))) (let* ((iteration (gethash state demo-prev-state 0)) (itful-state (cons `(:i ,iteration) state))) (when (demo-match triggers itful-state) (puthash state (1+ iteration) demo-prev-state)))) (defmacro demo-step (&rest forms) "Execute a function based matching list of states at point. Where FORMS is an even number of _matcher_ and _function_ to call. Probably best to explain this in an example: (demo-step (:buffer \"demonstrations.py\") (message \"In a buffer\") (:mode 'dired-mode) (message \"In a dired\") (:head \"Raven Civilizations\" (message \"In an org file\"))) Calling this function displays a message based on position of the point in a particular buffer or place in a heading in an Org file. You can use the `:i' to specify different forms to call when the trigger matches the first time, versus the second time, etc. (demo-step (:buffer \"demonstrations.org\" :i 0) (message \"First time\") (:buffer \"demonstrations.org\" :i 1) (message \"Second time\"))" (interactive) `(let ((state (list :buffer (buffer-name) :mode major-mode :head (when (eq major-mode 'org-mode) (org-get-heading))))) (cond ,@(seq-map (lambda (tf-pair) (seq-let (trigger func) tf-pair (list `(demo-state-match ',trigger state) func))) (seq-partition forms 2))))) (provide 'org-demo-macro)