Jeremy Friesen: Whipping Up a Quick Emacs Helper Function for Hugo

Continuing to Build Out Utility Functions

I’ve previously written about Emacs Function to Rename Hugo Blog Post and since then, I’ve added more functions. Someday, I’ll get around to sharing more of them. They’re almost ready to packaged up, but I haven’t spent the mental cycles thinking what’s in the package and what’s my local needs.

I was thinking about my process for finding the Hugo 🔍 file associated with a blog post.

The specific situation was that I wanted to update Ever Further Refinements of Org Roam Usage to include a reference to the follow-up post Diving into the Implementation of Subject Menus for Org Roam.

I had the URL for the post I wanted to update. I also had some existing functions that I’d written to help me find all of the drafts in my Hugo repository.

Code for finding a Hugo file based on a URL.

These constants and functions were things I'd previously written.

Note: This implementation assumes you are using the f package and have installed ripgrep, which is aliased as rg in the command shell.


(defconst jnf/tor-home-directory
  (file-truename "~/git/takeonrules.github.io")
  "The home directory of TakeOnRules.com Hugo repository.")

(defconst jnf/tor-hostname-regexp
  "https?://takeonrules\.com"
  "A regular expression for checking if it's TakeOnRules.com.")

(cl-defun jnf/tor-prompt-or-kill-ring-for-url (&key
                                               (url-regexp "^https?://"))
  "Prompt and return a url.

If the \`car' of \`kill-ring' matches the URL-REGEXP, default the
prompt value to the \`car' of `kill-ring'."
  (let ((car-of-kill-ring (substring-no-properties (car kill-ring))))
    (read-string "URL (optional): "
                 (when (string-match url-regexp car-of-kill-ring)
                   car-of-kill-ring))))

(cl-defun jnf/list-filenames-with-file-text (&key matching in)
  "Build a list of filenames MATCHING IN the given directory."
  (let ((default-directory (f-join jnf/tor-home-directory in)))
    (split-string-and-unquote
     (shell-command-to-string
      (concat
       "rg \""
       matching "\" --only-matching --files-with-matches "
       "| sort | tr '\n' '~'"))
     "~")))

They provided the bits and pieces for crafting jnf/tor-find-hugo-file-by-url, the function that prompts for a URL and finds the associated Hugo file.


(cl-defun jnf/tor-find-hugo-file-by-url (url)
  "Find the associated TakeOnRules.com file for the given URL."
  (interactive (list
                (jnf/tor-prompt-or-kill-ring-for-url
                 :url-regexp jnf/tor-hostname-regexp)))
  ;; With the given URL extract the slug
  (let* ((slug (car (last (split-string-and-unquote url "/"))))
         (filename (car
                    (jnf/list-filenames-with-file-text
                     :matching (concat "^slug: .*" slug "$")
                     :in "content"))))
    (find-file (f-join
                jnf/tor-home-directory
                "content"
                filename))))

Conclusion

With the above Elisp 🔍, I can now use M-x jnf/tor-find-hugo-file-by-url, type (or paste) the URL into the prompt, and Emacs 🔍 will open the corresponding blog post.

This does require that all of my blog posts have a slug frontmatter entry. This function does not work for non-blog post pages on my site. They have a different frontmatter structure.

To handle both pages and posts, I’m going to need to introduce some switching logic. But I don’t yet need it, so I’ll hold off.

-1:-- Whipping Up a Quick Emacs Helper Function for Hugo (Post Jeremy Friesen (jeremy@takeonrules.com))--L0--C0--August 28, 2021 04:46 PM

Jeremy Friesen: Adding More Tag Rendering Functions for SHR in Emacs

Adding More Default Styles of Browsers

As I’ve been using Emacs 🔍, I’m favoring the Emacs Web Wowser (EWW 🔍). The rendering logic uses the Simple HTML Renderer (SHR 🔍) package. Both EWW and SHR are part of the core Emacs distribution).

I like the experience of reading blogs via a text-based browser. I also like eschewing Cascading Stylesheet 🔍 and Javascript 🔍 from websites.

However, as I was writing a new blog post, and previewing it with EWW, I noticed that some of the HTML tags I use didn’t render as I would’ve thought. I spent some time reading through the SHR source code to get clearer sense of defaults.

I then took inspiration from some of the other rendering functions for my favorite Emacs Web Wowser. These options align with many of the default user agent style sheets.

Each browser has a default stylesheet. You can find an excellent list at Jens Oliver Meiert’s User Agent Style Sheets: Basics and Samples.

I use the following tags throughout Take on Rules:

And the base SHR does not have corresponding shr-tag- functions for them.

Here Is the Code for the Tags

;; Inspired from shr-tag-em
(defun shr-tag-dfn (dom)
  (shr-fontize-dom dom 'italic))

;; Inspired from shr-tag-em
(defun shr-tag-cite (dom)
  (shr-fontize-dom dom 'italic))

;; Inspired from shr-tag-a
(defun shr-tag-q (dom)
  (shr-insert "“")
  (shr-generic dom)
  (shr-insert "”"))

;; Drawing inspiration from shr-tag-h1
(defun shr-tag-small (dom)
  (shr-fontize-dom
   dom (if shr-use-fonts '(variable-pitch (:height 0.8)))))

;; Drawing inspiration from shr-tag-abbr
(defun shr-tag-time (dom)
  (when-let* ((datetime (or
                         (dom-attr dom 'title)
                         (dom-attr dom 'datetime)))
	      (start (point)))
    (shr-generic dom)
    (shr-add-font start (point) 'shr-abbreviation)
    (add-text-properties
     start (point)
     (list
      'help-echo datetime
      'mouse-face 'highlight))))

Conclusion and Next Steps

I added the above functions to my init.el file; These little tweaks improve my already fantastic EWW browsing experience.

I was also thinking it would be nice if I could get Imenu to render the headings of HTML pages. But that’s something for another time.

-1:-- Adding More Tag Rendering Functions for SHR in Emacs (Post Jeremy Friesen (jeremy@takeonrules.com))--L0--C0--August 26, 2021 11:57 AM

Jeremy Friesen: Diving into the Implementation of Subject Menus for Org Roam

It's Macros, Functions, and Property Lists…Oh My!

I wrote Ever Further Refinements of Org Roam Usage. In that post I talked about what I was implementing and why. I’m writing about the implementation details.

After writing Ever Further Refinements of Org Roam Usage, I spent a bit of time refactoring the code. I put that code up as a gist on Github. You can see the history of the refactoring, albeit without comments.

One result of the refactoring is that the menus now look a bit different. But the principle remain the same.

The Lists to Define Subjects

First, let’s start with the jnf/org-roam-capture-templates-plist. I created a Property List, or plist, for all of my org-roam templates.

Property list jnf/org-roam-capture-templates-plist implementation

(setq jnf/org-roam-capture-templates-plist
      (list
       :hesburgh-libraries
       '("h" "Hesburgh Libraries" plain "%?"
	 :if-new
         (file+head
          "hesburgh-libraries/%<%Y%m%d>---${slug}.org"
          "#+title: ${title}\n#+FILETAGS: :hesburgh: %^G\n\n")
	 :unnarrowed t)
       :jf-consulting
       '("j" "JF Consulting" plain "%?"
	 :if-new
         (file+head
          "jeremy-friesen-consulting/%<%Y%m%d>---${slug}.org"
          "#+title: ${title}\n#+FILETAGS: :personal:jeremy-friesen-consulting: %^G\n\n")
	 :unnarrowed t)
       :personal
       '("p" "Personal" plain "%?"
	 :if-new
         (file+head
          "personal/%<%Y%m%d>---${slug}.org"
	  "#+title: ${title}\n#+FILETAGS: :personal: %^G\n\n")
	 :unnarrowed t)
       :personal-encrypted
       '("P" "Personal (Encrypted)" plain "%?"
	 :if-new
         (file+head
          "personal/%<%Y%m%d>---${slug}.org.gpg"
          "#+title: ${title}\n#+FILETAGS: :personal:encrypted: %^G\n\n")
	 :unnarrowed t)
       :public
       '("u" "Public" plain "%?"
	 :if-new
         (file+head
          "public/%<%Y%m%d>---${slug}.org"
	  "#+title: ${title}\n#+FILETAGS: :public: %^G\n\n")
	 :unnarrowed t)
       :thel-sector
       '("t" "Thel Sector" plain "%?"
         :if-new
         (file+head
          "personal/thel-sector/%<%Y%m%d>---${slug}.org"
          "#+title: ${title}\n#+FILETAGS: :thel-sector: %^G\n\n")
         :unnarrowed t)
       ))

With the above, I have a symbolic name for each template. I can then use lookup functions to retrieve the implementation details.

I then created a plist for the subjects (e.g., jnf/org-roam-capture-subjects-plist). Each subject is itself a plist.

Property list jnf/org-roam-capture-subjects-plist implementation

(setq jnf/org-roam-capture-subjects-plist
      (list
       ;; The :all subject is different from the other items.
       :all (list
             ;; Iterate through all registered capture templates and
             ;; generate a list
             :templates (-non-nil (seq-map-indexed (lambda (template index)
                     (when (evenp index) template))
                   jnf/org-roam-capture-templates-plist))
             :name "all"
             :title "All"
             :group "All"
             :prefix "a"
             :path-to-todo "~/git/org/todo.org")
       :jf-consulting (list
                       :templates (list :jf-consulting)
                       :name "jf-consulting"
                       :title "JF Consulting"
                       :group "Projects"
                       :prefix "j"
                       :path-to-todo "~/git/org/jeremy-friesen-consulting/todo.org")
       :hesburgh-libraries (list
                            :templates (list :hesburgh-libraries)
                            :name "hesburgh-libraries"
                            :title "Hesburgh Libraries"
                            :group "Projects"
                            :prefix "h"
                            :path-to-todo "~/git/org/hesburgh-libraries/todo.org")
       :personal (list
                  :templates (list :personal :personal-encrypted)
                  :name "personal"
                  :title "Personal"
                  :group "Life"
                  :prefix "p"
                  :path-to-todo "~/git/org/personal/todo.org")
       :public (list
                :templates (list :public)
                :name "public"
                :title "Public"
                :group "Life"
                :prefix "u"
                :path-to-todo "~/git/org/public/todo.org")
       :thel-sector (list
                     :templates (list :thel-sector)
                     :name "thel-sector"
                     :title "Thel Sector"
                     :group "Projects"
                     :prefix "t"
                     :path-to-todo "~/git/org/personal/thel-sector/todo.org")
       ))

The jnf/org-roam-capture-subjects-plist plist contains the various org-roam subjects. Each subject is a plist with the following properties:

:templates
A list of named templates available for this subject. See jnf/org-roam-capture-templates-plist for list of valid templates.
:name
A string version of the subject, suitable for creating function names.
:title
The human readable "title-case" form of the subject.
:group
Used for appending to the "All" menu via pretty-hydra-define+.
:prefix
Used for the prefix key when mapping functions to key bindings for pretty-hydra-define+.
:path-to-todo
The path to the todo file for this subject.

Functions to Help Build the Hydra Menus

I wrote the jnf/org-roam-templates-for-subject function to retrieve a subject’s Org-roam 🔍 templates.

Function jnf/org-roam-templates-for-subject implementation

(cl-defun jnf/org-roam-templates-for-subject (subject
                                              &key
                                              (subjects-plist jnf/org-roam-capture-subjects-plist)
                                              (template-definitions-plist jnf/org-roam-capture-templates-plist))
  "Return a list of \`org-roam' templates for the given SUBJECT.

Use the given (or default) SUBJECTS-PLIST to fetch from the
given (or default) TEMPLATE-DEFINITIONS-PLIST."
  (let ((templates (plist-get (plist-get subjects-plist subject) :templates)))
    (-map (lambda (template) (plist-get template-definitions-plist template))
          templates)))

I then created jnf/org-subject-menu–all, a pretty-hydra-define menu.

Pretty-hydra-define jnf/org-subject-menu--all implementation

(defvar jnf/org-subject-menu--title (with-faicon "book" "Org Subject Menu" 1 -0.05))
(pretty-hydra-define jnf/org-subject-menu--all (:foreign-keys warn :title jnf/org-subject-menu--title :quit-key "q" :exit t)
  (
   ;; Note: This matches at least one of the :groups in \`jnf/org-roam-capture-subjects-plist'
   "Personal / Public"
   ()
   ;; Note: This matches at least one of the :groups in \`jnf/org-roam-capture-subjects-plist'
   "Projects"
   ()
   "Org Mode"
   (("@" (lambda ()
           (interactive)
           (find-file (file-truename (plist-get (plist-get jnf/org-roam-capture-subjects-plist :all) :path-to-todo))))
     "Todo…")
    ("+" jnf/org-roam--all--capture     "Capture…")
    ("!" jnf/org-roam--all--node-insert " ├─ Insert…")
    ("?" jnf/org-roam--all--node-find   " └─ Find…")
    ("/" org-roam-buffer-toggle         "Toggle Buffer")
    ("#" jnf/toggle-roam-subject-filter "Toggle Default Filter")
    )))

The jnf/org-subject-menu–all frames out the menu structure. The menu has three columns: “Personal / Public”, “Projects”, and “Org Mode”. The “Personal / Public” and “Projects” are the two named groups I assigned each subject in the jnf/org-roam-capture-subjects-plist.

In the above implementation, they start as empty lists. But as we move down the implementation, we’ll append the subjects to those empty lists.

The Macro That Populates the Hydra Menu

Now we get to the create-org-roam-subject-fns-for macro that does the heavy lifting.

Macro create-org-roam-subject-fns-for impelementation.

(cl-defmacro create-org-roam-subject-fns-for (subject
                                              &key
                                              (subjects-plist jnf/org-roam-capture-subjects-plist))
  "Define the org roam SUBJECT functions and create & update hydra menus.

The functions are wrappers for `org-roam-capture', `org-roam-node-find', `org-roam-node-insert', and `find-file'.

Create a subject specific `pretty-define-hydra' and append to the `jnf/org-subject-menu–all' hydra via the `pretty-define-hydra+' macro.

Fetch the given SUBJECT from the given SUBJECTS-PLIST." (let* ((subject-plist (plist-get subjects-plist subject)) (subject-as-symbol subject) (subject-title (plist-get subject-plist :title)) (subject-name (plist-get subject-plist :name))

     ;; For todo related antics
     (todo-fn-name (intern (concat "jnf/find-file--" subject-name "--todo")))
     (path-to-todo (plist-get subject-plist :path-to-todo))
     (todo-docstring (concat "Find the todo file for " subject-name " subject."))

     ;; For hydra menu related antics
     (hydra-fn-name (intern (concat "jnf/org-subject-menu--" subject-name)))
     (hydra-menu-title (concat subject-title " Subject Menu"))
     (hydra-todo-title (concat subject-title " Todo…"))
     (hydra-group (plist-get subject-plist :group))
     (hydra-prefix (plist-get subject-plist :prefix))
     (hydra-kbd-prefix-todo    (concat hydra-prefix " @"))
     (hydra-kbd-prefix-capture (concat hydra-prefix " +"))
     (hydra-kbd-prefix-insert  (concat hydra-prefix " !"))
     (hydra-kbd-prefix-find    (concat hydra-prefix " ?"))

     ;; For \`org-roam-capture' related antics
     (capture-fn-name (intern (concat "jnf/org-roam--" subject-name "--capture")))
     (capture-docstring (concat "As \`org-roam-capture' but scoped to " subject-name
                        ".\n\nArguments GOTO and KEYS see \`org-capture'."))

     ;; For \`org-roam-insert-node' related antics
     (insert-fn-name (intern (concat "jnf/org-roam--" subject-name "--node-insert")))
     (insert-docstring (concat "As \`org-roam-insert-node' but scoped to " subject-name " subject."))

     ;; For \`org-roam-find-node' related antics
     (find-fn-name (intern (concat "jnf/org-roam--" subject-name "--node-find")))
     (find-docstring (concat "As \`org-roam-find-node' but scoped to "
                        subject-name " subject."
                        "\n\nArguments INITIAL-INPUT and OTHER-WINDOW are from \`org-roam-find-mode'."))
     )
\`(progn
   (defun ,todo-fn-name ()
     ,todo-docstring
     (interactive)
     (find-file (file-truename ,path-to-todo)))

   (defun ,capture-fn-name (&optional goto keys)
     ,capture-docstring
     (interactive "P")
     (org-roam-capture goto
                       keys
                       :filter-fn (lambda (node) (-contains-p (org-roam-node-tags node) ,subject-name))
                       :templates (jnf/org-roam-templates-for-subject ,subject-as-symbol)))
   (defun ,insert-fn-name ()
     ,insert-docstring
     (interactive)
     (org-roam-node-insert (lambda (node) (-contains-p (org-roam-node-tags node) ,subject-name))
                           :templates (jnf/org-roam-templates-for-subject ,subject-as-symbol)))

   (defun ,find-fn-name (&optional other-window initial-input)
     ,find-docstring
     (interactive current-prefix-arg)
     (org-roam-node-find other-window
                         initial-input
                         (lambda (node) (-contains-p (org-roam-node-tags node) ,subject-name))
                         :templates (jnf/org-roam-templates-for-subject ,subject-as-symbol)))

   ;; Create a hydra menu for the given subject
   (pretty-hydra-define ,hydra-fn-name (:foreign-keys warn :title jnf/org-subject-menu--title :quit-key "q" :exit t)
     (
      ,hydra-menu-title
      (
       ("@" ,todo-fn-name        ,hydra-todo-title)
       ("+" ,capture-fn-name     " ├─ Capture…")
       ("!" ,insert-fn-name      " ├─ Insert…")
       ("?" ,find-fn-name        " └─ Find…")
       ("/" org-roam-buffer-toggle            "Toggle Buffer")
       ("#" jnf/toggle-roam-subject-filter    "Toggle Filter…")
       )))

   ;; Append the following menu items to the \`jnf/org-subject-menu--all'
   (pretty-hydra-define+ jnf/org-subject-menu--all()
     (,hydra-group
      (
       (,hydra-kbd-prefix-todo    ,todo-fn-name    ,hydra-todo-title)
       (,hydra-kbd-prefix-capture ,capture-fn-name " ├─ Capture…")
       (,hydra-kbd-prefix-insert  ,insert-fn-name  " ├─ Insert…")
       (,hydra-kbd-prefix-find    ,find-fn-name    " └─ Find…")
       )))
   )))

The create-org-roam-subject-fns-for macro does six things for the given subject:

  1. Creates a function to find-file of the subject’s todo.
  2. Creates a subject specific capture function that wraps org-roam-capture.
  3. Creates a subject specific insert function that wraps org-roam-node-insert.
  4. Creates a subject specific find function that wraps org-roam-node-find.
  5. Uses pretty-hydra-define to create a subject specific menu.
  6. Uses pretty-hydra-define+ to append menu items to the jnf/org-subject-menu–all menu.

Calling the Macro to Populate the Menu

I then call the create-org-roam-subject-fns-for macro for each of the subjects, except for the :all subject.


(create-org-roam-subject-fns-for :personal)
(create-org-roam-subject-fns-for :public)
(create-org-roam-subject-fns-for :hesburgh-libraries)
(create-org-roam-subject-fns-for :jf-consulting)
(create-org-roam-subject-fns-for :thel-sector)

The Function and Aliases that Allow for Setting the Subject

Because I didn’t call the create-org-roam-subject-fns-for macro for the :all subject, I create some aliases.


(defalias 'jnf/org-roam--all--node-insert 'org-roam-node-insert)
(defalias 'jnf/org-roam--all--node-find 'org-roam-node-find)
(defalias 'jnf/org-roam--all--capture 'org-roam-capture)

In creating these aliases, I reduce the need for complicated logic switching in the jnf/toggle-roam-subject-filter function; this function allows me to toggle the current Org-roam subject.

Function jnf/toggle-roam-subject-filter implementation

(defun jnf/toggle-roam-subject-filter (subject)
  "Prompt for a SUBJECT, then toggle the 's-i' kbd to filter for that subject."
  (interactive (list
                (completing-read
                 "Project: " (jnf/subject-list-for-completing-read))))
  (global-set-key
   ;; Command + Control + i
   (kbd "s-TAB")
   (intern (concat "jnf/org-roam--" subject "--node-insert")))
  (global-set-key
   (kbd "C-s-c")
   (intern (concat "jnf/org-roam--" subject "--capture")))
  (global-set-key
   (kbd "C-s-f")
   (intern (concat "jnf/org-roam--" subject "--node-find")))
  (global-set-key
   (kbd "s-i")
   (intern (concat "jnf/org-roam--" subject "--node-insert")))
  (global-set-key
   (kbd "C-c i")
   (intern (concat "jnf/org-subject-menu--" subject "/body"))))  (global-set-key
   (kbd "C-c i")
   (intern (concat "jnf/org-subject-menu--" project "/body"))))

The jnf/toggle-roam-subject-filter function once had a hard-coded list of , but I extracted the jnf/subject-list-for-completing-read function to leverage the jnf/org-roam-capture-subjects-plist variable.

Function jnf/subject-list-for-completing-read implementation

(cl-defun jnf/subject-list-for-completing-read (&key
                                                (subjects-plist
                                                 jnf/org-roam-capture-subjects-plist))
  "Create a list from the SUBJECTS-PLIST for completing read.

The form should be ‘(("all" 1) ("hesburgh-libraries" 2))." ;; Skipping the even entries as those are the “keys” for the plist, ;; the odds are the values. (-non-nil (seq-map-indexed (lambda (subject index) (when (oddp index) (list (plist-get subject :name) index))) subjects-plist)))

Loading the Org Roam Package

With all of that pre-amble, I finally load the Org-roam package.


(use-package org-roam
  :straight t
  :custom
  (org-roam-directory (file-truename "~/git/org"))
  ;; Set more spaces for tags; As much as I prefer the old format,
  ;; this is the new path forward.
  (org-roam-node-display-template "${title:*} ${tags:40}")
  (org-roam-capture-templates (jnf/org-roam-templates-for-subject :all))
  :init
  (add-to-list 'display-buffer-alist
               '("\\*org-roam\\#"
                 (display-buffer-in-side-window)
                 (side . right)
                 (slot . 0)
                 (window-width . 0.33)
                 (window-parameters . ((no-other-window . t)
                                       (no-delete-other-windows . t)))))

  (setq org-roam-v2-ack t)
  (org-roam-db-autosync-mode)
  ;; Configure the "all" subject key map
  (jnf/toggle-roam-subject-filter "all"))

In loading the Org-roam package, I use the jnf/org-roam-templates-for-subject function to ensure that the capture templates contain “all” of the expected templates.

I also use the jnf/toggle-roam-subject-filter function to build the initial keymap for the “all” subject.

Conclusion

I hope it’s been helpful walking through the what and the how of implementing subject based contexts for Org-roam.

The process of refactoring towards the create-org-roam-subject-fns-for macro helped me better think through the composition of the menus. In the early stages, I had 1 macro per function definition, but moved to the `(progn) declaration to chain together the creation of several functions.

-1:-- Diving into the Implementation of Subject Menus for Org Roam (Post Jeremy Friesen (jeremy@takeonrules.com))--L0--C0--August 23, 2021 01:37 PM

Jeremy Friesen: Ever Further Refinements of Org Roam Usage

Leveraging Some Org Roam Version 2 Changes

update: In Diving into the Implementation of Subject Menus for Org Roam, I wrote about the implementation details for the following post.

Earlier I wrote about Adding Hydra Menu for Org Roam Lookup in Emacs and then Revisiting Hydra Menu for Org Roam Lookup in Emacs. I wrote those when I was using Org-roam 🔍 version 1. The release of version 2 of Org-roam broke that setup. But the breaking changes are well worth it!

Let’s dive into my new Org-roam menu:

The Org Subject Menu. I invoke jnf/org-subject-menu--all/body via the keybinding C-c i.

Refer to Table #226 below for the description of heavy text-based image
Table 226: Textual Representation of Org Subject Menu
Key CombinationCommand
p tOpen Personal Todo File
p cCapture Personal
p iInsert Personal
p fFind Personal
u cCapture Public
u iInsert Public
u fFind Public
h tOpen Hesburgh Libraries Todo File
h cCapture Hesburgh Libraries
h iInsert Hesburgh Libraries
h fFind Hesburgh Libraries
t cCapture Thel Sector
t iInsert Thel Sector
t fFind Thel Sector
cCapture
iInsert
fFind
/Toggle Org Roam Side Buffer
#Toggle Default Filter

In the default menu, there’s duplication based on subject (e.g., Personal, Public, Hesburgh Libraries, and Thel Sector).

Let’s go over the basic commands:

  1. Capture
  2. Insert
  3. Find
  4. Subject Todo
  5. Toggle Buffer
  6. Toggle Default Filter

The Capture, Insert, and Find are three of the core functions of org-roam; I map org-roam-captureto C-s-c, org-roam-node-insert to C-s-i, and org-roam-node-find to C-s-f.

When you Capture something, you find or create a new node title. If you’re creating a new node, you select your template. You then start writing down your note. When you finish the capture, the buffer closes and you’re back to the original context in which you launched the capture.

When you Insert something, it’s like Capture, except when you finish writing, org-roam inserts a link to your node in the original context in which you launched the capture.

When you Find something, you open a buffer for the found node.

In the above Org Subject Menu, there’s a Capture, Insert, and Find for each subject. Each of those subjects are configured with a set of filters and templates appropriate for the subject. More on that later.

The Subject Todo is my way of partitioning todo lists. Each subject location it’s own todo.org that I maintain.

The Toggle Buffer calls org-roam-buffer-toggle, which toggles the backlinks buffer. When the backlinks buffer is open, and I’m on an org-roam node, I can see the list of nodes that link to the current org-roam node.

The Toggle Default Filter allows me to narrow my Org-roam activity to a single subject. What does that mean?

The prompt area for toggling the default filter; the subjects are: all, hesburgh-libraries, personal, public, and thel-sector.

An emacs minibuffer with five entries: all, hesburgh-libraries, personal, public, and thel-sector.

When I select the “thel-sector” as the default filter, I re-map C-c i to jnf/org-subject-menu--thel-sector/body. The subject menu looks as follows:

Refer to Table #227 below for the description of heavy text-based image
Table 227: Textual Representation of Org Subject Menu
Key CombinationCommand
cCapture Thel Sector
iInsert Thel Sector
fFind Thel Sector
/Toggle Org Roam Side Buffer
#Toggle Default Filter
Note that I've dropped the t leading key.

In addition, I re-map C-s-c, C-s-i, and C-s-f to functions that automatically narrow the filter and templates to the “thel-sector” subject. So when I’m focusing on a particular subject, I can narrow my keyboard shortcuts to the subject.

Conclusion

This implementation feels much cleaner that my Org-roam version 1 implementation. There’s both an internal consistency and a few more places to pivot.

I wrote an issue and submitted a pull request to org-roam. The maintainer of Org-roam merged the pull request, and it’s now part of v2.1.0. The issue includes a lot more detail of the why and the how.

You can checkout this gist for my org-roam configuration. There’s room for improvement, but for now this is working quite well for my needs.

Those diving into the Emacs code will see that the Personal subject currently has two capture templates: a simple template and an encrypted template. It’s relatively simple to add new templates for a given subject.

For example, if I were to do more work in the Thel Sector, I might consider making a capture template for an Non-Player Character 🔍, Session Notes, Locations, and Faction Turns.

Similarly, for the Hesburgh Libraries subject I could see templates for Meeting Minutes and Problem Statements. For now, I have a simple template.

I hope this provides some insights into hacking on Emacs 🔍 and configuring Org-roam.

-1:-- Ever Further Refinements of Org Roam Usage (Post Jeremy Friesen (jeremy@takeonrules.com))--L0--C0--August 22, 2021 08:15 PM

Jeremy Friesen: Using Magit Built-in Functions for Workflow

Improving on a Hacked Together Function Using Magit

I wrote Emacs Script to Review Git Status of Repositories for creating a checklist of repositories to review. Over on /r/emacs, someone provided the following:

Similar to that, one can define magit-repository-directories which is a list of folders for magit to look for git projects - including an optional integer per each representing how deep to search. After you do that you can get a status overview using magit-list-repositories which shows projects name, version, status (untracked, unstaged, staged) and numbers of unpushed/unpulled commits from upstream. Very convenient. Also C-u magit-status lets you jump to one of these repositories using auto-complete.

Curious, I spent a bit of time exploring the Magit 🔍 function route, and settled on the following configuration:

(setq magit-repolist-columns
      '(("Name"    25 magit-repolist-column-ident ())
        ("Version" 25 magit-repolist-column-version ())
        ("D"        1 magit-repolist-column-dirty ())
        ("⇣"      3 magit-repolist-column-unpulled-from-upstream
         ((:right-align t)
          (:help-echo "Upstream changes not in branch")))
        ("⇡"        3 magit-repolist-column-unpushed-to-upstream
         ((:right-align t)
          (:help-echo "Local changes not in upstream")))
        ("Path"    99 magit-repolist-column-path ())))
(setq magit-repository-directories
      `(
        ("~/git/takeonrules.github.io/themes/hugo-tufte" . 1)
        ("~/git/takeonrules.github.io/" . 1)
        ("~/git/dotzshrc/" . 1)
        ("~/git/ndlib/sipity" . 1)
        ("~/git/samvera/hyrax" . 1)))

Now when I run M-x magit-list-repositories I get the equivalent buffer:

Table 225: Results of custom M-x magit-list-properties
NameVersionDBranchPath
dotzshrc20210802.2144-g9155fd9 00main~/git/dotzshrc/
sipity20210802.0852-g2ecdaa4 00main~/git/ndlib/sipity
hyrax20210731.1844-g3d92137 00main~/git/samvera/hyrax
hugo-tufte20210731.1839-gfef60e9 00main~/git/takeonrules.github.io/themes/hugo-tufte
takeonrules.github.io20210802.1134-gc84acbf6N00trunk~/git/takeonrules.github.io/

If there’s a non-blank in D column then there’s changes to commit. The column shows me what’s upstream that I don’t have locally. And the column shows me what I have locally that I haven’t pushed upstream.

And from the above buffer, I can quickly open a magit-status buffer to begin commiting changes and synchronizing repositories.

So with that, I can get an overview of all of the relevant repositories and take action accordingly. This supplants jnf/git-data-statuses function.

-1:-- Using Magit Built-in Functions for Workflow (Post Jeremy Friesen (jeremy@takeonrules.com))--L0--C0--August 03, 2021 01:47 PM

Jeremy Friesen: Emacs Script to Review Git Status of Repositories

Generating a Quasi-TODO List for a Common Mental Model Task

update: After some input on /r/emacs, I wrote Using Magit Built-in Functions for Workflow. Those changes supplant what I’ve written below.

Throughout my day, I work on several different Git 🔍 repositories. And sometimes, I can lose track of what all I’ve worked on.

To help with this task, I created the following Emacs 🔍 variable and function to let me quickly and methodically check the status of those repositories.

;; This is a truncated list of my projects
(setq jnf/data-dirs
      '(
        "~/git/takeonrules.github.io/themes/hugo-tufte"
        "~/git/takeonrules.github.io/"
        "~/git/dotzshrc/"
        "~/git/ndlib/sipity"
        "~/git/samvera/hyrax"))

(cl-defun jnf/git-data-statuses (&optional (dirs jnf/data-dirs))
  "Review DIRS via `magit'.

By default the DIRS are `jnf/data-dirs'"
  (interactive)
  (message "Review status of local git repos...")
    (dolist (path dirs)
      (if (f-dir-p (file-truename path))
          (magit-status path))))

When I execute the jnf/git-data-statuses command, Emacs opens one Magit 🔍 buffer for each of the git repositories in the jnf/data-dirs list. I then work through what I need to do for each git repository.

Below is an example of the magit-status buffer for the ~/git/takeonrules.github.io/ git repository:

Head:     trunk Publishing general update eg. no posts
Rebase:   origin/trunk Publishing general update eg. no posts
Push:     origin/trunk Publishing general update eg. no posts

Untracked files (1)
content/posts/2021/emacs-script-to-review-git-status-of-repositories.md

Unstaged changes (1)
modified   data/glossary.yml

From that buffer, I can perform the various git commands (e.g., stage all changes, commit the changes, push the branch). And when I’m done with that project’s buffer, I close it out and move on to the next project.

-1:-- Emacs Script to Review Git Status of Repositories (Post Jeremy Friesen (jeremy@takeonrules.com))--L0--C0--August 02, 2021 12:59 PM

Jeremy Friesen: Amplifying the Blogosphere (v2021-06-29)

RSS, Procrastination, and Chipping Away at Boundaries

Another Reason Elfeed Is The Best RSS Reader

As you all know, I’m a big fan of Chris Wellons’ Elfeed package for reading my RSS feed. There’s a lot to like. Wellons reimagined what an RSS reader should be and organized it around search. Most of the time, the search is implicit, defaulting to “show me the unread entries for the last 6 months” but you can specify anything you want.

At one point I wroe about Switching from Inoreader to Newsboat for RSS Reader. In my switch to Emacs 🔍, I switched to using elfeed. And I love it.

I wrote some scripts for Further Molding Emacs to Reinforce Habits. These scripts help me capture entries in Elfeed and amplify them in my blog. I used the linked script to capture the above blockquote.

Having my feed reader conceptually close to my text editor is analogue to reading a book with pencil in hand; I’m more prone to engage the text I read.

The Gold Hack

Hi! I made a fan hack of The Burning Wheel Gold edition (I don’t own Gold Revised… yet!). It is called The Gold Hack

I made a lot of changes (most of them inspired by Mouse Guard RPG) to reduce the system to 11 pages (+5 if you count the cover, the index of contents, the character sheet, the rules summary, and the credits, license and greetings pages).

I love Burning Wheel Gold, but continue to struggle with Luke Crane’s behavior. The Gold Hack along with Hot Circle

TSR Games has spent the weekend pushing forward with their plan and escalating the fight. The Facebook Group Old School TSR Games has reported the new TSR Games has threatened legal action.

I’m old enough to remember the dying gasps of Tactical Rules Studies (TSR 🔍) (the original) when they unleashed as many legal antics as possible. This was the early days of ubiquitous internet. I had heard rumors of a TSR sanctioned site that allowed for people to upload their house rules.

The site only allowed a few people access at a time. I spent many odd hours during my first year of college trying to connect to that site. It’s this odd memory, of green screens and gopher clients. And I once made it into the site, to then scrounge around for all kinds of Advanced Dungeons and Dragons: Second Edition (AD&D 2E 🔍) materials. I found a few.

But it was this odd gated space, this promised trove of information. A harbinger of the internet to come. Guarded by the malignant dragon that was TSR. Looks like this incarnation’s doubling down on that legacy. And more.

The psychology of revenge bedtime procrastination

Getting revenge on our daytime life.

The term “bedtime procrastination” was coined in by Dr. Floor Kroese, a behavioral scientist from Utrecht University, and her team. They defined it as “going to bed later than intended while no external circumstances are accountable for doing so.”

I don’t have full control of my calendar and schedule, but when my kids were younger I have memories of this behavior. This is related to burnout, and is an issue with how your employer chooses to treat you. Sure, understand the issues and how to address them. But it’s about time we reimagine coping mechanisms for living in capitalism.

-1:-- Amplifying the Blogosphere (v2021-06-29) (Post Jeremy Friesen (jeremy@takeonrules.com))--L0--C0--June 29, 2021 09:27 PM

Jeremy Friesen: Delving Further into the Why of Emacs

It's Reducing Context Shifting

Over on /r/emacs, one of the community members asked the about Integrated Development Environments (IDEs 🔍) with the following post:

I have been using emacs for an year now mostly for Clojure development with little bit of golang and python here and there. When i started using emacs here i convinced myself that

  • it makes me more productive by allowing me to do everything from keyboard
  • multi language support with packages
  • highly customisable with thousands of packages and config.
  • suited for clojure development.
  • org mode

But lately i have been thinking if i was wrong or if my assumptions still holds true, today ides like vs code provide all the above features in a fast modern looking ide.

So does the question of Emacs vs Modern IDEs boils down to asthetic choice between modern vs classic/vintage or is there any real advantage in using Emacs today.

I posted my response regarding Emacs 🔍 but figured I’d share that observation here as well:

I use Emacs for 3 reasons:
  1. Coding
  2. Blogging
  3. Note Taking

Is it best for coding? Maybe not. Corporate sponsored IDEs sure seem to provide lots of tooling. (Those same companies deploy a “Embrace, Extend, and Extinguish” strategy regarding FOSS. They’re trying to enclose the commons) .

But, coding is only one concern. I write a lot. For both personal and professional reasons. And in this case Emacs shines like none other; I’ve used Textmate 🔍, Sublime Text 🔍, Atom text editor 🔍, and Visual Studio Code 🔍 for those purposes.

But, what I’ve found: using the same tool for all three results in expanding my thinking and ability regarding those three primary topics. When I make one conceptual gain (e.g., think about a function that helps me in my note taking) my other two primary contexts benefit.

And after posting, I continued to think about this.

In the years before adopting Emacs, I would write all kinds of functions for gaming and note taking. I think to my GM::Notepad. After I released that tool, a fellow gamer and tinkerer on the computer gently quipped: “Interesting, were I to have done this I would’ve written that in Emacs.”

I like the concepts of GM::Notepad, but it failed because to use it, I had to run outside of one of my normal contexts. Yes, I often have a terminal window open. However, that context primes me for one off considerations; which is antithetical to the mindset I’m taking while running an Role Playing Game (RPG 🔍).

That comment sat with me, not as anything damning, but as a reminder that we each approach problems with different tools.

Where as I’m quite good at Ruby 🔍, I’m asking myself, “Why not encode this in Emacs? After all this is where I spend more and more of my digital day.”

From another vantage, by continuing to leverage Emacs, I’m reducing the context switching. And in reducing context switching, I’m creating more space to connect pieces of information to build my personal knowledge.

At this stage, I can’t imagine switching from Emacs to any other software; I’m finding the lessons I’ve learned compound, further expanding my understanding of how I can use Emacs to further my understanding of the games I play, the articles I read, the code I write, and all of the interconnections that emerge.

Emacs helps me get better at doing better with digital information.

-1:-- Delving Further into the Why of Emacs (Post Jeremy Friesen (jeremy@takeonrules.com))--L0--C0--June 13, 2021 12:17 AM

Jeremy Friesen: Further Molding Emacs to Reinforce Habits

It Ain't Emacs if You Ain't Hacking on Your Config Daily

I wrote about Molding Emacs to Reinforce Habits I Want to Develop. In that post, I outlined how I wrote some functions to accelerate grabbing some text and starting a new blog post.

I refined and further extended those functions. To start, I wrote down the desired behavior.

Desired Behavior

I want a function that will pre-populate an Amplifying post from an elfeed entry.

If there’s an active region (e.g., selected text), I will wrap that region in a blockquote shortcode. The elfeed entry’s title will be the cite parameter and the entry’s url will be the cite_url.

If there’s no active region, add wrap an A-tag in a CITE-tag. The A-tag’s href will be the entry’s url. And the A-tag’s inner html will be the entry’s title.

For Elfeed Mode

I use elfeed for my Rich Site Summary (RSS 🔍) feed reader. By implementing the above functional behavior, I’ll more quickly be able to add entries that I read to my Amplifying the Blogosphere series.

The jnf-amplify-elfeed function uses the updated tor-post-amplifying-the-blogosphere (I’ll go into more of that later). Yesterday I decided to map the unused F7 key to tor-post-amplifying-the-blogosphere, so in the elfeed-show-mode-map I decided to over write the global binding but preserve the over-arching functional behavior (e.g. grab the thing and make an entry in today’s amplifying the blogosphere post).

See Github 🔍 for the elfeed configuration.

(use-package elfeed
  :straight t
  :after org
  :config
  (setq-default elfeed-search-filter "@2-days-ago +unread ")
  (defun jnf/amplify-elfeed ()
    "Amplify the current `elfeed-show-entry'"
    (interactive)
    (let* ((citeURL (elfeed-entry-link elfeed-show-entry))
           (citeTitle (elfeed-entry-title elfeed-show-entry)))
      (tor-post-amplifying-the-blogosphere citeTitle
                                           :citeTitle citeTitle
                                           :citeURL citeURL)))
  :bind (:map elfeed-search-mode-map
              ("q" . jnf/elfeed-save-db-and-bury))
  :bind (:map elfeed-show-mode-map
             ("" . jnf/amplify-elfeed)
             ("s-7" . jnf/amplify-elfeed)
             ("q" . jnf/elfeed-save-db-and-bury)))

For EWW Mode

In implementing the desired behavior in elfeed, it became trivial to implement this in eww; a text based browser for Emacs 🔍.

In cases where the RSS feed is a summary, I often open the elfeed entry in eww. With a small refinement, I created jnf/amplify-eww, a function analogous to jnf/amplify-elfeed.

Similar to the elfeed-show-mode-map, I’m mapping the jnf/amplify-eww to F7. Now, when I’m using eww, I can quickly grab something to add to my Amplifying the Blogosphere series.

See Github for the eww configuration.

(use-package eww
  :straight t
  :config
  (defun jnf/amplify-eww ()
    "Amplify the current `eww-data'"
    (interactive)
    (let* ((citeURL (plist-get eww-data :url))
           (citeTitle (plist-get eww-data :title)))
      (tor-post-amplifying-the-blogosphere citeTitle
                                           :citeTitle citeTitle
                                           :citeURL citeURL)))
  :bind (:map eww-mode-map
              ("U" . eww-up-url)
              ("" . jnf/amplify-eww)
              ("s-7" . jnf/amplify-eww))
  :hook ((eww-mode . jnf/reader-visual)))

For All Other Modes

The tor-post-amplifying-the-blogosphere is independently a useful function. In adding the optional parameters citeTitle and citeURL, I’ve extended it’s usefulness.

As I said earlier, the default function for F7 is to create a record in the Amplifying the Blogosphere series. Other mode-map’s override with a more useful function.

See Github for the tor-post-amplifying-the-blogosphere definition.

(global-set-key (kbd "s-7") 'tor-post-amplifying-the-blogosphere)
(global-set-key (kbd "<f7>") 'tor-post-amplifying-the-blogosphere)

(defun tor-post-amplifying-the-blogosphere (subheading &rest ARGS)
  "Create and visit draft post for amplifying the blogosphere.

If there's an active region, prompt for the `SUBHEADING'.  The file
for the blog post conforms to the path schema of posts for
TakeOnRules.com.

Pull the `citeTitle' and `citeURL' from `ARGS' and pass those
along to the `tor-post---create-or-append'"
  (interactive (list (if (use-region-p)
                         (read-string "Sub-Heading: ")
                       nil)))
  (tor-post---create-or-append
   (format-time-string "Amplifying the Blogosphere (v%Y-%m-%d)")
   :toc "true"
   :subheading subheading
   :series "amplifying-the-blogosphere"
   :tags "response to other blogs"
   :citeTitle (plist-get ARGS :citeTitle)
   :citeURL (plist-get ARGS :citeURL)))

Extended Create or Append Behavior

And here’s the extended function. I’ve added optional parameters for citeURL and citeTitle.

See Github for the tor-post—create-or-append definition.

(defun tor-post---create-or-append (title &rest ARGS)
  "Create or append a post with `TITLE'.

The following `ARGS' are optional:

`:tags' one or more tags, as a list or string, to add to the
        frontmatter.
`:series' the series to set in the frontmatter.
`:toc' whether to include a table of contents in the post.
`:citeTitle' the title of the URL cited (if any)
`:citeURL' the URL cited (if any)
`:subheading' if you have an active region, use this header.

If there's an active region, select that text and place it."
  (let* ((default-directory (concat tor--repository-path
                                    "/content/posts/"
                                    (format-time-string "%Y/")))
         (slug (s-dashed-words title))
         (series (plist-get ARGS :series))
         (citeTitle (plist-get ARGS :citeTitle))
         (citeURL (plist-get ARGS :citeURL))
         (tags (plist-get ARGS :tags))
         (toc (plist-get ARGS :toc))
         (subheading (plist-get ARGS :subheading))
         (fpath (expand-file-name
                 (concat default-directory slug ".md"))))
    ;; If the file does not exist, create the file with the proper
    ;; frontmatter.
    (if (not (file-exists-p fpath))
        (write-region
         (concat "---"
                 "\ndate: " (format-time-string "%Y-%m-%d %H:%M:%S %z")
                 "\ndraft: true"
                 "\nlayout: post"
                 "\nlicenses:\n- all-rights-reserved"
                 "\nslug: " (format "%s" slug)
                 "\ntitle: '" title "'"
                 "\ntype: post"
                 (if series (concat "\nseries: " series))
                 (if toc (concat "\ntoc: true"))
                 (if tags (concat "\ntags:"
                                  (mapconcat
                                   (lambda (tag)
                                     (concat "\n- " tag))
                                   (flatten-tree tags) "")))
                 "\n---\n")
         nil fpath))
    ;; If we have an active region, append that region's content to
    ;; the given file.
    (if (use-region-p)
        (write-region
         (concat
          (if subheading
              (concat "\n## " subheading "\n")
            (if citeTitle (concat "\n## " citeTitle "\n")))
          (if citeURL (concat
                       "\n{{< blockquote cite=\""
                       citeTitle "\" cite_url=\""
                       citeURL "\" >}}\n"))
          (buffer-substring (region-beginning) (region-end))
          (if citeURL "\n{{< /blockquote >}}"))
         nil fpath t)
      ;; Without an active region, if we have a citeURL insert a link
      ;; to it.
      (if citeURL
          (write-region
           (concat
            "\n<cite><a href=\"" citeURL
            "\" class=\"u-url p-name\" rel=\"cite\">"
            (or (citeTitle) (citeURL)) "</a></cite>\n")
           nil fpath t)))
    ;; Finally open that file for editing.
    (find-file fpath)))

Conclusion

With just a bit of work, I expanded the function that I am using for capturing and amplifying posts from the blogosphere.

Along the way, I learned more about plist-get; This is similar to older versions of Ruby 🔍 using hashes as named parameters.

And with these modifications, I’m beginning to suspect that I’ll want to use something like jnf/amplify-eww and jnf/amplify-elfeed to quickly add to a blog post that isn’t part of the Amplifying the Blogosphere series.

-1:-- Further Molding Emacs to Reinforce Habits (Post Jeremy Friesen (jeremy@takeonrules.com))--L0--C0--June 09, 2021 02:42 AM

Jeremy Friesen: Molding Emacs to Reinforce Habits I Want to Develop

But Also, Don't Go Out and Implement a Bunch of Things

Before I switched to Emacs 🔍, I had a Rake 🔍 task that I invoked to create new blog posts. I’d hop on the terminal, run the task. That task would both stub out a new blog post and open the task in my editor of choice.

This worked, but the script wasn’t integrated into my text editor. So there was a slight context shift to go from I have an idea for a blog post to I’m starting that blog post.

I decided to rewrite it as a Emacs function. This interactive function, named tor-post-new, prompts for the posts title. I fill out the title, and the function create the file with the appropriate Hugo 🔍 front matter and opens that file in a buffer. Later in this post, I share the functions I’ve created.

By moving the function into my text editor, I reduced the friction of creating a new blog post.

, I wrote the first in the Amplifying the Blogosphere. Which got me thinking, I really should create an interactive function to ease writing entries to the Amplifying the Blogosphere series.

So I wrote tor-post-amplifying-the-blogosphere. This function doesn’t prompt for a title, but instead derives the title based on the day. So I extracted a common function so I could have the two interactive functions use the same basic function for creating the file, filling in the front matter, and editing the new file.

And as I was thinking about it, I realized “You know, if I have an active Emacs region, I may as well grab that regions text and copy that into the new buffer.”

Elisp Functions to Ease Creating Posts

update: I’ve updated the code below to provide a bit more utility. I’ve also favored a plist instead of positional parameters.

The Elisp 🔍 functions

The following code is available on Github.

(defun tor-post-new (title &optional)
  "Create and visit a new draft blog post for the prompted TITLE.

The file for the blog post conforms to the path schema of posts
for TakeOnRules.com."
  (interactive "sTitle: ")
  (tor-post---create-or-append title))
(defun tor-post-amplifying-the-blogosphere (subheading &optional)
  "Create and visit draft blog post for amplifying the blogosphere.

The file for the blog post conforms to the path schema of posts
for TakeOnRules.com."
  (interactive (list (if (use-region-p)
                         (read-string "Sub-Heading: ")
                       nil)))
  (tor-post---create-or-append
   (format-time-string "Amplifying the Blogosphere (v%Y-%m-%d)")
   :toc "true"
   :subheading subheading
   :series "amplifying-the-blogosphere"
   :tags (list "response to other blogs")))

The tor-post--create function is called by both of the above functions. It’s purpose is to encode the logic and procedures for creating a new post based on the given parameters.

(defun tor-post---create-or-append (title &rest ARGS)
  "Create or append a post with TITLE, any ARGS are optional.

ARGS:

`:tags' a list of tags to add to the frontmatter.
`:series' the series to set in the frontmatter.
`:toc' whether to include a table of contents in the post.
`:subheading' if you have an active region, use this header

If there's an active region, select that text and place it."
  (let* ((default-directory (concat tor--repository-path
                                    "/content/posts/"
                                    (format-time-string "%Y/")))
         (slug (s-dashed-words title))
         (series (plist-get ARGS :series))
         (tags (plist-get ARGS :tags))
         (toc (plist-get ARGS :toc))
         (subheading (plist-get ARGS :subheading))
         (fpath (expand-file-name (concat default-directory slug ".md"))))
    ;; If the file does not exist, create the file with the proper frontmatter.
    (if (not (file-exists-p fpath))
        (write-region
         (concat "---"
                 "\ndate: " (format-time-string "%Y-%m-%d %H:%M:%S %z")
                 "\ndraft: true"
                 "\nlayout: post"
                 "\nlicenses:\n- all-rights-reserved"
                 "\nslug: " (format "%s" slug)
                 "\ntitle: '" title "'"
                 "\ntype: post"
                 (if series (concat "\nseries: " series))
                 (if toc (concat "\ntoc: true"))
                 (if tags (concat "\ntags:"
                                  (mapconcat
                                   (lambda (tag) (concat "\n- " tag))
                                   tags
                                   "")))
                 "\n---\n")
         nil fpath))
    ;; If we have an active region, append that region's content to
    ;; the given file.
    (if (use-region-p)
        (write-region
         (concat
          (if subheading (concat "\n## " subheading "\n\n"))
          (buffer-substring (region-beginning) (region-end)))
         nil fpath t))
    ;; Finally open that file for editing.
    (find-file fpath)))
                                  "")))
                "\n---\n")
        nil fpath nil nil t))
    ;; If we have an active region, append that region's content to
    ;; the given file.
    (if (use-region-p)
        (write-region
         (concat
          "\n## YOUR H2 HERE\n\n"
          (buffer-substring (region-beginning) (region-end)))
          nil
          fpath
          t nil nil nil))
    ;; Finally open that file for editing.
    (find-file fpath)))

With the new tor-post-amplifying-the-blogosphere function, and the “grab the active region’s text”, I’m aware of further workflow refinements. I’m also seeing that I’m drawing closer to a possible event horizon in which I shift fully to Org-mode 🔍 for blogging. I can almost feel the tug of Org-mode.

Let’s dive into that just a bit.

The goal of the tor-post-amplifying-the-blogosphere function is to make it easy to highlight things I’ve found during the day. And in it’s current implementation, the first thing I find is easier to acknowledge.

For those familiar with Org-mode, I have implemented a naive and rudimentary capture process. I am aware that Org-mode’s current tooling would make easier the Amplifying the Blogosphere process.

However, I’ve only written one post in the Amplifying the Blogosphere, so I’m not about to begin migrating my blog from Hugo to Org-mode. However, knowing that there’s tooling that supports the behavior I want to develop raises the possibility of a migration.

Another way to look at this is to think about the functional desires that I might have. When I’m reading articles, I want an easy way to capture the title, URL, and perhaps a block quote of that article. With that captured information, I want to either create a new for today Amplifying the Blogosphere post or append to an existing one. update: I’ve since updated the code and it now does what I’m describing in the preceding sentence.

Of course, I can mimic quite a bit of this by adding to a copy buffer or Emacs kill ring.

So for now, I’ll proceed with what I have and be mindful of possible future work.

Reflection

An earlier version of myself might have jumped at implementing these speculative changes. But, now in my third decade of professional software development, I see this premature feature development as a questing beast.

In a way, this builds on my observations from Bringing the Whole Toolkit to Problem-Solving; Yes, I might learn something new in pursuing it, but is it actually worth spending that time?

I don’t know, but I do know that slowing down my personal implementation tendencies has always yielded a better solution and conserved my energy to address the important issues. And realistically, I don’t even know if I have a problem that warrants a solution.

-1:-- Molding Emacs to Reinforce Habits I Want to Develop (Post Jeremy Friesen (jeremy@takeonrules.com))--L0--C0--June 07, 2021 03:19 PM

Phil Jackson: Using the CIDER debugger in Evil

When using evil and CIDER together, you might find that rebinding the keys required to control the debugger is a pain. Luckily, you don’t actually have to, you can just enter insert mode instead:

(defun my-cider-debug-toggle-insert-state ()
  (if cider--debug-mode    ;; Checks if you're entering the debugger
      (evil-insert-state)  ;; If so, turn on evil-insert-state
    (evil-normal-state)))  ;; Otherwise, turn on normal-state

(add-hook 'cider--debug-mode-hook 'my-cider-debug-toggle-insert-state)

Borrowed from here.

-1:-- Using the CIDER debugger in Evil (Post)--L0--C0--June 06, 2021 04:00 PM

Phil Jackson: Widescreen emacs and vertical splits

On a widescreen monitor, especially the really wide ones, it’s nice to have only vertical splits. This snippet will give bias to vertical splits:

(defun my-split-window-sensibly (&optional window)
  "replacement `split-window-sensibly' function which prefers
vertical splits"
  (interactive)
  (let ((window (or window (selected-window))))
    (or (and (window-splittable-p window t)
             (with-selected-window window
               (split-window-right)))
        (and (window-splittable-p window)
             (with-selected-window window
               (split-window-below))))))

(setq split-window-preferred-function #'my-split-window-sensibly)
-1:-- Widescreen emacs and vertical splits (Post)--L0--C0--June 06, 2021 12:14 PM

Phil Jackson: Having Smartparens commands work with Evil-mc

As described here, when using evil-mc, it’s very frustrating when a Smartparens command works on only the first of the multiple cursors. Here’s how to fix that:

(dolist (cmd '(sp-up-sexp
               sp-copy-sexp
               sp-down-sexp
               sp-join-sexp
               sp-kill-sexp
               sp-next-sexp
               sp-split-sexp
               sp-wrap-curly
               sp-wrap-round
               sp-raise-sexp
               sp-clone-sexp
               sp-wrap-square
               sp-splice-sexp
               sp-end-of-sexp
               sp-forward-sexp
               sp-backward-sexp
               sp-convolute-sexp
               sp-transpose-sexp
               sp-kill-whole-line
               sp-beginning-of-sexp
               sp-forward-barf-sexp
               sp-forward-slurp-sexp
               sp-backward-barf-sexp
               sp-backward-slurp-sexp
               sp-splice-sexp-killing-forward
               sp-splice-sexp-killing-backward))
    (add-to-list
     'evil-mc-custom-known-commands
     `(,cmd (:default . evil-mc-execute-call))))
-1:-- Having Smartparens commands work with Evil-mc (Post)--L0--C0--June 04, 2021 11:38 PM

Jeremy Friesen: Emacs Function to Rename Hugo Blog Post

Writing a Function to Practice Lisp and Complete an Uncommon Task

As part of writing for my blog, I need to start with a file. I derive the filename from the post’s title. Sometimes, I find myself wanting to change the post’s title.

As a matter of preference, I want the title, the filename, and the slug of the Uniform Resource Locator (URL 🔍) to align. I previously wrote an elisp function (tor-post-new) to create a new blog post. That function follows my naming preferences.

Below is that function.

The tor-post-new function

This is the Elisp: dialect of Lisp used in GNU Emacs (Elisp 🔍) to create a new TakeOnRules.com blog post.


(defun tor-post-new (title &optional)
  "Create and visit a new draft blog post for the prompted TITLE.

The file for the blog post conforms to the path schema of posts
for TakeOnRules.com."
  (interactive "sTitle: ")

  (let* ((default-directory (concat tor--repository-path
                                   "/content/posts/"
                                   (format-time-string "%Y/")))
         (fpath (concat default-directory (s-dashed-words title) ".md"))
         (slug (s-dashed-words title)))
    (write-region (concat
                   "---"
                   "\ndate: " (format-time-string "%Y-%m-%d %H:%M:%S %z")
                   "\ndraft: true"
                   "\nlayout: post"
                   "\nlicenses:\n- all-rights-reserved"
                   "\nslug: " slug
                   "\ntitle: '" title "'"
                   "\ntype: post"
                   "\n---\n")
                  nil (expand-file-name fpath) nil nil nil t)
    (find-file (expand-file-name fpath))))

With the above, I type M-x tor-post-new and fill in the blog posts title. This creates a configured file (and buffer) for me to start writing a blog post.

Earlier today, in writing Conceptualizing a Process for Where and How to Publish the Thing, I had a different working title. I decided to change the title.

To conform to my preference, I chose to replace the title, slug, and renamed the file. Nothing complicated.

I do this a few times a year, but figured I’d practice my Elisp and write jnf/retitle-tor-content, a function that retitles a blog post. Below is that function.

The jnf/retitle-tor-content function

This is the Elisp to re-title a TakeOnRules.com blog post.


(defun jnf/retitle-tor-content (&optional title)
  "Replace the given buffer's title with the new TITLE.

This function will: replace the content's title, update the slug,
and rename the buffer."
    (interactive "sTitle: ")
    (let* ((metadataTitle (concat "title: '" title "'"))
           (slug (s-dashed-words title))
           (metadataSlug (concat "slug: " slug))
           (filename (buffer-file-name))
           (new-filename (concat (file-name-directory filename)
                                 slug
                                 ".md")))

      ;; Replace the title metadata entry
      (goto-char (point-min))
      (while (search-forward-regexp "^title:.*$" nil t)
        (replace-match metadataTitle))

      ;; Replace the slug metadata entry
      (goto-char (point-min))
      (while (search-forward-regexp "^slug:.*$" nil t)
        (replace-match metadataSlug))

      ;; Need to save before we rename the buffer
      (save-buffer)

      ;; Rename the buffer, accounting for version control
      (cond
       ((vc-backend filename)
        (vc-rename-file filename new-filename))
         (t
          (rename-file filename new-filename t)
          (set-visited-file-name new-filename t t)))

      ;; Report filename change
      (message "Renamed %s -> %s" filename new-filename)))

All told, this took about 45 minutes; I’ll never save that much time from this function. However, I continued to learn more about Emacs (Emacs 🔍). I took actions reinforcing that I can extend my text editor to conform to my uses. And I have something to contribute to the larger group of Emacs adopters.

-1:-- Emacs Function to Rename Hugo Blog Post (Post Jeremy Friesen (jeremy@takeonrules.com))--L0--C0--May 20, 2021 08:33 PM

Emacs@Habrahabr: НЕ VIM, а круче (xah fly keys) или XAH FLY KEYS. Большой выпуск

Насколько я знаю многие программисты используют VIM, а некоторые также используют VIM, но они это делают в EVIL (злом) режиме, это значит, что эти люди понимают все прелести VIM (быстрое редактирование текста без использования мыши, наличие нескольких режимов и т.д.). Но также я знаю, что VIM был сделан несколько миллионов лет назад, для программистов того времени, поэтому VIM использует не все возможности которые мог бы, например: Клавиша для входа в командный режим находится фиг знает где, в самой жопе самом углу клавиатуры (и. да я знаю что это можно легко поправить в .vimrc), также если я захочу поменять свой редактируемый файл, то мне нужно:

Мне это интересно!
-1:-- НЕ VIM, а круче (xah fly keys) или XAH FLY KEYS. Большой выпуск (Post semenInRussia)--L0--C0--May 17, 2021 07:41 PM

Jeremy Friesen: A Year or So of Emacs

Swapping out Ivy for Selectrum

Jakub Kadlčík FrostyX.cz wrote A Year with Emacs. on /r/emacs, someone asked How much time you need to spent with Emacs to become more productive?

All of this reminds me that I’m coming upon my first anniversary of adopting Emacs 🔍. According to my configuration log entry It looks like is my Emacs anniversary. I have no idea how that was my first “commit” for Emacs. But in the days prior, I was committing changes to configure my Visual Studio Code (VS Code 🔍) installation, so we’ll go with . I have long since uninstalled VS Code and Atom text editor (Atom 🔍).

Earlier I wrote about Why I Chose Emacs as My New Text Editor, and I’ve changed a few things. Instead of using Ivy/Counsel/Swiper, I’m now using Selectrum/Consult.

Switching from Ivy to Selectrum

During my first year of Emacs, I’ve started following Sacha Chau’s blog. This includes Emacs News. And I’ve joined /r/emacs and /r/planetemacs. A common thread is tips and tricks that people have used to further configure Emacs to their needs or as a challenge/exercise for themselves.

I had initially settled on Ivy and Swiper and Counsel for search enhancements. And I experimented with Helm et al including Helm Swoop)

In , I learned about Selectrum and then Embark.

As I read about Selectrum, I honed in on two sections:

The focus of Selectrum is on providing an enhanced completion UI and compose with other packages which stay within the constraints of the standard Emacs API.

Ivy is a promising alternative to Selectrum. It is described as a minimal alternative to Helm which provides a simpler interface. The problem with Ivy is that its architecture and API have grown organically, and as a result the implementation is complex. Ivy was originally designed to be used as a backend to Swiper, a buffer search package that originally used Helm. When Ivy became a more general-purpose interactive selection package, more and more special cases were added to try to make various commands work properly. As a result, the ivy-read API is complex with around 20 arguments and multiple special cases for particular values. Numerous functions in Ivy, Counsel, and Swiper have special cases hardcoded into them to detect when they’re being called from specific other functions in the other two packages.

What caught my eye is Selectrum’s cleaving close to the Emacs Application Programming Interface (API 🔍) and that Ivy has expanded in scope. In my experience this means that Ivy will be harder to maintain/extend/support compared to Selectrum. Note: I’m looking at scopes of 5+ years, as I intend for Emacs to be my editor of choice until I’m no longer using a computer.

That difference prompted me to explore what would it take to switch?

First, I clarified what I wanted. The primary feature was Swiper-like behavior. I use Swiper all the time to orient to the contents of a buffer. I found consult-line to be a great replacement. consult-line comes from the Consult package

Other behaviors that I’ve found useful is Counsel edit mode and wgrep-ag.

With those features in mind, I began my refactor.

I went through my Emacs configuration, and gathered up all of the declarations I had for using Ivy, Swiper, and Counsel. I moved those into a file. Then restarted Emacs. And “everything” worked. I removed the require for that file, and nothing broke on initialization. This gave me enough confidence to say was successful in isolating those packages and packages that depended on them.

You can see that file in in my emacs/jnf-ivy.el file. Also, I did all of this rework with Git (git 🔍) commits. Thus, if I broke something, I could rollback.

With Ivy et al isolated, I created emacs/jnf-selectrum.el and started configuring/building the replacement.

At one point, during the replacement, I decided to halt work and go back to Ivy. I wasn’t yet convinced that this puttering on my Emacs config was worthwhile. Later, I picked up the work and completed the cutover.

I tested the replacement by removing the require for jnf-ivy.el and adding a require for jnf-selectrum.el. With some poking and prodding, I was mostly up and running. And to verify the isolation, I removed the require for jnf-selectrum.el and nothing broke during Emacs initialization. So I had successfully compartmentalized both the Ivy and Selectrum ecosystem.

A few things in the Selectrum / Consult ecosystem that I love:

Preview of Buffers

For most of the buffer oriented features (e.g., consult-bookmark, consult-buffer, etc.), when the mini-buffer opens and you select a different entry, the original window shows the file. A nice helper when you’re looking to change context.

Embark Export

The embark-export function, similar to Ivy’s occur, opens a new buffer with the current mini-buffer results. From there I can use wgrep to edit lines from other files. All within the original mini-buffer context.

Consult Yank

The consult-yank function is one that’s on probation. I might like it, but am continuing to try it out. The consult-yank function wraps yank but provides a minibuffer preview of what you would be yanking back into the buffer. The hiccup, is you need to confirm the consult-yank.

As a long-time user of Jumpcut, I like the ability to preview what I’m about to paste, and be able to select from the history of copy/cut calls.

Further Delves into Elisp

I’ve been programming for years, and have preivouly dabbled with Lisp 🔍. It’s different, but in the last month, Elisp 🔍 started feeling quite natural.

I’ve written about Adding Hydra Menu for Org Roam Lookup in Emacs, and extended Emacs to conform to my note taking, blogging, software development, Rich Site Summary (RSS 🔍) reader, and PDF 🔍 annotator. These days, I look at “What am I doing outside of Emacs that I might be able to better incorporate into Emacs?”

I wrote an advice-add function that mirrored a feature I found in Helm Swoop. Namely, when you initiate consult-line, if you’ve highlighted text, it will use that for the initial filter. Otherwise, it will use the word at point.

(advice-add #'consult-line
            :around
            #'jnf/consult-line
            '((name . "wrapper")))

(defun jnf/consult-line (consult-line-function &rest rest)
  "Advising function around `CONSULT-LINE-FUNCTION'.

When there's an active region, use that as the first parameter
for `CONSULT-LINE-FUNCTION'.  Otherwise, use the current word as
the first parameter.  This function handles the `REST' of the
parameters."
  (interactive)
  (if (use-region-p)
      (apply consult-line-function
        (buffer-substring (region-beginning) (region-end)) rest)
      (apply consult-line-function
        (thing-at-point 'word) rest)))

Conclusion

With my Emacs config are little changes I’ve made to make Emacs better fit my approaches. I’m also

Emacs feels like an ecosystem that is defiant of corporate Embrace, Extend, and Extingush approaches. I look to The Open-Source Software bubble that is and the blogging bubble that was, and am thankful I continue blogging and use a Free Open Source Software (FOSS 🔍) text editor that’s not actively a part of Google, Microsoft, Facebook, Amazon, nor Apple’s full-blown extraction efforts.

My plan with Emacs is to engage with the community of practioners and provide what help I can. I’m not an expert, but I’m more experienced than some of us using Emacs.

update

I had nor have no current problems with Ivy/Swiper/Counsel (ISC). I think they’re great tools. What I found is that Selectrum/Consult/Embark (SCE) covered how I used ISC. There may be additional functionality of ISC that I’m missing out on, or don’t know exist.

So, given that my desired feature set exists in ISC and SCE, then I started looking at package size and surface area of functionality. In my experience, the larger that surface area the more effort will be required to maintain it. Which isn’t to say that that effort won’t be applied, it’s instead framing that as a risk.

And in taking the time to test and use both, I’m in a better position to be more specific in why I’m using the package.

-1:-- A Year or So of Emacs (Post Jeremy Friesen (jeremy@takeonrules.com))--L0--C0--May 15, 2021 12:06 PM

Emacs@Habrahabr: [Перевод] Как прорешать SICP: Отчёт о создании решебника для самого известного в мире задачника по программированию. Ботаны есть?

(Хабр-Статья представляет собой авторский перевод доклада, представленного автором на Scheme Workshop 2020, проводившегося в рамках Международной Конференции по Функциональному Программированию, 28 августа 2020 года)

Эта статья -- своего рода "отчёт" по самому большому проекту, который я сделал в своей жизни по собственной инициативе. Я сделал полное, исчерпывающее решение всех задач из одной из самых извесных книг по программированию в мире "Структура и Интерпретация Компьютерных Программ" (Structure and Interpretation of Computer Programs -- SICP), за авторством Абельсона, Сассмана и Сассман.

В ходе выполнения проекте я собрал довольно много данных о том, как решалось это задание в частности, и сформулировал несколько эвристик, помогающих выполнять проекты вообще, а именно:

Читать далее
-1:-- [Перевод] Как прорешать SICP: Отчёт о создании решебника для самого известного в мире задачника по программированию. Ботаны есть? (Post lockywolf)--L0--C0--February 14, 2021 02:15 PM

Jeremy Friesen: Many Small Tools Make Light Work (in Emacs)

Stiching Together YASnippet, Hugo Short Codes, Shell Commands, and YAML Files in Emacs

Recently, I received heart warming words of thanks and encouragement from emsenn.net for my recent Emacs (Emacs 🔍) blog posts. See Toggling Keyboard Mapping for Org Roam and Revisiting Hydra Menu for Org Roam Lookup in Emacs.

I want to share a few more bits of tooling I use to assist in my writing. This involves the following:

  • The YASnippet package for Emacs
  • A Hugo (Hugo 🔍) shortcode
  • Some Elisp: dialect of Lisp used in GNU Emacs (Elisp 🔍)
  • A Yet Another Markup Language (YAML 🔍) file
  • The The Silver Searcher (ag 🔍), cut (Unix command) (cut 🔍), and sort (Unix command) (sort 🔍) command-line functions.

Example Walkthrough

Throughout my site you might read the following:

In my editor, I write those those three list items with the following markdown and Hugo shortcode.


* {{< linkToGame "swn" >}}
* {{< linkToSeries "new-vistas-in-the-thel-sector" >}}
* {{< abbr "swn" >}}

Anatomy of a Hugo Shortcode

A Hugo shortcode encodes a bit of rendering logic. It’s useful when multiple pages want to render the same concept but provide different options.

Note: I added line numbers to help discuss this.

Here is my custom linkToGame shortcode
 1  {{- $game := .Get 0 }}
 2  {{- $key := printf "linkToGame-%s" $game }}
 3  {{- $entry := index (where $.Site.Data.glossary "game" "eq" $game) 0 }}
 4  {{- if ($.Page.Scratch.Get $key) }}
 5    <cite>{{- $entry.title }}</cite>
 6  {{- else }}
 7    {{- with $entry.offer }}
 8      <cite><a href="{{- . }}">{{- $entry.title }}</a></cite>
 9    {{- else }}
10      <cite>{{- $entry.title }}</cite>
11    {{- end }}
12  {{- end }}
13  {{- .Page.Scratch.Set $key "t" }}

Let’s walk through each of the 13 lines. The actual code for this is 20 lines long and includes some additional markup. For the purposes of simplifying this post, I’ve shorted the shortcode.

Line 1: {{- $game := .Get 0 }}
Get the first positional parameter for this shortcode. In the above example, $game is the string swn.
Line 2: {{- $key := printf "linkToGame-%s" $game }}
Create a string (e.g. `linkToGame-swn`). Using Hugo's Scratch variable, I'll use this string as a means of knowing if I've previously rendered a link. If I use this shortcode more than once on a page, I only want to render a link the first time.
Line 3: {{- $entry := index (where $.Site.Data.glossary "game" "eq" $game) 0 }}
Lookup up given game in my personal glossary; Choose the first found instance and set that to the `$entry` variable. This leverages Hugo's Data Folder. My glossary contains several hundred concepts. I'll get into that later on the Glossary File.
In Ruby, the Glossary file is an Array of Hashes. In Golang, the Glossary file is a Map of Dictionaries.
Line 4: {{- if ($.Page.Scratch.Get $key) }}
This line of logic asks: "If we've already rendered a link to this game."
Line 5: <cite>{{- $entry.title }}</cite>
Since we've already rendered a link, don't render a link to the game. Instead cite the game by the glossary entry's title.
Line 6: {{- else }}
Else, if we haven't already rendered the link to this game, then do line 7 through 11.
Line 7: {{- with $entry.offer }}
Using Hugo's with function, if the given game's glossary entry has an `offer` attribute, then do line 8.
Line 8: <cite><a href="{{- . }}">{{- $entry.title }}</a></cite>
Render a link to a purchase offer for this game.
Line 9: {{- else }}
Else, if I don't have a URL, do line 10.
Line 10: <cite>{{- $entry.title }}</cite>
I don't have a link for the game, so cite the game's title instead.
Line 11: {{- end }
Close the with statement opened on line 7.
Line 12: {{- end }
Close the if statement opened on line 4.
Line 13: {{- .Page.Scratch.Set $key "t" }}
Record, via the scracth, that we've rendered this game via. Future calls to this shortcode within the same page will now answer line 4 as false.

The Glossary File

The following references YAML notation. The indentation matters, as does the - at the beginning of the first line. In this single Dictionary entry, there are 7 terms: title, key, itemid, offer, tag, game, and abbr. Each term has a single value (e.g. ‘Stars without Number: Revised Edition’, SWN, etc).

In line 3 of the linkToGame shortcode, we fetch the Dictionary (or Hash) that has swn the game key. Below is a glossary entry printed in YAML form.

- title: 'Stars without Number: Revised Edition'
  key: SWN
  itemid: https://www.wikidata.org/wiki/Q67963569
  offer: https://www.drivethrurpg.com/product/226996/Stars-Without-Number-Revised-Edition?affiliate_id=318171
  tag: swn
  game: SWN
  abbr: SWN

update: I extracted the glossary to my Hugo theme repository; Checkout the README for more information.

The additional keys help me maintain consistency reference the same concepts post after post. Examples include:

  • Using consistent abbreviations
  • Linking to disambiguation pages (e.g. Stars without Number has a Wikidata ID of Q67963569)
  • Consistently linking to offer URLs
  • Using the same title

The YASnippet for linkToGame

I use YASnippet to help in my writing. One of the snippets I have is to help with my linkToGame shortcode. Below is that snippet:


# -*- mode: snippet -*-
# contributor : Jeremy Friesen <jeremy@takeonrules.com>
# group: takeonrules
# name: {{< linkToGame >}}
# key: ltg
# --
{{< linkToGame "${1:$$(yas-choose-value (tor-game-list))}" >}}$0
The linkToGame snippet prompting for one of the games

That snippet is available in my blogging context. When I type ltg followed by the TAB key, the ltg expands into the following: {{< linkToGame "" >}}. The cursor positions between the two quote marks, and Emacs prompts me to select from the given list.

I generate that list in Emacs with the function named tor-game-list.

The tor-game-list Function

update: I added the fourth piped shell command (e.g. tr) to the Elisp function. In adding that fourth shell command, I can now account for spaces in my game entries.

Below is the Elisp code to generate the list for the YASnippet.

1 (defun tor-game-list ()
2  "Return a list of games from TakeOnRules.com."
3  (split-string-and-unquote
4   (shell-command-to-string
5    (concat
6     "ag \"game: .*$\" "
7     (f-join tor--repository-path "data/glossary.yml")
8     " -o --nofilename | cut -d \" \" -f 2- | sort" | tr '\n' '~'"))
9    "~"))

Let’s step through the function:

Line 1: (defun tor-game-list ()
Define the function named `tor-game-list`; This function has no input parameters.
Line 2: "Return a list of games from TakeOnRules.com."
A Docstring (Docstring 🔍) that describes the function.
Line 3: (split-string-and-unquote
A function that will split the STRING into a list of strings. In this case the STRING is the results of function call on line 4, and the optional separator (e.g. "`~`" is on line 9.)
Line 4: (shell-command-to-string
A function that will execute shell command COMMAND and return its output as a string. In this case the COMMAND is the result of line 5.
Line 5: (concat
A function that will concatenate all the arguments and make the result a string. In this case those arguments are lines 6, the result of line 7's function call, and line 8.
Line 6, 7, 8
The result of lines 6, 7, and 8 is ag "game: .*$" ~/git/takeonrules/data/glossary.yml -o --nofilename | cut -d " " -f 2- | sort | tr '\n' '~'. This is the command then run by line 4.
Line 9 is "~"))
We will split the results of line 4 on the "`~`" character.

The Shell Commands

I want to explain the shell command a bit more. I’ve separated the command into the four salient parts:

  1. ag "game: .*$" ~/git/takeonrules/data/glossary.yml -o ‐‐nofilename
  2. cut -d " " -f 2-"
  3. sort
  4. tr ‘\n’ ‘~'

First is the ag command.

The first parameter (eg. "game: .*$") is a regular expression. The regular expressions will find lines that have the phrase game: followed by a space, and any sequence of characters.

Looking back at my glossary file, this regular expression will only select the line game: swn.

The second parameter is the path to my glossary. The third parameter (eg. -o) says to only print the matching portions. The fourth parameter (eg. --nofilename) says to skip printing what file had the match.

, here's the results of that ag command.
game: awda
game: bwg
game: diaspora
game: dragonknights
game: dcc
game: dw
game: 5e
game: ll
game: lh
game: mhrpg
game: shsrd
game: sfad
game: swn
game: torchbearer
game: traveller
game: wwn

The second command (e.g. cut -d " " -f 2-) treats the entries like table. The -d " " switch identifies the column separator as an empty space (e.g. SPACE). The -f 2- switch tells the command to pick the second column and everything after. This is important if a game in my glossary were multiple words separated by spaces.

See the results of running the aforementioned cut command with the the above ag command output.
awda
bwg
diaspora
dragonknights
dcc
dw
5e
ll
lh
mhrpg
shsrd
sfad
swn
torchbearer
traveller
wwn

The third command (e.g. sort) alphabetizes the entries.

The fourth command (e.g. tr (Unix command) (tr 🔍)) will take the multi-line result, which is split by the \n character, and join those lines by with the ~ character.

Conclusion

I hope this rather extensive walkthrough highlights how to use different functional pieces to improve your writing workflows.

For myself, I write to learn and explore. In the process of writing, I’m also

-1:-- Many Small Tools Make Light Work (in Emacs) (Post Jeremy Friesen (jeremy@takeonrules.com))--L0--C0--December 20, 2020 11:10 PM

Jeremy Friesen: Directory Structure for my Org Instance

Sharing Some Personal Documentation

This blog post is a repurposing of personal documentation for my internal note taking structure using org-mode and org-roam.

This blog post details the note taking structure that works for me; In part because I use the same tool for writing software as I do for writing notes and blog posts. This means most of my written output goes through the same ecosystem, and benefits from the same tooling.

I found the process of writing the intial documentation helpful; It provided concretion to my own organizational structure.

Before I get started, I wanted to provide a diagram of my org-directory:

.
├── fleeting
├── permanent
│   ├── bibliographies
│   ├── cards
│   └── letters
└── projects
    ├── ardu
    ├── hesburgh-libraries
    ├── samvera
    └── thel-sector

I also have a root level agenda.org file that I use for my daily todo items.

In reading “How to Take Smart Notes” by Sonke Ahrens, the author discusses three types of notes:

  • Fleeting
  • Permanent
  • Project

I’ve expanded this to reflect my mode of thinking and operation.

Organizational Concepts (for Me)

This blog post provides some additional conceptual framing for a previously written post about using Emacs and the Hydra package to create a menu for my Org concepts.

Let’s look at the concepts.

Fleeting

Fleeting notes are momentary “flashes”. They may be more than that, but their purpose is to capture the moment. There is an assumed process that will review fleeting notes within two days or so to see if they merit moving to something permanent.

A useful analogy is that these are my sticky notes. I put all of my sticky notes in the same place and can review them at my next convenience.

Permanent

Permanent notes are of three varieties:

Bibliographies

Bibliographies are for cited works. I’ve adopted a "Title" by author format for the “root” object; However, nothing limits me from having multiple bibliographic records for the same book/article (perhaps a citation or passage). I have not went through the rigors of grad school, and my degree is in applied mathematics and computer science, so citations are something that I’ve organically developed.

I haven’t settled on what could be bibliographic citations or cards as it relates to chapters or sub-sections or concepts of a book. For example I filed the chapter “Six Steps to Successful Writing: Separate and Interlocking Tasks (Sonke Ahrens)” in ./permanent/bibliographies yet I filed the sub-sections (e.g. “Become an Expert Instead of a Planner (Sonke Ahrens)") in ./permanent/cards, as I’m discussing those concepts a bit more.

Cards

A Card my own creation, but highlights that its something beyond a fleeing note. The card is where you flesh out ideas, and continue to inter-relate those ideas. The moniker card pays homage to Zettelkasten.

I’ve assigned a sorting suffix to all ./permanent/cards. The schema for the sorting suffix is set using the following “pattern”: 000aa00aa00aa00­­­slug_with_many_words.org.

Top-level concepts, as they arrive in the “slip-box”, will have a three digit prefix, followed by ­­­ and the slug. The three digit prefix will be “1” greater than the highest prefix.

Concepts “nested” within will then have a prefix of 000aa the 000 will match the containing concept and the aa will increment based on the next available slug. If we had 001aa and incremented we’d have 001ab. I wrote create-index-for-permanent-cards to use the above documented schema. I also have the script sort-unsorted for sorting an unsorted card.

Letters

Letters are the weird things for which I’ve felt are important to keep, but are conceptual terminals.

Projects

Project notes typically exist in a single project, and when the project completes may be “disposable”.

Example projects are my Thel Sector campaign; I use these notes to write about the New Vistas in the Thel Sector series.

I also have a project for my work on Samvera. While our community wiki was down for migration maintainence, I wrote our Tech Call notes as a note in the Samvera project.

Later, I needed to add those notes back to the wiki. And since I wrote the notes in the Samvera project, I likely won’t lose them if I forget to follow-up on that task.

Org Roam’s find-file mini-buffer. The words in parantheses are tags.

I configure my org-roam-tag-sources to use explicit tags and implicit tags from the directories. (setq org-roam-tag-sources '(prop all-directories)) Then, when I use org-roam' find file or insert function, the tags are part of the search index and display prompt.

If I want a note to span multiple projects, I can add the project name as a tag to that note.

Automation

I created two scripts to help manage my directory structure:

org-file-link-checker
to help audit the health of my Org file links. I haven't used this much as Org Roam provides some built-in functionality when renaming documents in the UI.
org-file-mover
Helps move files within the above structure. It implements and negotiates the card prefix algorithm.

These scripts helped “Reduce the Number of Decisions”—Sonke Ahrens.

Conclusion

I’ve been writing quite a bit about my Emacs (Emacs 🔍). tooling. I hope sharing the organizational structure that works for me provides some insights in how to configure Emacs (or other text editors) to work for you.

-1:-- Directory Structure for my Org Instance (Post Jeremy Friesen (jeremy@takeonrules.com))--L0--C0--December 18, 2020 03:41 PM

Jeremy Friesen: Toggling Keyboard Mapping for Org Roam

Dynamically Changing Keyboard Shortcuts to Match Working States

I previously wrote about creating a Hydra menu for Org Roam projects. I’ve been using this for the past 10 days, and it’s working great.

However, I noticed one nussance. If I’m focusing on a project, I am usually only using one of those menu items.

For example, when I spent writing up adventure notes for a new Worlds without Number 🔍 game, I only used the “World of Ardu” menu option.

What I wanted was a quick means to temporarily override the keyboard shortcut that invoked the Hydra menu. The override would then filter the org-roam-insert for the specific project.

In other words, when I’m writing notes for my New Vistas in the Thel Sector game, I want the search and insert function of org-roam narrowed to only the Thel Sector project. This narrowed scope removes a keystroke, keeps my roam-roam interactions topical, and reinforces one of my goto key combinations when writing.

Some Code

Elisp function to toggle what Cmd+i invokes.

See the relevant Github file for more context.

(defun jnf-toggle-roam-project-filter (project)
  "Prompt for a PROJECT, then toggle the `s-i' kbd
  to filter for that project."
  (interactive (list
                (completing-read
                 "Project: " '((":all" 1)
                               ("ardu" 2)
                               ("hesburgh-libraries" 3)
                               ("permanent-bibliographies" 4)
                               ("permanent-cards" 5)
                               ("samvera" 6)
                               ("thel-sector" 7)))))
  (if (string= project ":all")
      (global-set-key (kbd "s-i")
        'jnf-org-subject-menu/body)
      (global-set-key (kbd "s-i")
        (intern (concat
                   "org-roam-insert--filter-with--"
                   project)))))

Emacs Gives You the Tools to Help You Out

This solution helps me move from general writing situations into project specific writing modes. It also hightlights the dynamism of Emacs (Emacs 🔍).

While I’m writing code, prose, notes, or documentation, if I think of something that might help me out, I take a quick note. Later, I sit with the problem a bit.

I think through if it’s something I want, and how it would might be useful. I then work out a possible approach and write up some Elisp: dialect of Lisp used in GNU Emacs (Elisp 🔍) to help out.

The goal of these tweaks is to help keep me in a flow state. And Emacs provides ample means for tweaking and helping me operate closer to my thoughts.

-1:-- Toggling Keyboard Mapping for Org Roam (Post Jeremy Friesen (jeremy@takeonrules.com))--L0--C0--December 18, 2020 02:21 PM

Jeremy Friesen: Revisiting Hydra Menu for Org Roam Lookup in Emacs

A General Solution (Thusfar)

Previously, I wrote about Adding Hydra Menu for Org Roam Lookup in Emacs. Since then, I’ve explored the solution.

update: Over on org-roams Slack channel, @nobiot provided some clarity on exploring the solution. Neither @nobiot nor myself are experienced Elisp developers.

As a recap, I wanted two key behaviors from Org Roam.

First, I wanted a way to narrow the scope of my initial search when finding a file. I have two Role Playing Game (RPG 🔍) campaigns, and want a quick way to limit the search to a given campaign. Here’s that initial solution.

Second, I wanted a way to narrow the scope of linking to a file. When I’m running a game via Zoom, I’m often taking notes while playing. When I create a new concept (e.g. an Non-Player Character (NPC 🔍) or location) I want to create an org-roam note for that concept. I also want the lookup to have a similar filter.

Working Through the Solution

A Hydra Menu for Org Roam. This Hydra menu organizes the subjects.

Here’s what I have. There are four general areas:

Permanent
Non-project information; Concepts that span work and play.
RPGs
The campaigns I'm running or building.
Work
Stuff that pays the bills.
General Org
Other org-mode utilities.

Within each area, I have a few concepts. Let’s dive into the RPG area.

I have four menu options, two options for each of the two subjects: The World of Ardu and the Thel Sector. Each of these subjects have two actions; Thus four menu options.

The find file and insert prompt for the "Ardu" subject. For those following along, both find file and insert now use the filter-fn parameter of the underlying org-roam-insert and org-roam-find-file. Also, by using the filter-fn parameter, the search text does not include the project.

The Find action launches a prompt to search the scoped concern. That is “Thel Sector Find” will open a prompt limiting the file search to the Thel Sector files. By convention the key for each of these actions is the upper case of the related insert action.

The insert action is where the magic lives. When I select that action, I open a similar file search as above. When I select a file, it creates a link in the originating buffer. If I don’t find a matching file, I’m prompted to create a quick note.

update: Based on my directory structure fixed the permanent,bibliographies and permanent,cards to account for the comma separation of the roam tags.

Emacs Code for Org Subject Menu

You can find also find this code on Github. Below is that code.

(defun xah-filter-list (@predicate @sequence)
  "Return a new list such that @PREDICATE is true on all members of @SEQUENCE.
URL `http://ergoemacs.org/emacs/elisp_filter_list.html'
Version 2016-07-18"
  (delete
   "e3824ad41f2ec1ed"
   (mapcar
    (lambda ($x)
      (if (funcall @predicate $x)
          $x
        "e3824ad41f2ec1ed" ))
    @sequence)))

(defmacro org-roam-inserter-fn (project)
  "Define a function to wrap the `org-roam-inser' with a filter for the given PROJECT."
  (let* ((fn-name (intern (concat "org-roam-insert--filter-with--"
                                  (replace-regexp-in-string "\\W+" "-" project))))
         (docstring (concat "Insert an `org-roam' file for: " project)))
    `(defun ,fn-name (&optional lowercase completions description link-type)
       ,docstring
       (interactive "P")
       (let* ((filter (lambda(completions) (xah-filter-list
                                            (lambda(item) (string-match-p (concat "\\W" ,project "\\W") (first item)))
                                            completions))))
         (org-roam-insert lowercase completions filter description link-type)))))

(defmacro go-roam-find-file-project-fn (project)
  "Define a function to find an `org-roam' file within the given PROJECT."
  (let* ((fn-name (intern (concat "org-roam-find-file--" (replace-regexp-in-string "\\W+" "-" project))))
         (docstring (concat "Find an `org-roam' file for: " project)))
    `(defun ,fn-name (&optional initial-prompt completions)
       ,docstring
       (interactive)
       (let* ((filter (lambda(completions) (xah-filter-list
                                            (lambda(item) (string-match-p (concat "\\W" ,project "\\W") (first item)))
                                            completions))))
         (org-roam-find-file initial-prompt completions filter)))))

(go-roam-find-file-project-fn "thel-sector")
(go-roam-find-file-project-fn "ardu")
(go-roam-find-file-project-fn "permanent,bibliographies")
(go-roam-find-file-project-fn "permanent,cards")
(go-roam-find-file-project-fn "hesburgh-libraries")
(go-roam-find-file-project-fn "samvera")
(org-roam-inserter-fn "thel-sector")
(org-roam-inserter-fn "ardu")
(org-roam-inserter-fn "permanent,bibliographies")
(org-roam-inserter-fn "permanent,cards")
(org-roam-inserter-fn "hesburgh-libraries")
(org-roam-inserter-fn "samvera")

(defvar jnf-org-subject-menu--title (with-faicon "book" "Org Subject Menu" 1 -0.05))
(pretty-hydra-define jnf-org-subject-menu (:foreign-keys warn :title jnf-org-subject-menu--title :quit-key "q")
  (
   "Permanent"
   (("b" org-roam-insert--filter-with--permanent-bibliographies "Bibliography")
    ("B" org-roam-find-file--permanent-bibliographies " └─ Find")
    ("c" org-roam-insert--filter-with--permanent-cards "Card")
    ("C" org-roam-find-file--permanent-cards " └─ Find"))
   "RPGs"
   (("a" org-roam-insert--filter-with--ardu "Ardu, World of")
    ("A" org-roam-find-file--ardu " └─ Find")
    ("t" org-roam-insert--filter-with--thel-sector "Thel Sector")
    ("T" org-roam-find-file--thel-sector " └─ Find"))
   "Work"
   (("h" org-roam-insert--filter-with--hesburgh-libraries "Hesburgh Libraries")
    ("H" org-roam-find-file--hesburgh-libraries " └─ Find")
    ("s" org-roam-insert--filter-with--samvera "Samvera")
    ("S" org-roam-find-file--samvera " └─ Find"))
   "General Org"
   (("i" org-roam-insert "Insert Unfiltered")
    ("I" org-roam-find-file " └─ Find")
    ("O" gorg "Agenda")
    ("R" org-roam-jump-to-index "Roam Index"))
))

(global-set-key (kbd "s-1") 'jnf-org-subject-menu/body) ;; Deprecated
(global-set-key (kbd "s-i") 'jnf-org-subject-menu/body)

update

If you dig into the code, you’ll see quite a bit of duplication. I spent just a bit of time trying to remove the duplication.

The one that sticks out most is the filter lambda. Those two lambdas are duplicates, and have a rather large contextual concern. Below is a copy:

(lambda(completions) (
    xah-filter-list
    (lambda(item) (string-match-p (concat "\\W" ,project "\\W") (first item)))
    completions)
))

Let’s step through this from the inside out.

The following code (lambda(item) (string-match-p (concat "\\W" ,project "\\W") (first item)) creates is the filter. The given item is list from a completion result alist. The first item of the list is a string. The lambda returns true if there’s a match. Let’s replace this logic with the symbol predicate-matching-function.

Stepping out we have (xah-filter-list predicate-matching-function completions). In this section I’m calling xah-filter-list with the function predicate-matching-function and completions, an alist of candidate results. xah-filter-list returns a new alist in which each completion is a match. We use the predicate-matching-function to determine if its a match. Let’s replace this with completions-filtering-function.

Stepping out again, we now have lambda(completions) (completions-filtering-function); This is what we will use as the filter-fn parameter for both org-roam-find-file and org-roam-insert. Both methods require that filter-fn be a function that takes one parameter; eg. the alist of completions.

I could refactor this, but at this stage it’s good enough for my needs.

And if I start refactoring, I’ll need to look at consolidating the two macros that each generate a function. And I might look at my menu generation.

Wrapping Things Up

I love the malleability of Emacs. I love that its a text editor; What I really love is that I can build on the vast array of functions to help me organize according to my needs.

-1:-- Revisiting Hydra Menu for Org Roam Lookup in Emacs (Post Jeremy Friesen (jeremy@takeonrules.com))--L0--C0--December 09, 2020 01:01 AM

Jeremy Friesen: Adding Hydra Menu for Org Roam Lookup in Emacs

Pre-Populating 'org-roam-find-file' to Help Topic Lookup

For the New Vistas campaign, I’ve been using org-roam to help organize my notes. org-roam builds on top of org mode, providing a vast array of text and information management tooling.

, I reached out to my brother, son, and two long-time friends to see about running a campaign; This would be in addition to my New Vistas in the Thel-Sector campaign.

I said I’m interested in running either Stars without Number: Revised Edition 🔍 or Worlds without Number 🔍. Right now we’re leaning towards WWN. I backed the Kickstarter for “Worlds without Number” which gave me access to the ongoing Beta documents.

As I started taking notes on my campaign, I ran into a bit of an organizational snag; My current note-taking tooling works well with one fictional setting and campaign. Adding a second fictional setting and campaign will make disambiguation more difficult.

Organizing Information

I started thinking about what I needed.

First and foremost, I needed a way to quickly scope my searches. With minimal key strokes I wanted to narrow my search to “thel-sector” or the new campaign. Or “samvera” or “bibliography” or what not.

Second, I wanted the above search behavior to apply to Org Roam’s quick search and insert link function org-roam-insert. When I invoke org-roam-insert, it brings up a prompt to search all notes. As I type, the list narrows. If I select a result from the search, the function creates a link in the file from which I started the search. If I don’t select a result, the function opens an buffer to create a new note. When I close the buffer, the function creates a link to the newly created file.

Organization Thus Far

With an hour of learning and work, here’s what I settled on using the pretty-hydra.el package.

(use-package pretty-hydra
  :straight (pretty-hydra
             :type git :host github
             :repo "jerrypnz/major-mode-hydra.el"
             :files (
               :defaults (
                 :exclude "major-mode-hydra.el"))))

A menu of Org Mode topics. Table XX shows an HTML rendering of this iamge.

Table 214: Textual Representation of my Find File in Roam Project
GroupKeyLabel
OrgoOrg Agenda
OrgiOrg Roam Index
PermanentbBibliography
PermanentcCard
RPGsaArdu, World of
RPGstThel Sector
WorktHesburgh Libraries
WorksSamvera

Here’s the the configuration for my Org Roam Hydra.

The org-roam-find-file command prepopulated with "thel-sector".

I invoke the menu via Cmd+1, then pick an option. For the “Org” groups, these commands open associated files. For the “RPGs”, “Work”, and “Permanent” it invokes the org-roam-find-file and pre-populates the search result with the corresponding text.

This means when I want to look-up something I can open the “Find File in Roam Project” menu. However, this does not help in searching for a note and linking to the originating note.

What I Still Want

I would like the org-roam-insert to pre-populate a search result in a similar manner as what I’ve worked out the “Find File in Roam Project” menu.

I haven’t yet explored that functionality, and will be asking for some help from the Emacs (Emacs 🔍) community.

This second need is most useful when I’m taking notes while playing in a game.

While running a game, I have a primary document (e.g. the “session”) where I record notes. During the session, as we introduce topics or subjects, I either link to an existing note or create a new note and link to that note.

Having a quick means of narrowing both search and subject creation will help in my game facilitation and note taking.

I’ve got some ideas, but for now, I’m focusing on writing up my notes from Thel Sector game and preparing for a possible Session 0 for a Worlds without Number game.

-1:-- Adding Hydra Menu for Org Roam Lookup in Emacs (Post Jeremy Friesen (jeremy@takeonrules.com))--L0--C0--December 07, 2020 01:07 AM

Jeremy Friesen: Synchronizing OSX, Terminal, and Emacs Dark/Light Appearance

Composing Shell, Applescript, and Emacs Eval

Throughout the day, I find myself toggling between Light and Dark modes. This evening I wrote up a shell script to keep Macintosh Operating System X (OS X 🔍), Emacs (Emacs 🔍), and my Terminal synchronized.

I named the script dark. Now, from the command-line, I can type dark to switch between Light and Dark mode.

Below is a copy of that script:

#!/bin/sh

# This script toggles the Operating System, Terminal, and Emacs
# themes.  It uses the state of the Operating System to determine
# if the terminal and Emacs should be "dark" or "light".

# First, tell OS-x to toggle dark mode.
osascript -e 'tell application "System Events" to tell appearance preferences to set dark mode to not dark mode'

# Second, determine what is the newly set appearance.
appearance=`defaults read -g AppleInterfaceStyle 2>/dev/null`
if [ -z "$appearance" ]
then
  # No value for AppleInterfaceStyle, so the OS has us in light
  # mode, proceed accordingly.
  sh $HOME/.config/base16-shell/scripts/base16-google-light.sh
  editor --eval "(disable-theme 'modus-vivendi)" \
    --eval "(modus-operandi-theme-load)" 1> /dev/null
else
  # AppleInterfaceStyle is set, and that means we're now in "Dark"
  # mode.
  sh $HOME/.config/base16-shell/scripts/base16-google-dark.sh
  editor --eval "(disable-theme 'modus-operandi)" \
    --eval "(modus-vivendi-theme-load)" 1> /dev/null
fi

Nothing fancy.

update

Emacs Configuration

Later on , I started tinkering to see about Emacs synchronizing with the Dark/Light mode of OS X.

(defun jnf-dark ()
  "Toggle system-wide Dark or Light setting."
  (interactive)
  (shell-command "osascript -e 'tell application \"System Events\" to tell appearance preferences to set dark mode to not dark mode'")
  (jnf-emacs-theme-by-osx-appearance))


(defun jnf-emacs-theme-by-osx-appearance ()
  "Set theme based on OSX apperance state."
  (if (equal "Dark" (substring
                      (shell-command-to-string
                        "defaults read -g AppleInterfaceStyle") 0 4))
  (progn
    (disable-theme 'modus-operandi)
    (modus-vivendi-theme-load))
  (progn
    (disable-theme 'modus-vivendi)
    (modus-operandi-theme-load))))

;; Load the appropriate Emacs theme based on OSX appearance
(jnf-emacs-theme-by-osx-appearance)

I added this to my one of the files I load for Emacs.

-1:-- Synchronizing OSX, Terminal, and Emacs Dark/Light Appearance (Post Jeremy Friesen (jeremy@takeonrules.com))--L0--C0--November 12, 2020 02:48 AM

Jeremy Friesen: Using Emacs While Running Online Games

These Are The Tools I Use for Table Top Gaming

Any sufficiently advanced hobby is indistinguishable from work.
Jeremy Friesen

The epigraph of unknown origin invokes one of the three famous laws of the future. Below is the original:

Any sufficiently advanced technology is indistinguishable from magic.

Arthur C. Clark, Hazards of Prophecy: The Failure of Imagination

Introduction

On Sunday mornings, I’ve been facilitating a Stars without Number campaign. You can read about it in my New Vistas in the Thel Sector series.

While I prefer in-person gaming, I’m coming around to having access to my work tools while running a game.

Tools of my Hobby

Earlier, I wrote gm-notepad and a gm-notepad data-set for Stars without Number. Truth be told, I used the tool once while running a face to face game. It was alright. The tool worked at the command-line level which hindered its utility. At the time, Alex Schroeder said he would just use Emacs for such a thing. I wasn’t using emacs in , so that path felt prohibitive; And I certainly wasn’t going to make a full-blow application out of gm-notepad. I don’t regret the time I spent building the tool. I learned more about low-level tools, tokenization, refactoring, and ePubs.

, I use the following tools for the Stars without Number game:

Online Video
Either Zoom or Google Meet; I really just need audio.
Emacs
A highly configurable text-editor.
Org Mode
An organizational framework built into Emacs.
Org Roam
An organizational module that brings aspects of Zettelkasten.
swnt
Command line tools for generating random Stars Without Number results.
Custom Emacs Functions
The tools that I wrote.

Let’s dive into each of those components.

I’ve been writing code for at least 30 years, however, this is my first year of Elisp: dialect of Lisp used in GNU Emacs (Elisp 🔍) and Emacs. Several years ago, I poked around Lisp and Clojure, but I didn’t have a compelling reason to learn them.

Online Video

When running a game on-line, I don’t use a virtual table-top nor a dice roller. Instead we go for a Theater of the Mind approach. I describe the situation and relative positioning (if applicable). The players ask for clarification, and we talk it out. Players also roll physical dice and tell me the results.

I find the virtual table tops to interfere with the kind of play I’m looking for. I don’t want to focus on moving pieces on a board; I reserve that for some of my face to face play. Instead I want to focus on the fiction we’re producing. And yes, a map can help facilitate that, however spending t

Emacs

This is my text editor of choice. I use it for writing software, blogging, taking meeting notes, and just about anything text based.

Over on Reddit, someone asked What’s your job? What’s your daily emacs workflow?

The responses surprised me. Software developers and scientists use Emacs. But so do lawyers, nurses, and authors. It makes sense, Emacs is a great tool for consolidating your text-based work.

Org Mode

When talking about why I chose Emacs, I wrote a short section about Org Mode.

For the purposes of my campaign notes, I don’t use much of the Calendar and Agenda feature. Instead I use the org-mode syntax and publishing features.

org-mode syntax is similar to Markdown, but it’s the tools built on top of org-mode that make it shine.

First, I learned about Comment Lines. I write Referee notes as comments. These comments won’t make their way to my blog and public session reports.

Second, I use org-export-dispatch to export the org file to Markdown. My blogging engine, Hugo (Hugo 🔍), supports org as a valid extension, but for historical reasons everything I write for the blog is in Markdown. I may make the switch, but for now am keeping that separation. The export skips over comments, and leaves me a mostly ready to publish markdown file.

Third, I can use the +INCLUDE directive. Per the Include Files documentation, I can use +INCLUDE to aggregate multiple files into a single file export.

For example, I could create an org file for a single encounter that I plan. In that encounter, I might have Referee notes, Read Aloud text, Design Notes, as well as play throughs of that encounter. If I wanted to generate a “publishable” adventure, I could use +INCLUDE to target just the Notes and Read Aloud text. It’s the versatility of composition that I love.

Org Roam

I also wrote a section about Org Roam. For my campaign, I make extensive use of org-roam.

In essence, org-roam provides tooling to build a campaign wiki.

While I’m taking notes, either before, during, or after a session, I can invoke org-roam-insert (via CMD+i) to quickly find or create a new note. With that note org-roam creates a link at the point of my cursor.

org-roam also queries for backlinks. Let’s say I’m looking at the note for “Vern Schultz”, a player character in the game. I can see all notes that have a link to Vern Schultz. That’s super handy.

It also ships org-roam-server, which provides a graphical view of the network graph of notes.

swnt

SWNT is a command line tool written in go-lang. It provides a means of generating results from the random tables in the free version of Stars without Number.

I used swnt to generate the initial Thel Sector. It’s output is text-based and by extension highly portable. I’ve moved it around, repurposed it, and ran scripts against the initial sector output.

While running a Stars without Number game, I keep a terminal open for swnt. Most often, I use it to generate the name of an Non-Player Character (NPC 🔍).

Custom Emacs Functions

I wrote swn-npc, an interactive Emacs function that pipes the swnt new npc output into a file and adds a link to that file at the point of my cursor. This behaves similar to org-roam-insert, except instead of attempting to find an NPC, it just makes one.

In other words, when I need a random character, I run this function to get a random person’s name that links to their details. I may or may not use those details, but they are there for reference.

See the Emacs Code

You can also see this code over on Github

(defun thel-sector-filename (title)
  "Convert the given TITLE to a filename.

The filename is conformant to my org-roam capture templates."
  (f-join org-directory
          "projects"
          "thel-sector"
          (concat
           (format-time-string "%Y%m%d---")
           (s-snake-case title) ".org")))

;; TODO - Can this be shifted to an org-roam capture template?
(defun swn-npc (culture &optional)
  "Capture a random `swnt' npc of the prompted CULTURE.

This function writes to the as-of-now hard-coded Thel Sector
project in my org-directory."

  ;; Prompt for a culture that will be used as the basis for the
  ;; random name.
  (interactive (list
                (completing-read
                 "Culture: " '(
                               ("Arabic" 1)
                               ("Chinese" 2)
                               ("English" 3)
                               ("Greek" 4)
                               ("Indian" 5)
                               ("Japanese" 6)
                               ("Latin" 7)
                               ("Nigerian" 8)
                               ("Russian" 9)
                               ("Spanish" 10)))))
  (let* (
         ;; Get a list of the lines output from
         ;; swnt's command.
         (npc-string-list (split-string-and-unquote
                           (shell-command-to-string
                            (concat
                             "swnt new npc "
                             "--is-patron "
                             "--culture "
                             culture))
                           "\n"))
         ;; Extract the NPCs name
         (npc-name (string-trim
                    (replace-regexp-in-string
                     ":"
                     ""
                     (car npc-string-list))))
         ;; Build the document's body, conforming
         ;; to org-mode format.
         (body (concat
                "#+title: " npc-name
                "#+roam_tags: npc"
                "\n\n* " npc-name
                "\n\n" (string-join
                        npc-string-list "\n")))
         ;; Get the path to the file
         (fpath (thel-sector-filename npc-name)))

    ;; Write the body to the file at the FPATH.
    (write-region body nil fpath nil nil nil t)

    ;; Insert at point an org-mode link to
    ;; the randomly generated NPC.
    (insert (concat
             "[[file:" fpath "]["
             npc-name "]]"))

    ;; Rebuild the org-roam cache, as I've just added an NPC.  This is
    ;; a kludge, as I'm treating org-roam as a black box.  My
    ;; preference would be to avoid rebuilding the cache, but instead
    ;; rely on the inner workings of org-roam to do this work.  If I
    ;; go the path of an org-roam capture template, that would work.
    (org-roam-db-build-cache)))

;; Given that I'm in an org-mode context, then the following kbd
;; combination will prompt to generate a random SWN npc.
(define-key org-mode-map (kbd "C-c s n") 'swn-npc)
A Random NPC from swnt

Below is an org-mode file, built from the swnt output and the swn-npc function.

#+title: Dmitri Sikorski
#+roam_tags: npc

* Dmitri Sikorski

Dmitri Sikorski              :
Culture                      : Russian
Gender                       : Other
Age                          : Middle-aged or elderly
Background                   : The local underclass or poorest natives
Role in Society              : Military, soldier, enforcer, law officer
Biggest Problem              : Drug or behavioral addiction
Greatest Desire              : They want fame and glory
Most Obvious Character Trait : Devotion to a cause
Hooks                        :
Initial Manner               : Somewhat intoxicated by recent indulgence
Default Deal Outcome         : They firmly intend to actively betray the PCs
Motivation                   : Greed for wealth and indulgent riches
Want                         : Retrieve a lost or stolen object
Power                        : They control the use of large amounts of violence
Hook                         : Visible signs of drug use
Reaction Roll Results        : Positive, potentially cooperative with PCs
Patron                     :
Patron Eagerness to Hire   : Desperate, might offer what they can’t pay
Patron Trustworthiness     : They’ll pay without quibbling
Basic Challenge of the Job : Get proof of some misdeed
Main Countervailing Force  : The job is spectacularly illegal
Potential Non-Cash Rewards : Information the PCs need
Complication to the Job    : Critical gear will fail partway through

An Example Walkthrough of My Tool Chain

Before a session, I go to my “New Vistas in the Thel Sector Sessions” document; It lists all of the session recaps.

To create a new session document, I use org-roam-insert (via CMD+i) to prompt me to either find or create a document.

I type “New Vistas in the Thel Sector Session NN" where NN is the next session number and hit enter. org-roam didn’t find this file so it creates one. Because I’m creating a new document, I need to pick a template. I choose the “Project > Thel Sector”. I have one template per project. The template defines where I’ll file the document.

I now have a blank document. I start filling out the boilerplate information for a sesson. As a todo, I should probably create a snippet that is a template for my sessions. For now, I usually copy from a previous session.

I save the document, and Emacs writes a link to the new document to the existing “New Vistas in the Thel Sector Sessions” document.

As part of my preparation, I review past sessions, maybe creating new documents or connecting to existing documents. For the upcoming session, I start writing notes. Some notes are only intended for me (e.g. Notes about the off-camera actions of any opposition). Those Referee only notes I mark as comments. Other notes might be “Read Aloud” text or bookkeeping notes.

I might also launch org-roam-server; This creates a web page at http://localhost:8080/ that I can use to explore the links between documents. It’s a quick way to sift through the documents.

During the session, I keep “New Vistas in the Thel Sector Session NN” open. I write down my notes. I’ll use org-roam-insert to connect to documents that come up.

For example, I might write the note “The character’s head over to the Grand Kall Theatre to talk with Suliat Adunola. The bold words are links to corresponding documents.

If I need an NPC, I invoke swn-npc (via Ctrl + c s n), and poof, I get a random PC name along with results from all of the NPC random tables. I might ignore those results or dive into those for further inspiration. As I sift through my NPC notes, I remove any unused or unreferenced aspects of these random NPCs.

Once the sessions over, I go back to my session notes and start converting those notes to a blog post. If I find something interesting that I don’t want in my blog, I’ll mark it as a comment. I’ll also read through and look for any “notable” things that might merit their own note. I’ll create those.

Finally, I export the “New Vistas in the Thel Sector Session NN” and do a quick conversion to get it ready for my blog.

Conclusion

Much of my day is spent writing. It might be software, blog posts, meeting minutes, documentation, or session notes. I’m striving to continue to use the same toolset for all of those. That way, when I learn something new during one task, I might be able to bring it to another task.

In writing this blog post, I started thinking about different approaches I could try. I’ll just keep folding my processes back into my tool chain and see what comes out next.

Post Script

I’ve been writing this blog post as my computer cycles through Operating System (OS 🔍) upgrades. During that time, on another device, I looked through the schedule for the upcoming Emacs Conference 2020. The talks cover a wide-spectrum of how people use Emacs. I’m looking forward to the free 2-day conference on and . I’m sure I’ll pick up more ideas for extending Emacs to best suit my needs.

Updates

update

Gretzuni.com posted Why (non-techie) Emacs?, which provides some non-technical insights into Emacs.

-1:-- Using Emacs While Running Online Games (Post Jeremy Friesen (jeremy@takeonrules.com))--L0--C0--November 09, 2020 09:08 PM

Jeremy Friesen: Reducing Duplication in my Emacs Config

Learning from One Small Refactor

Earlier this week, I realized that I wanted some keyboard bindings to open a few of my common org-mode files.

I have the following files:

agenda.org
At the end of the day, this is where I put tomorrows expected work.
elfeed.org
This is the org file that manages my elfeed's feed list; It's analogue to an Outline Processor Markup Language (OPML 🔍) file.
index.org
This is the index into the multitude of org files that comprise my Org Roam repository.
permanent/card_index.org
Following the slip-case method, I'm capturing ideas and how they stack in my virtual slip-case.
permanent/bibliographic_index.org
This is a list of all the bibliographic cards that I've filed away.
troubleshoot.org
Sometimes I have long-running problems that I'm poking it. I record that work in the Troubleshooting.org file.

Note: the filenames aren’t important, but help provide some context.

Pre-Refactor Code

Prior to tonight’s work, I mapped function keys (e.g. F2, F3, etc.) to some of these files. It was in an ad-hoc fashion.

Below is the code I used for opening these org files:

(defun gorg (&optional org_file_basename)
  "Jump to the given ORG_FILE_BASENAME or toggle it's org-sidebar.

If no ORG_FILE_BASENAME is given default to `agenda.org'. I chose
`gorg' as the mnemonic Goto Org."
  (interactive)
  ;; Need to know the location on disk for the buffer
  (unless org_file_basename (setq org_file_basename "agenda.org"))
  (setq org_filename (concat org-directory "/" org_file_basename))
  (let ((current_filename (if (equal major-mode 'dired-mode) default-directory (buffer-file-name))))
    (if (equal current_filename (expand-file-name org_filename))
        (progn (org-sidebar-toggle))
      (progn (find-file org_filename) (delete-other-windows)))))

Note: I’m also making use of the org-sidebar package; But that’s not important to the refactoring.

And here are the key mappings for those files:

(global-set-key (kbd "<f2>") 'gorg)
(global-set-key (kbd "<f3>") `(lambda () (interactive) (gorg "index.org")))
(global-set-key (kbd "<f4>") `(lambda () (interactive) (gorg "permanent/card_index.org")))
(global-set-key (kbd "<f5>") `(lambda () (interactive) (gorg "troubleshooting.org")))
(global-set-key (kbd "<f6>") `(lambda () (interactive) (gorg "permanent/bibliographic_index.org")))

At this time, it is my understanding that in Emacs I cannot bind a parameterized function to a keyboard shortcut. That is to say the following would not work: (global-set-key (kbd "<f6>") 'gorg "permanent/bibliographic_index.org")

The key definitions were passable. But there wasn’t a mnemonic with the function key and filename.

I thought about C-c o a for command to open the agenda.org file. And decided to change the key combinations. Below are the changes:

(global-set-key (kbd "C-c o a") 'gorg)
(global-set-key (kbd "C-c o i") `(lambda () (interactive) (gorg "index.org")))
(global-set-key (kbd "C-c o c") `(lambda () (interactive) (gorg "permanent/card_index.org")))
(global-set-key (kbd "C-c o t") `(lambda () (interactive) (gorg "troubleshooting.org")))
(global-set-key (kbd "C-c o b") `(lambda () (interactive) (gorg "permanent/bibliographic_index.org")))
(global-set-key (kbd "C-c o e") `(lambda () (interactive) (gorg "elfeed.org")))

Better, but my brain wanted to reduce duplication.

An Observation

In other configurations, I’d seen mode-maps. I wondered about creating a mode map. I searched and found it in the documentation:

Some prefix keymaps are stored in variables with names:

  • ctl-x-map is the variable name for the map used for characters that follow C-x.
  • help-map is for characters that follow C-h.
  • esc-map is for characters that follow ESC. Thus, all Meta characters are actually defined by this map.
  • ctl-x-4-map is for characters that follow C-x 4.
  • mode-specific-map is for characters that follow C-c.

Curious about what all was mapped to mode-specific-map, I looked it up. I use the helpful package’s helfpul-variable. The function for C-c o i was registered as #<anonymous-function>. I had bound the key combination to a lambda, which is an anonymous function.

If I wanted Emacs to best reinforce my mnemonic, I wanted to move away from the anonymous function and to something meaningful.

Post-Refactor

What I wanted was to loop through an array of key/value pairs. The key would be the keyboard shortcut and the value would be the name of the relative name of the file.

I left the above gorg function defined as is. The following code is the macro I wrote:

(defmacro gorg-sexp-eval (sexp &rest key value)
  `(eval (read (format ,(format "%S" sexp) ,@key ,@value))))

(dolist (the-map  '(("a" . "agenda.org")
                    ("b" . "permanent/bibliographic_index.org")
                    ("c" . "permanent/card_index.org")
                    ("e" . "elfeed.org")
                    ("i" . "index.org")
                    ("t" . "troubleshooting.org")))
  ;; Create a function for element in the above alist.  The `car'
  ;; (e.g. "a"), will be used for the kbd shortcut.  The `cdr'
  ;; (e.g. "agenda.org") will be the filename sent to `gorg'
  (gorg-sexp-eval
   (progn (defun gorg--%1$s-%2$s ()
      "Invoke `gorg' with %2$s"
      (interactive)
      (gorg "%2$s"))
          (global-set-key (kbd "C-c o %1$s") 'gorg--%1$s-%2$s))
   (car the-map) (cdr the-map)))

Originally, I had two calls to the gorg-sexp-eval macro, but factored that away by using progn to wrap the method definition and the keybinding.

Now when I look at the mode-specific-map and see that gorg--i-index.org is registered to the C-c o i keyboard combination.

update

Based on feedback on Reddit, I reworked the code.

In this work I have two considerations. First, is the code should be legible. One commenter rightly pointed out that I was jumping through some hoops with the defmacro. As a Ruby developer, I always look at the eval function with trepedation. It’s presence usually means something’s not quite right.

I learned a less convoluted way to do what I wanted to do. Here’s the code I’m going with:

(defmacro go-org-file-fn (file)
  "Define a function to go to Org FILE."
  (let* ((fn-name (intern (concat "go-org-file-" file)))
         (docstring (concat "Go to Org file at: " file)))
    `(defun ,fn-name ()
       ,docstring
       (interactive)
       (gorg ,file))))

(global-set-key (kbd "C-c o i") (go-org-file-fn "index.org"))
(global-set-key (kbd "C-c o a") (go-org-file-fn "agenda.org"))
(global-set-key (kbd "C-c o b") (go-org-file-fn "permanent/bibliographic_index.org"))
(global-set-key (kbd "C-c o c") (go-org-file-fn "permanent/card_index.org"))
(global-set-key (kbd "C-c o e") (go-org-file-fn "elfeed.org"))
(global-set-key (kbd "C-c o i") (go-org-file-fn "index.org"))

What’s happening? The go-org-file-fn returns a named function. Each of the global-set-key calls binds the keyboard combination to the named function.

Now, when I type C-c o ? I get a description of the key bindings. They look like:

Global Bindings Starting With C-c o:
key             binding
---             -------

C-c o a         go-org-file-agenda.org
C-c o b         go-org-file-permanent/bibliographic_index.org
C-c o c         go-org-file-permanent/card_index.org
C-c o e         go-org-file-elfeed.org
C-c o i         go-org-file-index.org

Were I to use an anonymous function they would look like:

Global Bindings Starting With C-c o:
key             binding
---             -------

C-c o a         #<anonymous-function>
C-c o b         #<anonymous-function>
C-c o c         #<anonymous-function>
C-c o e         #<anonymous-function>
C-c o i         #<anonymous-function>

The named binding is much nicer. Yes there’s still duplication, but the next step would be a loop and iteration. Which might obfuscate what was going on.

As it turns out, I’m more concerned about the legibility than removing all of the duplication.

A commenter also reminded me of Emacs (Emacs 🔍)’s bookmark system. It’s not quite what I want in this moment, but I think it’s going to be quite close going forward. I’m overloading behavior in this gorg function; I’ll continue to think on how I’m using it.

Conclusion

I spent more time than I would have thought. I cribbed the conceptual macro from the Modus Themes’s manual.

It took a bit of time to stumble upon splitting the key/values via the car and cdr functions. I may have done things wrong, but I believe the gorg-sexp-eval macro wanted strings for each parameter.

All of this is to say, I learned something new today, and want to share it with you.

-1:-- Reducing Duplication in my Emacs Config (Post Jeremy Friesen (jeremy@takeonrules.com))--L0--C0--October 31, 2020 01:54 AM

Jeremy Friesen: Visual Changes to Take on Rules

Minor Tweaks to Colors and Hover State

, I started using the Modus Themes by Protesilaos Stavrou in Emacs. I wrote a bit about the Modus themes in Why I Chose Emacs as My New Text Editor .

As an exercise, I switched my site’s theme colors to those from the modus-operandi and modus-vivendi; The light and dark themes respecively.

Why the switch? As I don’t use a lot of images, I felt I wanted more colors. Previously, I used Adobe Color to help choose related colors, but I wanted to add a few more.

Throughout the site, I use a few standard yet not often used HTML elements:

Some elements are more frequently used than others.

  • abbr
  • blockquote and cite
  • details and summary
  • dfn
  • dl, dt, and dd
  • ins and del
  • mark
  • time

I found myself wanting to call out three elements that I find close in meaning:

abbr
connects an abbreviation and it's expanded word
dfn
the word/phrase defined by the following text
dt
a defined term followed by a definition detail (e.g. dd)

I also wanted to gently call out dates. Each date element has a machine readable datetime attribute, which is not presented by most browsers. I thought it would be nice to differentiate these elements.

And I’ve got blockquotes, marginalia, and updates to consider. Each somewhat unique in purpose. The actual mental “Ah Ha” moment was seeing Modus patch updates around docstrings and comments. I started thinking of updates and marginlia as comments. And blockquotes as somewhat analogous to a docstring. Namely two different colors for two different types of human readable elements of code.

As the number of elements I used grew, I didn’t initially expand the color pallette; I didn’t want a haphazard set of colors.

Separate from blogging, I started tinkering with my Emacs configuration of the Modus themes.

I noticed how the Modus themes colored my Org-mode (Org-mode 🔍) files, and found these changes welcome and legible. So I spent a bit of time refactoring my Cascading Stylesheet (CSS 🔍) to use Modus colors. Along the way I learned about CSS var.

I want the colors to spark a bit of curiosity. To nudge people to open up their Inspect Element to see what’s going on. My favorite aspect of web development is that the CSS and HTML are available for anyone to inspect; This transparency is what made the web what it is.

I also want the colors to not overwhelm you, the reader. Instead, I want the colors to be a companion in your reading Take on Rules.

And with Protesilaos’ attention to mixing colors, I felt comforable adding some hover behavior. My hope is that this hover behavior helps provide a useful visual clue.

For example, if you hover over the superscript side note numbers, this will highlight corresponding marginalia.

These changes, while subtle, bring me joy. I learned a little bit more about CSS and removed some duplication.

-1:-- Visual Changes to Take on Rules (Post Jeremy Friesen (jeremy@takeonrules.com))--L0--C0--October 25, 2020 12:39 AM

Jeremy Friesen: Why I Chose Emacs as My New Text Editor

Hint: I Took the Time to Build What I Needed

About , I was changing jobs. I was leaving the walled garden of an Integrated Development Environment (IDE 🔍) for a proprietary language that deployed to an IBM System iSeries (AS/400 🔍). We wrote in Report Program Generator programming language from IBM (IBM RPG 🔍) and Cool Plex, which looked a lot of meta-code and what I now know to be RDF Triples

At my new job, I was writing web-facing applications using open source technology and deploying to Linux. I needed to find an editor to help me with the task.

I spent a bit of time exploring my options. During that exploration, I learned of Emacs from Carl Meyer. Carl is a friend of mine from high school, and has contributed a lot to the world.

At the time I had three young children, I had just changed jobs, changed programming languages, and couldn’t wrap my head around Emacs. I wanted my editor to behave like other Graphical User Interface (GUI 🔍) applications.

I didn’t take the time to walk through the Emacs tutorial, and left Emacs behind.

A few months into my new job, I switched languages and paradigms again. In , my company hopped in a van and drove to Chicago to learn about Django (Django 🔍) and Ruby on Rails (Ruby on Rails 🔍). Within a week, our organization adopted Ruby on Rails.

At that time, I adopted Textmate (TM 🔍) as my editor. It had a beautiful user-interface and required little effort to learn. I wasn’t aware of how useful a shell environment could be; In the years I’d learn more. I had, during my professional life, often relied on GUI views into files, systems, and processes. So I didn’t have a mental model that would have further nudged me towards an integrated text editor. And in my hubris, I didn’t step through the Emacs tutorial.

At the time Textmate was closed source. I used it and loved it, but it began to lag. The search a project function started misbehaving and gridning projects to a halt. I found Sublime Text (Sublime Text 🔍) and switched. At the time, Sublime was positioning as a Textmate replacement.

Then, as I engaged more and more in open source projects, I started wanting an open source text editor. I learned about Atom in 2015. It was open source and acted enough like Sublime, that I switched. During this time, I dabbled with VIM Text Editor (vim 🔍). However, the modal nature of editing felt foreign. At times, I’d try out a tutorial, but it never stuck. I also thought about revising Textmate as the owner later released it as open source.

Forward to . I had begun noticing more and more bugs and breaks in Atom. Not one to fear changing editors, I started looking.

I also gave Visual Studio Code (VS Code 🔍) a spin, and found it disconcerting. First, it felt constraining and off-putting. The configuration ecosystem felt clunky. The plugins felt like an App Store. Everything felt like VS Code was trying to obfuscate it’s underlying systems.

Years ago, I started aspiring to write most of my commit messages following Tim Pope’s guidance on commit messages. To this day, I try to write meaningful commit messages, knowing that these commit messages will most often be close to the code.

Some may put that information in a pull request message on Github or Gitlab, but that information does not remain close to the code. However, that information is now inextricably tied to the details of Github. When I want to spelunk through my code, I don’t want to have to go to Github to get a view into what happened. I want my git annotate and git log to carry as much meaning as possible.

The proverbial last straw was the Git (git 🔍) prompt for commit messages. The prompt was a small input box; It didn’t encourage meaningful commit messages. Instead it encouraged terse commits. That feature alone told me that people using it will be encouraged to write bad commit messages. I didn’t want to be that guy with my text editor.

So, I spent a bit of time again testing vim and Emacs.

I again tried vim but it felt off. I use Vim when I shell out to a server. I haven’t done it much recently, because I’d likely use Emacs’s Tramp mode.

I tried Emacs, and worked my way through the Tutorial. It was the strong commitment to the Tutorial and honestly the writing of that tutorial that nudged me to dive further in. I spent some time fiddling with Doom or Spacemacs, but in the end settled on bare metal Emacs.

This proved crucial. As someone that’s used text editors for 15+ years, I know the features I’ve used. What I chose to do in Emacs was to complete the tutorial and start coding.

If I found myself wanting a feature, I took note of it. Then, I went and found the package or packages that implemented the feature. I spent quite a bit of time reading through Melpa, looking for a package. What happened is that I have built up my own editor that meets my needs.

Now, 5 months or so in, I’m fully loving the experience. The community of Emacs developers seem to have a higher commitment to writing documentation. Many Emacs developers write their configuration files using the paradigm of Literate Programming. In other words, they first write down their intentions for the software, then write the software.

Org Mode

Org Mode is the missing piece for my past text editors. Carstin Dominik took the time to build out functionality for organizing the non-coding tasks of software development, research, and writing. Were I to begin my blog anew, I’d leverage org-mode and ox-hugo for blogging.

Org Mode layers meaningful tools on top of plain text files; The syntax is close to Markdown, but different enough. The simplicity of structure makes the world of difference. With the plain text, I can run low-level Unix commands (e.g. grep, sed, etc.) but also have higher level programmatic access to the data.

Magit

Prior to magit, I almost always used command line tools for git. My previous workflow would be to use Terminal to state my git commits, and then my text editor to write commit messages. Atom’s slowness to open as a commit message editor was another reason I left Atom. I don’t want to wait multiple seconds to start writing a commit message.

Except for reading git logs, I now do most all git tasks with Magit. That includes an amazing interactive git rebase environment.

Fill Paragraph

I must mention this lowly command.

When Carl introduced me to Emacs, he showed me fill-paragraph. That functionality stuck with me. It’s nothing fancy, but it shows that Emacs treats column-width as a first class citizen.

And why is column width important? First, re-read the note about commit message.

Conforming to that structure helps ensure that your git log excursions are not unduly messy. It also helps if you’re going to interact with the command-line. In other words, automatic word-wrapping is nice, but its not universal, nor does it work in all contexts.

I have used this command to quickly wrap documentation so as to not have it flow off the screen. For coding buffers, I disable word wrapping. I also aspire to 120 character line width for code and 80 character line width for comments. Why the variance? Comments should read more like prose, and long running lines make the paragraphs harder to read.

It ain’t much of a command, but I’ve held a candle for it since I learned about Emacs.

Swiper

I never knew I wanted Swiper until I stumbled onto it. I now use it all of the time; It’s even replaced my default Find behavior in Emacs.

What does it do? I type Ctrl+S In Emacs parlance, that’s C-s. and start typing a word. In a mini-buffer at the bottom of the current buffer, I see the lines that include that word. It’s a bit like a find with context. Importantly, this doesn’t move my cursor in the main buffer.

So I end up quickly referencing something and get back to typing. Or, I can navigate through the mini-buffer and jump to that location in the main buffer. Quite slick.

WGrep-Ag

The wgrep-ag package sort of blew my mind. It allows you to use The Silver Searcher (ag 🔍) with wgrep to edit search results

Follow along carefully:

In Emacs, I search a project using ag. Emacs renders the search results in a mini-buffer. In this case, the mini-buffer is a small set of rows at the bottom of Emacs that show a subset of the results.

With the mini-buffer active (e.g. I’ve been typing results there), I invoke ivy-occur. That function opens all of the search results in a read-only buffer. While I was writing this example, I thought to myself “I wonder if I can use ivy-occur from Swiper results? Yes I can. So I learned something while explaining something.

With this new buffer, I invoke the function wgrep-change-to-wgrep-mode. This toggles ivy-occur buffer into an edit mode. I begin editing the search results as though it were it’s own file.

Then I save the edits, and wgrep-ag writes all of those changes back to the found results.

Another way to think of it, wgrep-ag loads a semi-structured buffer. Each row has three fields: file name, line number, and line text. I can use wgrep-ag to write those changes back to the originating file.

Seriously, this functionality amazes me.

Multiple Cursors

Textmate first introduced me to this powerful concept. Since then, this functionality has been a mandatory feature of my editors.

Two packages help deliver on this:

  1. iedit - by default, if I type Ctrl+; (e.g. C-;), the iedit package highlights each occurence of the word. I can now type and iedit updates all occurrences.
  2. multiple-cursors - this package provides finer grain control, and allows me to set a cursor on ten contiguous lines and start typing.

Expand Region

I didn’t know what I was missing until I installed expand-region. Now with Ctrl+= (e.g. C-=) my cursor expands to the smallest logical region (e.g. highlighting a word), typing it again expands that region (e.g. highlighting the sentence), etc. And Ctrl+Shift+= (e.g. C-+) contracts the region.

Org Roam

Building on org-mode, org roam incorporates note taking paradigms inspired by Zettelkasten.

I’ve used this to write up campaign notes for my New Vistas in the Thel Sector campaign. The bi-directional link and quick note capture tools make for a dream in information management.

In a way, it creates a Wiki that I can use Emacs to edit and navigate.

update

YASnippet

I forgot to mention YASnippets. Back in the day, I wrote a few snippets in Textmate, ported them to Sublime, and reworked them in Atom. With YASnippets, I’ve found an easy to use snippet manager.

The documentation is easy to follow. I wrote up a handful of snippets to help with blogging. The following snippet provides guidance for making a custom Hugo Shortcode:

{{< update date="`(format-time-string "%Y-%m-%d")`"
  mode="${2:$$(yas-choose-value '(
    "inline"
    "marginnote"
    "paragraph"
    "sidenote"
    "section"))}"
>}}
  $0
{{< /update >}}

This snippet builds the parameters for my update shorcode. It sets today’s date and provides a list of valid modes for the shortcode.

update

Modus Themes

The Modus Themes by Protesilaos Stavrou have become my go to themes.

It’s been a bit of a circuitous path to get to this point, but it’s been worth it. Protesilaos is developing the Modus themes with the following guiding principle:

Accessible themes for GNU (GNU 🔍) Emacs (Emacs 🔍), conforming with the highest accessibility standard for colour contrast between background and foreground values ( Web Content Accessibility Guidelines (WCAG 🔍) AAA).

In past editors, I’ve often used a dark theme. I made a Vibrant Ink Ruby Atom text editor (Atom 🔍) theme that works with Hugo (Hugo 🔍). This also involved a few tweaks to a language-markdown plugin for Atom. In my current office setup, I often need a light theme. In my current setup, a dark theme often truns my glossy monitor into a mirror. But sometimes, I want a dark theme. And I don’t want the shift between dark and light to highlight the code via a different scheme.

Enter the Modus Themes:

  • modus operandi for light
  • modus vivendi for dark

Modus Operandi: a light theme

Modus Vivendi: a dark theme

I’ve grown quite fond of the color palette. I also appreciate the work Protesilaos is doing on visually conforming to WCAG. I’m going to look at leveraging that work to refresh the color palette of my website. But that’s an adventure for another day.

Conclusion

This year, Emacs drew me in. As I’ve entered my 3rd decade of software development, I’ve grown to appreciate tutorials, documentation, and owning my tools. Emacs delivers on all of that. It’s even as old as I am.

What I’ve found is I spend more and more time in Emacs, as it does more and more of what I want, all building from the basis of text files and directories.

-1:-- Why I Chose Emacs as My New Text Editor (Post Jeremy Friesen (jeremy@takeonrules.com))--L0--C0--October 18, 2020 03:07 PM

Jeremy Friesen: Principles of My Text Editor

Once Again Exploring a Different Solution

update

I’ve been using Emacs (Emacs 🔍) since , and wrote about the Emacs packages I am currently using. Over the weeks and months, I’ve configured Emacs to suit my needs. I’ve reworked the colors of my site to match an Emacs theme.

At this point, I’m happy, enjoying my text editor, and can’t see switching.

I’m preparing to leave behind Atom. It’s been a good run, but it’s not meeting my needs.

As I was practicing using Emacs, I found myself looking into configurations and plugins. Before I got too far, I wanted to write-down why I’m going through this exercise.

First, my guiding principles as I explore a new text editor.

Principles

  • It must be Free Open Source Software (FOSS 🔍).
  • It must have a sizeable community of adopters.
  • It should not be based on Electron.
  • It must be extensible, preferrably via a scripting language.
  • It must have good documentation.
  • It must have a keyboard first focus. Yes, I will use a mouse, but I want to favor my keyboard.
  • It must run in Linux or MacOS.
  • I must be able to store its configuration in source control.
  • I must be able to share the configuration between MacOS and Linux.

Let’s talk a bit about Electron. First, in my observation, Electron based applications devour resources. Second, by their nature, they must keep pace with browser evolution ( Hypertext Markup Language (HTML 🔍), Cascading Stylesheet (CSS 🔍), and Javascript). Maybe it’s me, but Javascript-based systems require a ridiculous amount of ongoing maintenance and weeding. Have a different opinion? Let me know.

And I would like if the tool and ecosystem “jived” with my philosophy and mental models.

Functionality

Tabs

When working on a project or group of files, I often have multiple tabs open in my text editor. At the top of my editor, I can see what tabs are open. I can use number keys (plus a modifier) to open that tab. As a bonus, I can click on the tab to open it.

Oddly, I don’t often use split pane, but that feature is nice when possible.

Invisible Characters

I often work with data from disparate sources. And sometimes those come with little surprises. I need to see the invisible control characters that may be impacting that file.

Fuzzy File Opening

Since my Textmate days of yore, I’ve relied on CMD+t to open a file finder; The file finder used a fuzzy match for file names. I don’t want to type the filename exactly, instead I want to start typing. The candidate results should have those typed letters, but they need not be contiguous. Further typing narrows the candidate pool.

For example, if I have three files: README.md, red-rom.txt, and ruby.rb, when I type “R”, all three files would match. Then “re” would match the first two, and finally “rex” would match only red-rom.txt

Keymapping

Already, as I’m writing this, I have Ctrl + d mapped to a duplicate line function. In other systems Ctrl + d will delete a character. That is an engrained keystroke incantation that I need to think through what I want.

I also make heavy use of Ctrl + w to highlight a word, then delete it. Right now, my Ctrl + w in emacs invokes “kill-region”, which is very aggressive compared to my current Atom keymap.

I should have the ability to re-map keys, see what keys are already mapped, and even unbind mapped keys.

File Browser

In the last three editors I’ve used (Atom, Sublime, and Textmate) all of them provided a file browser area. That area showed me all of the files in the current editor’s context. Typically this was the directory structure from which I opened the editor.

The file browser helps provide reminders and orientation to the structure of the code-base.

Git Commit Message

I can live without integrated git. However, I want to use my text editor for writing commit messages. When I run git commit from the command line, it should launch the text editor in a timely manner (350ms or less).

I aim to write good commit messages. The Seven Rules of a reat Git Commit Message One of those rules is that the first line of a commit message should be no more than 50 characters. And the body wrap at 70 characters. My text editor should help me structure good commit messages.

Any integrations of the text editor and git should follow the same above listed principles.

For a week, I tinkered with VS Code. And aside from its somewhat off UI, I could not tolerate VS Code’s commit message input box. The box encouraged very short commits, which I find violates the aforementioned Seven Rules.

Launch from Terminal

I should be able to launch my text editor from the terminal. Either at the current directory or at a location specified as a parameter.

Regular Expression Search/Replace

I must be able to search via regular expression and change text via regular expressions. I use this quite often through Atom. Though over the last month, I’ve noticed that it’s been somewhat inconsistent. I’ve had cases where the regular expression updated 80% correctly, but left token markers (e.g., “$1”) in other updates. For example, if I had a regexp of /(up|down)/ and replaced it with go-$1, in 80% of the cases, I’d see ‘go-up’ or ‘go-down’ but in other cases I’d see ‘go-$1’. That flakey behavior is one of the drivers for leaving Atom.

Other Nice to Haves

I love my current Atom Markdown Preview package. That would be nice to duplicate. Likewise for PlantUML preview.

I often use a multi-cursor edit function. I mark different spots in a document (usually via find) and start typing my change. Or, I’ll mark 10 consecutive lines at the same column and start editing there. If I invoke a cursor moving command (e.g., go to end of word), then the mark moves to all end of words.

Ideally, when I’ve gained some proficiency, I want people watching me using my text editor to be just a little bit lost. In other words, I need my text editor to keep up with my pace of thought.

Conclusion

These are my guiding principles and some of the features I require. I’m certain there are others that I’ve internalized, and not yet explicitly acknowledged.

What sold me on giving Emacs a go was its strong commitment to documentation. I’ve tried Vim several times and it never quite sat well. I can use Vim well enough to update text in a file, but have found it to work exceptionally well for my brain.

The shipped tutorial is amazing, using accessible language and seeking to gently guide you through the ecosystem. I believe one more trip throug the tutorial and the basics would gel.

The invocation C-h k lets me look up what that key combination does. The C-h f helps me go hunting for commands that I may not remember.

Postscript

I’m currently exploring Emacs, and went through the fantastic tutorial that ships with Emacs. When you install Emacs, type C-h t to start the tutorial. (Type the “control” and “h” key at the same time, then type the “t” key.)

I made it through 60% of the tutorial, and then started poking around with plugins and configurations. I went so far as to install Doom emacs. I found myself poking around in what I could do, instead of building from a small start.

I removed Doom and went back to my simpler ~/.emacs file. I’m sure as I explore, I’ll look to other packages or configuration frameworks.

In otherwords, this is my reminder to build up from a smaller foundation, and own each brick that I place.

-1:-- Principles of My Text Editor (Post Jeremy Friesen (jeremy@takeonrules.com))--L0--C0--May 25, 2020 02:58 PM

Jeremy Friesen: Switching from Inoreader to Newsboat for RSS Reader

update: In , I switch my everyday text editor Emacs (Emacs 🔍). In , I began exploring Elfeed, an Emacs plugin. In doing most of my work in Emacs, I find it convenient to have an RSS feed right in the text editor. Newsboat remains a solid RSS reader, its just I’ve found something closer to my toolkit.

update: Two weeks into using Newsboat, and I’m loving it. Because of the text-based RSS reader, I discovered that my site’s RSS feeds were publishing items that had relative Uniform Resource Locators (URLs 🔍). Which could’ve resulted in broken URLs for RSS subscribers. From a habit stand-point, I’m checking my RSS feeds twice a day: once during breakfast and once in the evening.

At present, I have a paid subscription to Inoreader. It provides both a web-interface as well as a mobile app, synchronzing read content between the two.

But, I’ve noticed that I graze on my Rich Site Summary (RSS 🔍) feed on my mobile device; Swiping through articles out of boredom.

This past week I began experimenting with Newsboat as my local RSS client. I exported my Outline Processor Markup Language (OPML 🔍) document from Inoreader. I then imported that OPML into Newsboat.

Visual Tour (for a text-based RSS Client)

First and foremost, Newsboat has a text-based command-line interface. You will not see images in your feed. I have configured Newboat so I can open any link or article in Firefox using a shell script

Instead of adding screenshots with the minimal colors, I’ll copy and paste each screen. For those curious, here is a link to my Newsboat configuration.

Feed List Dialogue

Newsboat loads a Feed List dialogue. In my configuration I have four columns configured: Index, Read/Unread, Unread/Total, and Feed Title. By default I hide feeds I’ve already read (by the config show-read-feeds no).

newsboat 2.19.0 - Your feeds (8 unread, 8 total)
   1 N     (1/459) Blogroll for Take on Rules
   3 N    (12/111) Recent articles
  82 N      (1/18) Alex Schroeder: Diary
 198 N     (3/113) Indie RPG Planet
 199 N     (5/206) Old School RPG Planet
 590 N      (1/26) Skullsword's Tower
 954 N      (1/26) Den of the Lizard King
1201 N      (1/27) msjx

q:Quit ENTER:Open n:Next Unread r:Reload R:Reload All A:Mark Read C:Mark All Read /:Search ?:Help

A Newsboat query feed populates the “Blogroll for Take on Rules”. The “Blogroll for Take on Rules” queries all feeds tagged with “blogroll” and aggregates those articles.

Here’s a sample from my urls.txt:

"query:Blogroll for Take on Rules:tags # \"blogroll\""
https://alexschroeder.ch/wiki/feed/full/ "Gaming/RPG" blogroll
https://gohugo.io/index.xml "FOSS"

In the above Feed List dialogue, “Alex Schroeder: Diary” shows as its own feed (index 82) and Newsboat counts the articles from Alex’s blog in the “Blogroll for Take on Rules.”

Article Dialog

The Article Dialog renders the textual content and footnotes each link.

Below is an excerpt from Alex Schroeder’s The effects of stats in simple games.

newsboat 2.19.0 - Your feeds (8 unread, 8 total)
Feed: Alex Schroeder: Diary
Title: The effects of stats in simple games
Author: kensanata@gmail.com (Alex Schroeder)
Date: Sun, 12 Apr 2020 09:15:56 -0400
Link: https://alexschroeder.ch/wiki/2020-04-12_The_effects_of_stats_in_simple_games

If you’ve played OD&D, or Swords & Wizardry, or
any of the other old school games out there, you
have encountered this before, regarding monster
stats: monsters have hit dice (HD); this
determines how many hit they can take, on average.
Each hit die is 1d6, and each damage die is also
1d6. In later games, the picture is not that
simple anymore. In 
Dungeons and Dragons: Basic/Expert (B/X 🔍) we already notice that many
fighters have a strength bonus, but monsters also
use 1d8 for hit dice. For the purpose of this blog
post, I don’t care about the details.

I care about this relationship: every extra 
Hit Die (HD 🔍)
allows a monster to survive one more hit; every
extra HD also allows a monster to hit more easily;
extra HD also allow a monster to better resist
spells.

What does it mean to survive more hits? It means
that fights take longer, or that monsters can take
on more enemies. It’s not that simple because the
monster is also better at hitting the opposition.
Without that twist, it’d be boring: four
characters hit a monster with 4 HD and it’s dead;
four characters hit monster with 8 HD and it takes
two rounds to kill. The difference is that the 8
HD monster probably deals more damage to the
characters. Most likely it also has special
abilities that make it even more dangerous.

The net effect is hard to predict and that’s
probably what makes the game interesting.

I’m trying to apply the same kind of analysis to
Just Halberds[1].
…

Links:
[1]: https://alexschroeder.ch/wiki/Just_Halberds (link)
…

When reading the article, I can type o to open the article in Firefox (as per my configuration). I can also type 1 to open the first link (e.g. https://alexschroeder.ch/wiki/Just_Halberds). I can also type s to save the article to my machine.

The saved articles are either “of future interest” or something which I may write a response. Regardless, I have a plain-text copy of the article for future reference.

Deeper Level Hacking

Newsboat stores the articles in a local SQLITE database. If I want, I can run queries against that database. Maybe I want to find all of the links to a given site throughout the articles? Easy.

As part of my publish process, I now use my URL file for Newsboat to populate the blogs in my Blog Roll. Prior to Newsboat, I manually managed the Blog Roll seperately from the Inoreader feeds.

Conclusion

With Newsboat, I’m able to continue using RSS. I can now consider leaving a paid subscription (which is certainly using my data as part of the surveillance capitalism paradigm). I had explored using NetWireNews, which provides a nice Graphical User Interface (GUI 🔍), but didn’t quite work for me.

I also get the ability to write scripts against the entire ecosystem. I can specify the directory for the SQLite cache database, the file to use for URLs, and where to save the plain text articles.

In other words, Newsboat gives me direct and unfettered access to the inputs and outputs of the entire RSS experience.

-1:-- Switching from Inoreader to Newsboat for RSS Reader (Post Jeremy Friesen (jeremy@takeonrules.com))--L0--C0--April 12, 2020 02:42 PM

Michael Kazarian: What is Python's biggest disadvantage?


1) Python seems simple at first glance. There are a lot of points that always need to keep in mind. Since 2003 I used Python because it was simple. Since 2016 I use Emacs Lisp instead of Python for disposable tasks, prototypes and text processing.
2) Backward incompatibility of language and libraries more than of Java, Delphi or Emacs.
3) Dynamic typing per se. This is good for little project/team but, it is a pain in another case. I wrote a library for type checking in a big project.
4) Dynamic typing implementation makes Python slow. Even slower than Emacs Lisp. Common Lisp implementations prove that dynamically typed languages could be faster.
-1:-- What is Python's biggest disadvantage? (Post Michael Kazarian (noreply@blogger.com))--L0--C0--January 28, 2019 08:09 AM