- Emacs Lisp 98.5%
- YASnippet 1%
- CSS 0.4%
| contrapunctus | Update calls of renamed function | |
| my-init | Configure nicer rendering in Org | |
| snippets | Add defcustom snippet | |
| .gitignore | Add snippets | |
| bookmarks | Add bookmarks | |
| init.el | Fix startup errors | |
| Makefile | Remove lint targets | |
| README.org | Apply uncommitted changes from Org LP branch | |
| templates | Add template for destructuring-bind | |
| UNLICENSE | Add Unlicense | |
Some background is important to understand my keybinding choices -
- I use modifier-less bindings wherever possible. Hydra and modal editing make up most of my interaction with Emacs.
- 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.
- I use the Dvorak layout, which reduces uncomfortable hand movements. The difference in comfort and hand fatigue over long typing sessions is very significant.
-
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 -
- 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),
- ...nor need the finger make an awkward jump to QWERTY H on each left-moving operation.
- 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.
- M-n, M-p, -> M-r, M-c
- M-q -> M-'
- M–
- multiple-cursors
- 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.
- M-n M-n not getting bound to font-lock-fontify-block ?
- 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 -
enlarge-window,enlarge-window-horizontallysave-buffer,kill-buffer-
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.
toggle-debug-on-error- "insert" hydra, for timestamps, dates; in Elisp, insert as strings; in Org, in angular brackets; etc
- 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)))