527 lines
18 KiB
Org Mode
527 lines
18 KiB
Org Mode
#+title: Gists by bram85
|
|
#+PROPERTY: header-args :mkdirp yes
|
|
|
|
#+begin_src org :tangle README.org
|
|
Gists referred from [[https://emacs.ch/@bram85][@bram85@emacs.ch]].
|
|
|
|
Unfortunately Forgejo has no gist support and there's no mature yet lightweight gist solution suitable for self-hosting, so resorting to this poor-man's setup of sharing gists with an audience.
|
|
|
|
Drop me a DM on Mastodon in case of comments.
|
|
#+end_src
|
|
|
|
* Termux keys :termux:emacs:
|
|
:PROPERTIES:
|
|
:URL: https://emacs.ch/@bram85/109393570150138285
|
|
:END:
|
|
|
|
Put this in ~/.termux/termux.properties for a better Emacs experience inside Termux.
|
|
|
|
#+begin_src conf :tangle gists/termux-emacs-keys.conf
|
|
extra-keys = [[ \
|
|
{key: ESC, popup: {macro: "CTRL g", display: "C-g"}}, \
|
|
{key: CTRL, popup: {macro: "CTRL x", display: "C-x"}}, \
|
|
{key: ALT, popup: {macro: "ALT x", display: "M-x"}}, \
|
|
{key: TAB}, \
|
|
{key: DEL, popup: {macro: "CTRL k", display: C-k}}, \
|
|
{key: LEFT, popup: HOME}, \
|
|
{key: DOWN, popup: PGDN}, \
|
|
{key: UP, popup: PGUP}, \
|
|
{key: RIGHT, popup: END} \
|
|
]]
|
|
#+end_src
|
|
|
|
* Nesting with use-package :emacs:usepackage:
|
|
:PROPERTIES:
|
|
:URL: https://emacs.ch/@bram85/109393551314878399
|
|
:END:
|
|
|
|
#+begin_src elisp :tangle gists/use-package-nesting.el
|
|
;; Demonstrate that use-package allows for nesting configurations,
|
|
;; although it is done semantically rather than syntactically. The
|
|
;; :after keyword makes sure that the 'bar' package is only evaluated
|
|
;; after the 'foo' package was loaded.
|
|
|
|
(use-package foo)
|
|
|
|
;; only loads after foo has been loaded
|
|
(use-package bar
|
|
:after foo)
|
|
#+end_src
|
|
|
|
* Track usage of Emacs packages over time :emacs:org:orgbabel:
|
|
:PROPERTIES:
|
|
:URL: https://emacs.ch/@bram85/109403483724552863
|
|
:END:
|
|
|
|
Using the idea of evaluating code on state changes [[id:3be8333e-7a47-4251-8ee4-2cba0ec4614b][below]].
|
|
|
|
#+begin_src org :tangle gists/track-use-package-over-time.org
|
|
,* TODO Update Emacs package count
|
|
DEADLINE: <2023-03-15 Wed .+1m>
|
|
:PROPERTIES:
|
|
:LAST_REPEAT: [2023-02-15 Wed 08:12]
|
|
:ON_DONE: (org-babel-execute-subtree)
|
|
:END:
|
|
:LOGBOOK:
|
|
- State "DONE" from "TODO" [2023-02-15 Wed 08:12]
|
|
- State "DONE" from "TODO" [2022-11-26 Sat 07:57]
|
|
:END:
|
|
|
|
A small org file to keep track of some metric in a table, see [[https://orgmode.org/manual/Results-of-Evaluation.html][Results of Evaluation]] in the Org mode manual for more information. It's important that the function is supposed to return a list in order to record the result as a table row.
|
|
|
|
In this case, count the number of packages in my Emacs init file. It is executed automatically when the task is marked as done, using [[https://apps.bram85.nl/gitea/bram/gists/src/branch/main/gists/evaluate-code-on-task-state-change.org][this function]].
|
|
|
|
Press =C-c C-c= inside the code block to add a new entry to the table below.
|
|
|
|
,#+begin_src elisp :results table append
|
|
(with-temp-buffer
|
|
(insert-file-contents user-init-file)
|
|
(list (format-time-string "%F")
|
|
(count-matches (rx (seq "(" (? "elpaca-") "use-package" space)))))
|
|
,#+end_src
|
|
|
|
,#+RESULTS:
|
|
| 2022-11-25 | 140 |
|
|
| 2022-11-26 | 140 |
|
|
| 2023-02-15 | 138 |
|
|
#+end_src
|
|
|
|
* vertico-repeat setup :emacs:vertico:
|
|
:PROPERTIES:
|
|
:URL: https://emacs.ch/@bram85/109408577100294769
|
|
:END:
|
|
|
|
My vertico-repeat setup.
|
|
|
|
#+begin_src elisp :tangle gists/vertico-repeat.el
|
|
(use-package vertico-repeat
|
|
:straight (vertico-repeat :host github :repo "emacs-straight/vertico" :files ("extensions/vertico-repeat.el"))
|
|
:after (vertico savehist)
|
|
:bind
|
|
("M-g r" . vertico-repeat-select)
|
|
:config
|
|
(add-to-list 'savehist-additional-variables 'vertico-repeat-history)
|
|
:hook
|
|
(minibuffer-setup . vertico-repeat-save))
|
|
#+end_src
|
|
|
|
* Evaluate code on task state changes :emacs:org:
|
|
:PROPERTIES:
|
|
:ID: 3be8333e-7a47-4251-8ee4-2cba0ec4614b
|
|
:END:
|
|
|
|
#+begin_src org :tangle gists/evaluate-code-on-task-state-change.org
|
|
The function below evaluates Lisp forms stored in properties of a task, when it changes to a certain state. The function is supposed to be added to the =org-after-todo-state-change-hook=.
|
|
|
|
The property should be named =ON_<STATE>= where =STATE= is a state defined in =org-todo-keywords=. See also the example task below (open this file in [[https://apps.bram85.nl/gitea/bram/gists/raw/branch/main/gists/evaluate-code-on-task-state-change.org][raw mode]] to see the properties).
|
|
|
|
,#+begin_src elisp
|
|
(defun my/task-state-event-handler ()
|
|
"Evaluate a Lisp form attached to the task whose state is being changed.
|
|
|
|
When this function is added to org-after-todo-state-change-hook,
|
|
it looks for a Lisp form stored in the property called ON_<STATE>
|
|
where STATE is the new state of the todo item. When the state is
|
|
cleared, ON_CLEAR will be used.
|
|
|
|
Example:
|
|
|
|
,,* TODO Example task
|
|
:PROPERTIES:
|
|
:ON_PROGRESS: (message \"Busy!\")
|
|
:ON_DONE: (message \"Done!\")
|
|
:ON_CLEAR: (message \"No state.\")
|
|
:END:"
|
|
|
|
(when-let* ((state (or org-state "CLEAR"))
|
|
(event-property-name (concat "ON_" state))
|
|
(code-string (cdr (assoc event-property-name
|
|
(org-entry-properties))))
|
|
(code (car (read-from-string code-string))))
|
|
(org-eval code)))
|
|
|
|
(add-hook 'org-after-todo-state-change-hook 'my/task-state-event-handler)
|
|
,#+end_src
|
|
|
|
,* TODO Example task
|
|
:PROPERTIES:
|
|
:ON_PROGRESS: (message "Busy!")
|
|
:ON_DONE: (message "Done!")
|
|
:ON_CLEAR: (message "No state.")
|
|
:END:
|
|
#+end_src
|
|
|
|
* Emacs commenting: do what I actually mean :emacs:
|
|
|
|
#+begin_src elisp :tangle gists/emacs-comments-do-what-i-actually-mean.el
|
|
;; Replace comment-dwim (M-;) with a modified version of
|
|
;; comment-or-uncomment-region. The modification is done by the crux
|
|
;; [1] package, which acts on a region if active otherwise on the
|
|
;; current line.
|
|
;;
|
|
;; My personal preference goes to using straight.el-powered
|
|
;; use-package forms, even for built-in modules.
|
|
;;
|
|
;; [1] https://github.com/bbatsov/crux
|
|
|
|
(use-package newcomment
|
|
:straight (:type built-in)
|
|
:bind
|
|
([remap comment-dwim] . #'comment-or-uncomment-region))
|
|
|
|
(use-package crux
|
|
:config
|
|
(crux-with-region-or-line comment-or-uncomment-region))
|
|
#+end_src
|
|
|
|
* Use xr for more readable regular expressions :emacs:
|
|
|
|
#+begin_src org :tangle gists/xr-for-readable-regular-expressions.org
|
|
If you look for the value of =org-link-any-re=, you'll see this beauty:
|
|
|
|
,#+begin_example
|
|
"\\(\\[\\[\\(\\(?:[^][\\]\\|\\\\\\(?:\\\\\\\\\\)*[][]\\|\\\\+[^][]\\)+\\)]\\(?:\\[\\([^z-a]+?\\)]\\)?]\\)\\|\\(<\\(bibtex\\|elisp\\|f\\(?:ile\\(?:\\+\\(?:\\(?:emac\\|sy\\)s\\)\\)?\\|tp\\)\\|h\\(?:elp\\|ttps?\\)\\|id\\|mai\\(?:lto\\|rix\\)\\|news\\|org-ql-search\\|shell\\):\\([^>
|
|
]*\\(?:
|
|
[ ]*[^>
|
|
][^>
|
|
]*\\)*\\)>\\)\\|\\(\\(?:\\<\\(?:\\(bibtex\\|elisp\\|f\\(?:ile\\(?:\\+\\(?:\\(?:emac\\|sy\\)s\\)\\)?\\|tp\\)\\|h\\(?:elp\\|ttps?\\)\\|id\\|mai\\(?:lto\\|rix\\)\\|news\\|org-ql-search\\|shell\\)\\):\\(\\(?:[^][
|
|
()<>]\\|(\\(?:[^][
|
|
()<>]\\|([^][
|
|
()<>]*)\\)*)\\)+\\(?:[^[:punct:]
|
|
]\\|/\\|(\\(?:[^][
|
|
()<>]\\|([^][
|
|
()<>]*)\\)*)\\)\\)\\)\\)"
|
|
,#+end_example
|
|
|
|
[[https://github.com/mattiase/xr][xr]] to the rescue:
|
|
|
|
,#+begin_src elisp
|
|
(pp-to-string (xr org-link-any-re))
|
|
,#+end_src
|
|
|
|
,#+RESULTS:
|
|
,#+begin_example
|
|
(or
|
|
(group "[["
|
|
(group
|
|
(one-or-more
|
|
(or
|
|
(not
|
|
(any "[\\]"))
|
|
(seq "\\"
|
|
(zero-or-more "\\\\")
|
|
(any "[]"))
|
|
(seq
|
|
(one-or-more "\\")
|
|
(not
|
|
(any "[]"))))))
|
|
"]"
|
|
(opt "["
|
|
(group
|
|
(+\? anything))
|
|
"]")
|
|
"]")
|
|
(group "<"
|
|
(group
|
|
(or "bibtex" "elisp"
|
|
(seq "f"
|
|
(or
|
|
(seq "ile"
|
|
(opt "+"
|
|
(or "emac" "sy")
|
|
"s"))
|
|
"tp"))
|
|
(seq "h"
|
|
(or "elp"
|
|
(seq "ttp"
|
|
(opt "s"))))
|
|
"id"
|
|
(seq "mai"
|
|
(or "lto" "rix"))
|
|
"news" "org-ql-search" "shell"))
|
|
":"
|
|
(group
|
|
(zero-or-more
|
|
(not
|
|
(any "\n>")))
|
|
(zero-or-more "\n"
|
|
(zero-or-more
|
|
(any " "))
|
|
(not
|
|
(any " \n >"))
|
|
(zero-or-more
|
|
(not
|
|
(any "\n>")))))
|
|
">")
|
|
(group bow
|
|
(group
|
|
(or "bibtex" "elisp"
|
|
(seq "f"
|
|
(or
|
|
(seq "ile"
|
|
(opt "+"
|
|
(or "emac" "sy")
|
|
"s"))
|
|
"tp"))
|
|
(seq "h"
|
|
(or "elp"
|
|
(seq "ttp"
|
|
(opt "s"))))
|
|
"id"
|
|
(seq "mai"
|
|
(or "lto" "rix"))
|
|
"news" "org-ql-search" "shell"))
|
|
":"
|
|
(group
|
|
(one-or-more
|
|
(or
|
|
(not
|
|
(any " \n ()<>[]"))
|
|
(seq "("
|
|
(zero-or-more
|
|
(or
|
|
(not
|
|
(any " \n ()<>[]"))
|
|
(seq "("
|
|
(zero-or-more
|
|
(not
|
|
(any " \n ()<>[]")))
|
|
")")))
|
|
")")))
|
|
(or
|
|
(not
|
|
(any " \n " punct))
|
|
"/"
|
|
(seq "("
|
|
(zero-or-more
|
|
(or
|
|
(not
|
|
(any " \n ()<>[]"))
|
|
(seq "("
|
|
(zero-or-more
|
|
(not
|
|
(any " \n ()<>[]")))
|
|
")")))
|
|
")")))))
|
|
,#+end_example
|
|
#+end_src
|
|
|
|
* Define a lambda in a let expression :elisp:emacs:
|
|
|
|
#+begin_src elisp :tangle gists/let-lambda.el
|
|
;; https://stackoverflow.com/questions/36039840/elisp-bind-a-lambda-in-a-let-and-execute-it
|
|
|
|
(let ((f (lambda (s) (message s))))
|
|
;; f is a variable so should be treated as such.
|
|
(funcall f "This works.")
|
|
|
|
;; f is not a function so cannot be found if called like this.
|
|
(f "This does not work."))
|
|
#+end_src
|
|
|
|
* Use outline-minor-mode with eshell :emacs:eshell:
|
|
|
|
Found [[https://www.reddit.com/r/emacs/comments/e2u5n9/comment/f918t22/][a Reddit comment]] suggesting to assign the prompt regexp to the outline regexp. Here's the corresponding =use-package= syntax:
|
|
|
|
#+begin_src elisp :tangle gists/outline-minor-mode-eshell.el
|
|
;; use C-c @ C-t to get a foldable shell history
|
|
(use-package eshell
|
|
:config
|
|
(add-hook 'eshell-mode-hook
|
|
(lambda () (setq-local outline-regexp eshell-prompt-regexp)))
|
|
:hook
|
|
(eshell-mode . outline-minor-mode))
|
|
#+end_src
|
|
|
|
* Automatically reformat source blocks in Org Mode :org:orgbabel:emacs:
|
|
|
|
#+begin_src elisp :tangle gists/format-org-mode-source-blocks.el
|
|
;; https://github.com/lassik/emacs-format-all-the-code
|
|
(use-package format-all)
|
|
|
|
(use-package org
|
|
:config
|
|
(defun my/format-all-advice ()
|
|
(ignore-errors ; in case there's no language support
|
|
(call-interactively #'format-all-buffer)))
|
|
|
|
(advice-add #'org-edit-src-exit :before #'my/format-all-advice))
|
|
#+end_src
|
|
|
|
* Paredit inside minibuffer commands :emacs:
|
|
|
|
Used [[https://github.com/purcell/emacs.d/blob/master/lisp/init-paredit.el][Purcell's configation]] as a starting point.
|
|
|
|
It turns out it didn't work as well as I hoped, paredit steals the RET binding so a newline is inserted in the minibuffer, instead of submitting the input.
|
|
|
|
#+name: minibuffer-paredit
|
|
#+begin_src elisp :tangle gists/minibuffer-paredit.el
|
|
(use-package paredit
|
|
:hook
|
|
(minibuffer-setup . my/conditionally-enable-paredit-mode)
|
|
(minibuffer-exit . my/restore-paredit-key)
|
|
:config
|
|
(defvar my/paredit-minibuffer-commands '(eval-expression
|
|
pp-eval-expression
|
|
eval-expression-with-eldoc
|
|
ibuffer-do-eval
|
|
ibuffer-do-view-and-eval
|
|
org-ql-sparse-tree
|
|
org-ql-search)
|
|
"Interactive commands for which paredit should be enabled in the minibuffer.")
|
|
|
|
(defun my/conditionally-enable-paredit-mode ()
|
|
"Enable paredit during lisp-related minibuffer commands."
|
|
(when (memq this-command my/paredit-minibuffer-commands)
|
|
(enable-paredit-mode)
|
|
(unbind-key (kbd "RET") paredit-mode-map)))
|
|
|
|
(defun my/restore-paredit-key ()
|
|
"Restore the RET binding that was disabled by
|
|
my/conditionally-enable-paredit-mode."
|
|
(bind-key (kbd "RET") #'paredit-newline paredit-mode-map)))
|
|
#+end_src
|
|
|
|
* Store revision of Emacs configuration in a constant :emacs:
|
|
|
|
#+begin_src elisp :tangle gists/store-configuration-revision-in-constant.el
|
|
;; Store the Git revision of your Emacs configuration at the moment
|
|
;; Emacs started.
|
|
|
|
(defconst my/has-git-p (if (executable-find "git") t nil)
|
|
"Indicates if git is in your PATH.")
|
|
|
|
(defun my/configuration-revision ()
|
|
"Retrieve the current Git revision of the Emacs configuration."
|
|
(when my/has-git-p
|
|
(with-temp-buffer
|
|
(cd user-emacs-directory)
|
|
(when (eql 0 (call-process "git" nil (current-buffer) nil
|
|
"describe" "--all" "--long" "--dirty"))
|
|
(string-trim (buffer-string))))))
|
|
|
|
(defconst my/configuration-revision
|
|
(my/configuration-revision)
|
|
"Contains the Git revision of the configuration at the moment Emacs started.")
|
|
#+end_src
|
|
|
|
|
|
* Use Tempel to insert links in Org-Mode :emacs:tempel:org:
|
|
|
|
#+begin_src elisp :tangle gists/tempel-org-links.el
|
|
;;; Introduction
|
|
;;
|
|
;; In my Org notes I often refer to terms like JIRA tickets or some
|
|
;; queries to an (internal) search engine. Doing it natively with Org
|
|
;; mode is a bit involved:
|
|
;;
|
|
;; 1. org-insert-link
|
|
;; 2. Type link, e.g. "JIRA:MYPRJ-1234"
|
|
;; 3. Type the visible description again: "MYPRJ-1234".
|
|
;;
|
|
;; The repetition is a bit cumbersome and a template to insert the
|
|
;; link would be more convenient. And sometimes I already typed the
|
|
;; ticket number and I want to turn it into a link quickly.
|
|
;;
|
|
;; The code below adds a element which can be used in Tempel templates
|
|
;; to insert [[org:links][links]]. It works with and without active
|
|
;; region. If no region is active, a term is prompted in the
|
|
;; minibuffer.
|
|
;;
|
|
;; The following template:
|
|
;;
|
|
;; (jira (ol "JIRA"))
|
|
;;
|
|
;; results in [[JIRA:ticket][ticket]] where ticket is prompted or
|
|
;; taken from the region.
|
|
;;
|
|
;; This relies on having link types configured in your
|
|
;; `org-link-abbrev-alist', the link type accepts one parameter
|
|
;; matching a link type.
|
|
;;
|
|
;;; Requirements / why na(t)ive templates don't work
|
|
;;
|
|
;; The template has to meet the following requirements:
|
|
;;
|
|
;; - type the search term / ID only once
|
|
;; - or, use the region instead, if active
|
|
;; - no visible prefixes (link abbreviations)
|
|
;;
|
|
;; We need to store the region before the templating kicks
|
|
;; in, because the region can be used only once in a template. For
|
|
;; example, this doesn't work:
|
|
;;
|
|
;; (jira "[[JIRA:" r "][" r "]]")
|
|
;;
|
|
;; (and it's a region-only template, so another template would be
|
|
;; needed for a prompt).
|
|
;;
|
|
;; Also, a prompting variant doesn't work due to a bug in Org mode
|
|
;; which makes the element cache trip:
|
|
;;
|
|
;; (jira "[[JIRA:" (p "Ticket: " ticket) "][" (s ticket) "]]")
|
|
;;
|
|
;; Unfortunately, this not-so-elegant code is needed to meet the
|
|
;; requirements.
|
|
|
|
(defun my/tempel-org-link (elt)
|
|
"Tempel field to insert an org link."
|
|
|
|
(when (eq (car-safe elt) 'ol)
|
|
(let ((link-type (cadr elt)))
|
|
(if (and (boundp 'my/region) my/region)
|
|
;; consume region with r and use the stored region
|
|
;; afterwards, as r can be used only once
|
|
`(l "[[" ,link-type ":" r "][" ,my/region "]]")
|
|
|
|
;; NOINSERT is used for the prompt because the org-mode
|
|
;; element cache doesn't handle simultaneous edits well. So
|
|
;; prompt in minibuffer and insert the result twice.
|
|
`(l "[[" ,link-type ":" (p "Term: " term t) term "][" term "]]")))))
|
|
|
|
(defun my/store-region (f &rest args)
|
|
"Store the region in the my/region variable."
|
|
|
|
(setq my/region (when (use-region-p)
|
|
(buffer-substring (region-beginning) (region-end))))
|
|
(apply f args)
|
|
(setq my/region nil))
|
|
|
|
(add-to-list 'tempel-user-elements #'my/tempel-org-link)
|
|
(advice-add #'tempel-insert :around #'my/store-region)
|
|
#+end_src
|
|
|
|
* Meta
|
|
** License
|
|
|
|
#+begin_src org :tangle LICENSE.txt
|
|
MIT License
|
|
|
|
Copyright (c) 2022 Bram Schoenmakers
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
SOFTWARE.
|
|
#+end_src
|
|
|
|
** COMMENT Local variables
|
|
|
|
Auto tangle this file on save.
|
|
|
|
Local variables:
|
|
eval: (add-hook 'after-save-hook #'org-babel-tangle 0 t)
|
|
End:
|