1
1
Fork
You've already forked dotemacs
0
No description
  • Emacs Lisp 98.5%
  • YASnippet 1%
  • CSS 0.4%
Find a file
2026年06月23日 20:32:35 +05:30
contrapunctus Update calls of renamed function 2026年01月13日 04:41:07 +05:30
my-init Configure nicer rendering in Org 2026年06月23日 20:31:18 +05:30
snippets Add defcustom snippet 2022年02月11日 13:03:04 +05:30
.gitignore Add snippets 2022年01月24日 00:32:22 +05:30
bookmarks Add bookmarks 2023年03月02日 13:48:24 +05:30
init.el Fix startup errors 2026年01月03日 21:08:40 +05:30
Makefile Remove lint targets 2025年12月30日 00:24:43 +05:30
README.org Apply uncommitted changes from Org LP branch 2025年12月31日 02:26:10 +05:30
templates Add template for destructuring-bind 2026年06月23日 20:32:35 +05:30
UNLICENSE Add Unlicense 2021年07月09日 10:32:06 +05:30

Some background is important to understand my keybinding choices -

  1. I use modifier-less bindings wherever possible. Hydra and modal editing make up most of my interaction with Emacs.
  2. I use Kmonad to have Space act as Control when held. This turns Control into the modifier which disrupts the typing position the least. It is also held with the thumbs - the strongest digits - reducing fatigue. So if I must use a modifier, I use Control.
  3. I use the Dvorak layout, which reduces uncomfortable hand movements. The difference in comfort and hand fatigue over long typing sessions is very significant.
  4. I've used Vim and Evil for the longest time, but I like Boon's movement layout best -

    • QWERTY IO move up and down
    • QWERTY KL move left and right in small units (characters), and
    • QWERTY J; move left and right in larger units (words or s-expressions)

    This has several benefits over HJKL -

    1. To move left, the wrist need not to be angled awkwardly to sit on QWERTY HJKL (which also means the keyboard's tactile nub on J does not sit under the index finger),
    2. ...nor need the finger make an awkward jump to QWERTY H on each left-moving operation.
    3. The relaxed position of the fingers placed on a flat surface is a curl, not a line, which is reflected in Boon's default JIO; position.

Dvorak

At first I decided to remap all Emacs, Boon, and Hydra bindings, but it turned out to not be my idea of fun. I wrote boon-dvorak, and let [most of] the others be.

(QWERTY) C-w, C-u, and C-h are probably best left as they are, since they are also present in other applications.

  1. M-n, M-p, -> M-r, M-c
  2. M-q -> M-'
  3. M–
  4. multiple-cursors
  5. boon-c-god is bound to "j", which emulates QWERTY "c", but the bindings after it do not. Thus, to input "C-c C-c", one must press "j c" in Dvorak. Yuck.
  6. M-n M-n not getting bound to font-lock-fontify-block ?
  7. binding C-d to keyboard-quit in the minibuffer

Hydra

I started off using Hydra for programming modes, when I noticed that Elisp, Common Lisp, and Scheme all had some semantically-analogous operations with different names, which could be abstracted away behind a generic interface. Then, around the time I got into using Org for literate programs, I added an Org hydra, and then a general hydra for frequently-used operations.

Add these common operations to the hydra -

  1. enlarge-window, enlarge-window-horizontally
  2. save-buffer, kill-buffer
  3. switch to last buffer, such that pressing it twice brings you back to the original buffer. Bind to "m" (same key as the one to launch the Hydra), moving magit to "M".

    • Trickier to implement than I thought.
  4. toggle-debug-on-error
  5. "insert" hydra, for timestamps, dates; in Elisp, insert as strings; in Org, in angular brackets; etc
  6. remove duplication

Examples of querying Chronometrist data

Total time spent on a task

(defun my-chronometrist-query-to-string (predicate)
 "Query Chronometrist database for records matching PREDICATE.
Return a string containing the human-readable duration and the
number of matching records."
 (cl-loop with dates
 with match-count = 0
 for plists being the hash-values of (chronometrist-to-hash-table (chronometrist-active-backend))
 using (hash-keys date-iso)
 sum
 (cl-loop for plist in plists
 when (funcall predicate plist)
 sum (chronometrist-interval plist) into sec
 and do (cl-pushnew date-iso dates :test #'equal)
 and do (cl-incf match-count)
 finally return sec) into seconds
 finally return
 (format "%s, over %s days. (%s matches)"
 (format-seconds "%Y, %D, %H, %M, and %S%z" seconds)
 (length dates)
 match-count)))
(my-chronometrist-query-to-string
 (lambda (plist)
 (equal (plist-get plist :project) "osm-ea")))
(my-chronometrist-query-to-string
 (lambda (plist)
 (and (equal (plist-get plist :name) "Programming")
 (equal (plist-get plist :component) "Common Lisp port"))))
(my-chronometrist-query-to-string
 (lambda (plist)
 (and (equal (plist-get plist :name) "Programming")
 (or (equal (plist-get plist :project) "osm-dm")))))
(my-chronometrist-query-to-string
 (lambda (plist)
 (and (equal (plist-get plist :name) "Programming")
 (equal (plist-get plist :project) "osm-dm-cli"))))
(my-chronometrist-query-to-string
 (lambda (plist)
 (and (equal (plist-get plist :name) "Programming")
 (or (equal (plist-get plist :project) "osm-dm")
 (equal (plist-get plist :project) "osm-dm-cli")))))
(my-chronometrist-query-to-string
 (lambda (plist)
 (let ((song (plist-get plist :song)))
 (and
 (equal (plist-get song :name) "कोई हमें बताएगा क्या?")
 (or (and (equal (plist-get plist :name) "Composing")
 (member 'accompaniment (plist-get plist :tags)))
 (equal (plist-get plist :name) "Sequencing"))))))
(my-chronometrist-query-to-string
 (lambda (plist)
 (let ((song (plist-get plist :song)))
 (and
 (equal (plist-get song :name) "आदि काल से आज तलक")
 (or (and (equal (plist-get plist :name) "Composing")
 (member 'accompaniment (plist-get plist :tags)))
 (equal (plist-get plist :name) "Sequencing"))))))

Intervals and durations for task Exercise

(defun my-chronometrist-query-to-buffer (predicate)
 "Return all records matching PREDICATE as a list of plists."
 (cl-loop for plist in (chronometrist-to-list (chronometrist-active-backend))
 with count = 0
 when (funcall predicate plist)
 collect
 (let* ((plist (copy-list plist))
 (interval-seconds (chronometrist-interval plist))
 (start (plist-get plist :start)))
 (plist-put plist :duration (ts-human-format-duration interval-seconds))
 (plist-put plist :date (seq-take start 10))
 (chronometrist-plist-remove plist :start :stop))))
(my-chronometrist-query-to-buffer
 (lambda (plist)
 (equal (plist-get plist :project) "StreetComplete")))

Unique key-values for task "Exercise"

(cl-loop with task = "Exercise"
 for plist in (chronometrist-to-list (chronometrist-active-backend))
 when (equal (plist-get plist :name) task)
 collect (chronometrist-plist-key-values plist) into key-values
 finally do
 (let ((buffer (get-buffer-create (generate-new-buffer-name "chronometrist-query"))))
 (with-current-buffer buffer
 (->> (cl-remove-duplicates key-values)
 (seq-filter #'identity)
 (format "%S")
 (insert))
 (emacs-lisp-mode)
 (switch-to-buffer buffer))))

Running data from the 13th of December 2021

(cl-loop with start = (parse-iso8601-time-string "2021年12月13日T00:00:00+05:30")
 with buffer = (get-buffer-create "*chronometrist-query*")
 with output
 for plists being the hash-values of (chronometrist-to-hash-table (chronometrist-active-backend))
 using (hash-keys date-iso)
 when
 ;; create a list of strings for each date on which I ran
 (cl-loop with first-line = t
 with date = (parse-iso8601-time-string (concat date-iso "T00:00:00+05:30"))
 for plist in plists
 when (and (plist-get plist :running)
 (or (time-less-p start date)
 (time-equal-p start date)))
 collect
 (let* ((plist (copy-list plist))
 (duration-seconds (chronometrist-interval plist))
 (start (plist-get plist :start))
 (distance (plist-get plist :running))
 (distance-int (car distance))
 (distance-km (/ (float distance-int) 1000))
 (duration-hours (/ (float duration-seconds) 60 60))
 (distance-string (format "% 4s %s" distance-int (cdr distance)))
 (speed-string (format "%.2f" (/ distance-km duration-hours))))
 (format "%s%s in % 5s (% 5s kmph)\n"
 (if (not first-line)
 (make-string (+ 3 (length date-iso)) ?\s)
 (setq first-line nil)
 (concat date-iso " - "))
 distance-string
 (format-seconds "%m:%02s" duration-seconds)
 speed-string)))
 collect it into lists
 ;; Add indices for each date, and indentation when there are >1
 ;; instances of running in a date
 finally do
 (setq output
 (cl-loop for list in (reverse lists)
 count list into index
 concat
 (let* ((index-string (format "%2s. " index))
 (indent (if (> (length list) 1)
 (make-string (length index-string) ?\s)
 "")))
 (concat index-string (first list) indent
 (mapconcat #'identity (rest list) indent)))))
 (with-current-buffer buffer
 (erase-buffer)
 (insert output)
 (text-mode)
 (switch-to-buffer-other-window buffer)))