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:
- At the end of the day, this is where I put tomorrows expected work.
- This is the org file that manages my
elfeed's feed list; It's analogue to an Outline Processor Markup Language (OPML 🔍) file.
- This is the index into the multitude of org files that comprise my Org Roam repository.
- Following the slip-case method, I'm capturing ideas and how they stack in my virtual slip-case.
- This is a list of all the bibliographic cards that I've filed away.
- 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.
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.
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-mapis the variable name for the map used for characters that follow C-x.
help-mapis for characters that follow C-h.
esc-mapis for characters that follow ESC. Thus, all Meta characters are actually defined by this map.
ctl-x-4-mapis for characters that follow C-x 4.
mode-specific-mapis for characters that follow C-c.
Curious about what all was mapped to
mode-specific-map, I looked it up.
I use the
The function for
C-c o i was registered as
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.
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.
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
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.