My Emacs Configuration
Table of Contents
- 1. Define Header
- 2. Check Preconditions
- 3. Configure Package Management
- 4. Enable Desktop Preservation
- 5. Customize Styling
- 6. Define Self Inserting Characters
- 7. Bind Some Low-Level Commands
- 8. Provide Some Basic Editing
- 9. Support Moving Point Around Within a Buffer
- 10. Change View of Buffer
- 11. Play With Mark
- 12. Work with Kill Ring
- 13. Provide Some Contextual Editing
- 14. Generate Content
- 15. Searching and Potentially Replacing
- 16. Support Some Inter-Process Communication
- 17. Provide Some Global Dispatching
- 18. Manage View Objects
- 19. Help Me!
- 20. Bookmark Management
- 21. Define Some Minibuffer Bindings
- 22. Define Some Navigation Through a Project
- 23. Interact With Source Control
- 24. Configure Org Mode
- 25. Configure Dired Mode
- 26. Configure Org Agenda
- 27. Configure hexl Mode
- 28. Configure Info Mode
- 29. Configure Emacs Lisp
- 30. Manage Environment for Called Commands
- 31. Structurizr Mode
- 32. Programming Support
- 33. Configure Lilypond
- 34. Configure Gnus
- 35. Configure EWW
- 36. Queue
I use my Emacs configuration both as a means to customize it and to document standard behavior. As a result much of it will strictly unnecessary.
I'll also be looking to build up the configuration incrementally along with supporting knowledge and will start with integrating desired functionality directly into this file rather than pulling in external packages. This ties in with an overall goal I have in software in general to make consistent reuse of abstractions, and also can hopefully address a concern I've had with Emacs in particular in the past where it felt as though there was both too much code floating around the ecosystem and too little readily composable functionality.
I'm currently using ^-
as a prefix for my local definitions.
This is inherited from a past project where I started to create a lot
of macros and since the "macs" in Emacs is for macros I was thinking
of it as macros squared…so ^-
is in the spirit of that while also
being nice and short.
Over time I'll likely also look to evolve the configuration so that it is increasingly declarative data structures operated upon by fewer functions. This may ease reuse of the same configuration across multiple targets as the number of operations to support will be reduced.
1. Define Header
Define some standard header boilerplate like copyright… most significantly define lexical binding.
;;; init.el -- Configure emacs to my liking. -*- lexical-binding: t; -*- ;; Copyright (C) 2023 Matt Whipple <mattwhipple@acm.org> ;; Author: Matt Whipple <mattwhipple@acm.org> ;; Maintainer: Matt Whiple <mattwhipple@acm.org> ;; Keywords: config ;; Package: ^-config ;; Package-Version: NA ;; Package-Requires: ((emacs "29.1") (dash "2.19.1") ;; Filename: init.el ;; This file is not part of GNU Emacs. ;; This is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This software is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this file. If not, see <http://www.gnu.org/licenses/>. ;;; Commentary: ;; This file will collect my configuration and documentation of emacs behavior, ;; along with being the initial location of any extensions on which I work. ;;; Code:
2. Check Preconditions
I'll be making use of whatever seems readily available rather than carefully tracking what features are available on what systems, but as the lack of certain capabilities translate into errors on some system or other I'll add tests with more helpful messages here. If any of this code ends up being split out into libraries this should be handled with more discipline (and compatibility options should be explored).
2.1. JSON Support
This is required for EAF stuff and seems generally useful.
(unless (fboundp #'json-parse-buffer) (warn "Emacs does not have JSON support!"))
3. Configure Package Management
I'm currently ideally being cautious about pulling in packages due to the more ambitious goal of building out a more unified platform, but some packages will be embraced and others will be used out of pragmatism (particularly for professional work).
Here I'll configure some of the standard repositories.
(require 'package) (setq package-archives '(("gnu" . "https://elpa.gnu.org/packages/") ("melpa" . "https://melpa.org/packages/")))
4. Enable Desktop Preservation
Use desktop mode to save and restore desktops across invocations.
(desktop-save-mode)
5. Customize Styling
5.1. Show Some Stuff
I often have Emacs running full-screen so it's nice if it just provides me info that I need in the status bar such as what time it is and how much time until my battery will die and maybe other things that aren't time related.
(display-battery-mode) (display-time-mode) (setq display-time-day-and-date t)
5.2. Hide Some Stuff
Currently I'm very keyboard oriented and don't want to waste previous screen real estate so I'd rather get rid of user interface elements intended for other forms of interaction. I may revisit this as I look at using Emacs across a wider range of devices.
(tool-bar-mode -1) (menu-bar-mode -1)
5.3. Go Dark
I (like many people) generally prefer themes with dark backgrounds. Emacs in particular looks particularly primitive with the default white GUI background whereas on black it looks at least a bit more retro. While I may be tempted at some point to fiddle with this a bit more (especially given that it comes with some disclaimers) I'll start with some code I stumbled upon within the Emacs code base (I think frame.el…I should link it when I come across it again).
(define-minor-mode dark-mode "Use light text on dark background." :global t :group 'faces (when (eq dark-mode (eq 'light (frame--current-background-mode (selected-frame)))) ;; FIXME: Change the face's SPEC instead? (set-face-attribute 'default nil :foreground (face-attribute 'default :background) :background (face-attribute 'default :foreground)) (frame-set-background-mode (selected-frame)))) (dark-mode)
5.4. Tweak Mode Line
I don't remember what this setting does specifically, but I had it sitting around in an old configuration and figured it was worth pulling in to figure out later.
(setq-default mode-line-format (list "%b L%l %p " mode-line-misc-info))
6. Define Self Inserting Characters
Define and bind chose keys that result in the associated character being inserted. This is particularly standard behavior as provided by the terminal though Emacs does add some custom logic. These are defined in a variable for subsequent specialization (there is likely a more idiomatic way to do that such as using remap but I haven't gotten that far yet.
This makes use of the relatively new keymap-set
function which
replaces define-key
. Elsewhere will primarily use the `bind-key`
family of macros but this make more direct use of the core
functionality as it lends itself more readily to composition (and
there's no clear value in using anything fancier). I'd generally be on
the fence about using bind-key
but since it's now included in Emacs
it's hard to resist. I'm fairly likely to switch that out later in
favor of something simpler like define-keymap
as I build out my Emacs knowledge.
(setq self-insert-keys '( "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z" "N" "O" "P" "Q" "R" "S" "T" "U" "V" "W" "X" "Y" "Z" "0" "1" "2" "3" "4" "5" "6" "7" "8" "9" "." "-" "$" "SPC" "=" "_" "*" "\"" "'" "`" "+" "~" "?" "!" "@" "#" "%" "^" "&" "|" "," ";" ":" "(" ")" "{" "}" "[" "]" "<" ">" "/" "\\" )) (dolist (k self-insert-keys) (apply #'keymap-set (list global-map k #'self-insert-command)))
7. Bind Some Low-Level Commands
Define some functions that seem relatively fundamental to dealing with commands in general.
(bind-keys :map global-map ("C-g" . keyboard-quit) ("M-x" . execute-extended-command) ("M-:" . eval-expression))
8. Provide Some Basic Editing
8.1. Insert Content
(bind-keys :map global-map ("C-q" . quoted-insert) ("RET" . newline) ("C-j" . electric-newline-and-maybe-indent) ("TAB" . indent-for-tab-command) ("C-o" . open-line))
8.2. Remove Content
(bind-keys :map global-map ("DEL" . backward-delete-char-untabify) ("M-DEL" . backward-kill-word) ;; Chrome OS support ("<deletechar>" . backward-kill-word) ("C-d" . delete-char) ("C-w" . kill-region) ("M-d" . kill-word) ("C-k" . kill-line) ("M-\\" . delete-horizontal-space) ("M-z" . zap-up-to-char))
9. Support Moving Point Around Within a Buffer
(bind-keys :map global-map ("C-f" . forward-char) ("C-b" . backward-char) ("C-n" . next-line) ("C-p" . previous-line) ("C-e" . move-end-of-line) ("C-a" . move-beginning-of-line) ("M-f" . forward-word) ("M-b" . backward-word) ("M-n" . forward-list) ("M-p" . backward-list) ("M->" . end-of-buffer) ("M-<" . beginning-of-buffer) ("M-}" . forward-paragraph) ("M-{" . backward-paragraph) ("C-v" . scroll-up-command) ("M-v" . scroll-down-command))
10. Change View of Buffer
(bind-keys :map global-map ("C-l" . recenter-top-bottom))
11. Play With Mark
(bind-keys :map global-map ("C-SPC" . set-mark-command) ("C-x h" . mark-whole-buffer))
12. Work with Kill Ring
(bind-keys :map global-map ("C-y" . yank) ("M-y" . yank-pop) ("M-w" . kill-ring-save))
13. Provide Some Contextual Editing
While much editing is fairly indifferent to the underlying content, some may vary behavior slightly based on its context. This does not extend to include more sophisticated behaviors that require deeper knowledge of syntactical elements.
(bind-keys :map global-map ("M-u" . upcase-word) ("M-;" . comment-dwim))
14. Generate Content
(bind-keys :map global-map ("M-/" . dabbrev-expand) ("M-SPC" . complete-symbol))
15. Searching and Potentially Replacing
(bind-keys :map global-map ("C-s" . isearch-forward) ("C-r" . isearch-backward) ("M-%" . query-replace) ("M-." . xref-find-definitions))
16. Support Some Inter-Process Communication
16.1. Support Invoking of External Commands
(bind-keys :map global-map ("M-!" . shell-command))
17. Provide Some Global Dispatching
(bind-keys :prefix "C-x C-z" :prefix-map dispatch-map ("a" . org-agenda) ("g" . gnus) ("m" . gnus-summary-mail-other-window) ("s" . shell) ("v" . view-mode) ("w" . eww))
17.1. Handle Escape Sequences from External Commands
(add-hook 'compilation-filter-hook 'ansi-color-compilation-filter)
18. Manage View Objects
Deal with buffers and windows. I'm currently lumping these together and customizing their binding a bit to make use of the `C-M` modifier combination. This is an experimental shift, but in a past adoption of Emacs I remember doing something or other to rebind window resizing such that it did not require a chain and as these operations are so fundamental this seems like a potentially nice route.
These are global and since the prefix is likely used elsewhere they are forced using `bind-keys*` to stick them in the override map.
(bind-keys* ("C-M-n" . next-buffer) ("C-M-<right>" . next-buffer) ("C-M-p" . previous-buffer) ("C-M-<left>" . previous-buffer) ("C-M-b" . switch-to-buffer) ("C-M-k" . kill-buffer) ("C-M-l" . list-buffers) ("C-M-s" . save-buffer) ("C-M-0" . delete-window) ("C-M-1" . delete-other-windows) ("C-M-2" . split-window-below) ("C-M-3" . split-window-right) ("C-M-o" . other-window) ("C-M-}" . enlarge-window-horizontally) ("C-M-{" . shrink-window-horizontally) ("C-M-]" . enlarge-window) ("C-M-[" . shrink-window))
19. Help Me!
(bind-keys :prefix "C-h" :prefix-map help-map :prefix-docstring "Help me!" ("b" . describe-bindings) ("f" . describe-function) ("i" . info) ("k" . describe-key) ("l" . view-lossage) ("m" . describe-mode) ("p" . describe-package) ("v" . describe-variable) ("w" . where-is))
20. Bookmark Management
Bookmarks are a feature I'm not actively using at the moment but this configuration has been carried forward from previous configuration.
(bind-keys :prefix "C-x C-r" :prefix-map bookmark-map ("m" . bookmark-set)) (setq bookmark-save-flag 1)
21. Define Some Minibuffer Bindings
(bind-keys :map minibuffer-mode-map ("TAB" . minibuffer-complete) ("RET" . exit-minibuffer) ("M-n" . next-history-element) ("M-p" . previous-history-element))
22. Define Some Navigation Through a Project
(bind-keys :map global-map ("C-x C-f" . ffap) ("C-x `" . next-error))
23. Interact With Source Control
Initially this section is just going to contain some bindings that I copied from a previous incarnation of my Emacs configuration.
(bind-keys :map global-map ("C-x v v" . vc-next-action) ("C-x v d" . vc-dir)) (use-package vc :bind (:map vc-dir-mode-map ("M-m" . vc-dir-mark) ("M-u" . vc-dir-unmark) ("M-=" . vc-diff)))
24. Configure Org Mode
(add-to-list 'auto-mode-alist '("\\.org\\'" . org-mode)) (use-package org :bind (:prefix "C-x C-o" :prefix-map global-org-map :prefix-docstring "Globally used org mode keys" ("a" . org-agenda) ("c" . org-capture) ("C-<SPC>" . org-store-link)) (:map org-mode-map ("DEL" . org-delete-backward-char) ("TAB" . org-cycle) ("M-RET" . org-meta-return) ("M-<right>" . org-metaright) ("M-<left>" . org-metaleft) ("M-<down>" . org-metadown) ;; Chrombook ("<next>" . org-metadown) ("M-<up>" . org-metaup) ("C-a" . org-beginning-of-line) ("C-k" . org-kill-line) ("C-c d" . org-deadline) ("C-c q" . org-set-tags-command) ("C-c s" . org-schedule) ("C-c t" . org-todo) ("C-c w" . org-refile) ("C-x RET" . org-open-at-point) ("C-M-_" . org-cycle)))
25. Configure Dired Mode
(use-package dired :bind (:map dired-mode-map ("C-p" . dired-previous-line) ("C-n" . dired-next-line) ("RET" . dired-find-file)))
26. Configure Org Agenda
(use-package org-agenda :bind (:map org-agenda-mode-map ("C-n" . org-agenda-next-line) ("C-p" . org-agenda-previous-line) ("TAB" . org-agenda-goto) ("g" . org-agenda-redo-all) ("t" . org-agenda-todo) ("u" . org-agenda-bulk-unmark) ("T" . org-agenda-show-tags)) :config (setq org-agenda-span 'day) ;; Allow for very old scheduled items for catch-up behavior. (setq org-scheduled-past-days 99999) ;; Use hl-line mode in org-agenda for visibilitiy (add-hook 'org-agenda-mode-hook 'hl-line-mode))
27. Configure hexl Mode
(use-package hexl :bind (:map hexl-mode ("C-v" . hexl-scroll-up) ("M-v" . hexl-scroll-down)))
28. Configure Info Mode
(use-package info :bind (:map Info-mode-map ("SPC" . Info-scroll-up)))
29. Configure Emacs Lisp
(add-to-list 'auto-mode-alist '("\\.el\\'" . emacs-lisp-mode)) (global-dash-fontify-mode) (with-eval-after-load 'info-look (dash-register-info-lookup)) (bind-keys :map global-map ("C-x C-e" . eval-last-sexp))
30. Manage Environment for Called Commands
Some operating systems (such as OS X) will invoke Emacs with a separate profile that may not include configuration that has been defined for login sessions such as environment variables.
This basically steals the logic from exec-path-from-shell (to link) but the code itself is a bit dumber and is more oriented towards building out a larger code base rather than providing drop-in functionality.
Most of the constructs here will be prefixed with ^-env
but those
that are more general will omit the env
segment.
30.1. Customization Group
I have not used customize in the past, but in addition to providing a user interface which may be more usable across devices it also seems to offer a natural means to locally specialize behavior without worrying about what goes into which file.
These will be functions which in some places helps reduce the overall complexity while retaining flexibility, and others are done largely for consistency. The large possible drawback is that this pushes more power and required expertise to the user (but additional protections and conveniences could be layered off if desired).
Initially funcall
will be used fairly liberally until I stumble across
a means to identify variables as functions and push Emacs lisp closer
to a lisp 1 (I know such mechanisms are readily available but I'm just
not actively looking).
(defgroup ^-env nil "Tune the environment." :prefix "^-env" :group 'environment)
30.2. Expand Using Shell
The underlying functionality revolves around evaluating expressions in a shell which is invoked with the login profile. Much of this may be able to be further generalized but there's currently no clear path or reason to do so.
At the high level such expansion will be done by wrapping the name as
a shell parameter and then passing it through the fairly ubiquitous
printf
function.
(defun ^-shell-expand (name) (let ((shell-variable (^-enveloper '("${" . "}")))) (^-shell-printf "%s" (funcall shell-variable name))))
30.2.1. Enveloper
The above uses an "enveloper" which wraps a string in the provided pair. There's some similar behavior that seems worth extracting into an object when I get to reading about some of the object libraries in Emacs (it doesn't seem worth going the manual route of routing messages).
This makes use of currying since…I like currying. More specifically I think use of partial applications in local variables can provide tidy invocations of general blocks. Later on I'll probably make use of some library or other (dash?) to provide a range of functional programming behavior for me.
(defun ^-enveloper (pair) "Produce a function which will wrap a string within pair." (lambda (s) (concat (car pair) s (cdr pair))))
30.2.2. Define the Called Commands
- Shell Supplier
The evaluation itself will be performed through a shell command which is defined here, defaulting to the Emacs standard.
(defcustom ^-env-shell-supplier (lambda () shell-file-name) "Define the function which will return the shell to invoke along with any additional arguments." :type 'function :group '^-env)
- Shell Login Argument Supplier
The shell command may also require some additional arguments to make sure that it is executed using the login profile.
(defcustom ^-env-shell-login-arg-supplier (lambda () '("-l" "-i")) "Define the function which will return arguments that when passed to the shell will operate in login mode." :type 'function :group '^-env)
- Printf Command Supplier
As previously mentioned this will expect to be evaluated using some form of printf…how practically extensible this is given the current logic seems unclear - likely most of these functions would be better off as being passed the argument they're expanding but all of that feels like premature generalization and right now the goal is just to define some of the magic values.
(defcustom ^-env-printf-supplier (lambda () "printf") "Define the function which will return the printf command to be invoked by the shell." :type 'function :group '^-env)
30.2.3. Support Invoking the Commands
There's likely some better constructs for this floating around somewhere, but for now I'll start with what was inherited.
- Warn On Slow Evaluation
Warning on a slow call is carried forward from exec-from-shell…I'm not entirely sure of its value given that there's no timeout behavior and the execution is not typically done repeatedly. I'd conjecture that maybe this is useful as such slow executions could produce undesirably slow Emacs start times. In any case it seems worth keeping around for the time being.
(defcustom ^-env-warn-evaluation-duration-millis-supplier (lambda () 500) "Print a warning if evaluation duration exceeds this number of milliseconds." :type 'function :group '^-env)
- Support Timed Evaluation
- TODO Replace with
with-delayed-message
There's nothing particularly specialized about timing a call so this will be implemented through providing a first class function which takes a curried handler to which the time will be passed and can then be passed any body as a thunk which will be evaluated and timed. This would almost certainly be more idiomatically a macro in Emacs lisp but I tend to avoid that (which will be covered separately at some point).
The implementation is typical timer behavior of tracking start time and then reporting the difference between the start and end times (in this case passing it to the handler. A general note (not specific to this code or language) is that it is often desirable to consistently report the time and therefore any calls that return through alternate paths (such as exceptions) should also be tracked (and preferably annotated accordingly). This is optimistically ignored for this code (and I'd typically prefer Either semantics over exceptions which makes that issue simpler to reason about) but that scenario may need further attention with this code.
- Millisecond Duration
The duration will be passed in milliseconds as that's what's inherited from the original logic. This also generally seems to be the most common unit for timing application code - it is appropriate for relatively slow actions like invoking another process and faster actions can be timed in aggregates which can help smooth out the many things can skew sub-millisecond timings.
This provides a basic implementation to calculate such a duration from two timestamps using Emacs functionality. There may be a date/time library floating around that could replace this later.
(defun ^-temporal-diff-millis (start end) (thread-last start (time-subtract end) float-time (* 1000.0)))
- Implement Timer
This function body is a fairly straightforward higher-order function. Currently the handler will be invoked with
funcall
(which should be modified later).(defun ^-timed (handler) "Produce a function that will invoke thunks and return their value while also passing the execution duration to <handler>." (lambda (thunk) (let* ((start-time (current-time)) (result (funcall thunk)) (duration (^-temporal-diff-millis start-time (current-time)))) (funcall handler duration) result)))
- Wire Warning
The specific handler for the duration warning needs to be configured to be plugged into the general timer. This will be provided by a closure over the setting which returns an appropriate handler.
- TODO Replace with
30.2.4. Transplants
The environment variables that should be carried across profiles will be called "transplants".
(defcustom ^-env-variable-transplants-supplier (lambda '("MANPATH")) "List of environment variables which will be transplanted on initialization." :group '^-env)
31. Structurizr Mode
The details of some of these should be covered. Why does font-lock-defaults require a nested list?
For now there is no hook defined since it wouldn't be used (although it is likely to be standard).
(define-derived-mode structurizr-mode fundamental-mode "Structurizr" "Major mode for editing Structurizr dsl" (set-syntax-table structurizr-mode-syntax-table) (make-local-variable 'structurizr-indent-offset) (set (make-local-variable 'indent-line-function) 'structurizr-indent-line) (setq font-lock-defaults (list (funcall structurizr-font-lock-default-collector))))
31.1. Font Lock
The definitions below are borrowed from the original mode, this should tied back more directly to the DSL reference with any resulting adjustment to naming or membership.
31.1.1. Words for Face
The original code also defined and then operated on values whereas this will attempt to make more use of in-place definitions with supporting functions as warranted.
This seems like something that likely already exists but to start this will make use of what was originally in place but wrap it up in a function for more direct use.
(defun ^-words-for-face (face words) "Return a pair of a regexp matching <words> and the provided <face>." (cons (regexp-opt words 'words) face))
31.1.2. List Collector
There is likely something readily available somewhere else that does this. When defining keywords in this file it is nice to allow each block to be self-contained so that it can be evaluated easily, but when collecting all lists that invites some possibly fragile mutability. To make this slightly cleaner, at least according to my tastes, we'll use an accumulator closure over the list that appends an argument if provided and returns the contents if no argument is provided.
(defun ^-list-collector () "Produce a list collector." (let ((l nil)) (lambda (&optional arg) (if arg (push arg l) l))))
31.1.3. Collect Categories of Known Words
Each block will push onto the list to keep the blocks self-contained.
There's a note around ordering being significant…ideally this can be avoided if it is substantiated but otherwise it should be very clearly laid out or ideally encoded within the logic.
(setq structurizr-font-lock-default-collector (^-list-collector))
- Keywords
(funcall structurizr-font-lock-default-collector (^-words-for-face 'font-lock-keyword-face '( "enterprise" "model" "views" "workspace" )))
- Types
(funcall structurizr-font-lock-default-collector (^-words-for-face 'font-lock-type-face '( "branding" "component" "container" "containerInstance" "deployment" "deploymentEnvironment" "deploymentGroup" "deploymentNode" "dynamic" "element" "filtered" "group" "infrastructureNode" "person" "perspectives" "properties" "relationship" "softwareSystem" "softwareSystemInstance" "styles" "systemContext" "systemLandscape" "themes" )))
- Relationship
(funcall structurizr-font-lock-default-collector (^-words-for-face 'font-lock-function-name-face '("->")))
- Properties
(funcall structurizr-font-lock-default-collector (^-words-for-face 'font-lock-variable-name-face '( "autoLayout" "background" "border" "color" "colour" "dashed" "description" "exclude" "fontSize" "height" "icon" "include" "metadata" "opacity" "position" "routing" "shape" "stroke" "tags" "technology" "thickness" "title" "url" "width")))
31.2. Indentation
31.2.1. Configure Offset
(defvar structurizr-indent-offset 4 "Define the indentation offset for `structurizr-mode'. Lines will be indented this offset multiplied by the detected level. Currently only spaces are supported.")
31.2.2. Determine Indentation Level
The basic algorithm here will start from what was defined upstream. This will be broken up a bit and in the future any other standard algorithms will be explored.
This currently adjusts the formatting after a newline rather than inserting the character itself so something more electric may be better.
(defun structurizr-indent-line () "Indent current line as Structurizr dsl." (interactive) (let* ((initial-level (^-opener-count-to-top "{")) (closers (^-closers-on-line "}")) (level (max 0 (- initial-level closers)))) (indent-line-to (* level structurizr-indent-offset))))
- Opener Count to Top
The current approach involves ascending lists until an ignored error is encountered, and then reporting the count of the number of a particular opener that was encountered.
This feels like it should be simpler in some way or another. This level should potentially be readily available and the ascension behavior should be tied to the relevant grammar which would obviate the need for the additional check. At the moment I don't know what options are available and unused versus those that would need further support so I'll circle back to this over time (especially as other modes are pulled in).
(defun ^-opener-count-to-top (opener) "Ascend through levels and count the number of <opener>s seen." (let ((level 0)) (save-excursion (beginning-of-line) (condition-case nil (while t (backward-up-list 1) (when (looking-at opener) (setq level (+ level 1)))) (error nil))) level))
- Closer Count
The current logic only decrements one if the first character is a closer. This may be nicer to just count all instances but that feels like a taste based on the resulting behavior. In any case it should likely support either.
In the current form this will either return 0 or 1.
There was also a separate test initially which seemed to look to prevent over-outdenting which can be addressed by a ramp function on outside use.
(defun ^-closers-on-line (closer) (save-excursion (back-to-indentation) (if (looking-at closer) 1 0)))
31.3. Syntax Table
The standard syntax table function newentry
value is bit arcane so
I'm going to try to define some more expressive functions to produce
the values (and likely find something that exists elsewhere).
This can be generalized a bit as needed by producing functions which produce the first character but in the short term the only interesting one I need is for punctuation so that will be written as specialized. The underlying logic can then be a function which accepts named flags as keywords each of which will map to the underlying terse flag.
Initially I'll use a fairly clunky cond
block though there is almost
certainly something cleaner. This could be cleaned up through partial
application but I'd imagine there's some better pattern matching stuff
I need to stumble upon.
This should all be significantly cleaned up into a nicer DSL (or ideally an existing one found) but right now I'm starting with the basics. In particular the relationships between some of the flags should be modeled in that DSL whereas the naive approach allows for seemingly invalid combinations. These should also operate on a closure over a comment table.
(defun ^-syntax-table-punctuation (&rest flags) (apply #'concat (cons ". " (mapcar (lambda (flag) (cond ((eq flag :start-2char-comment-open) "1") ((eq flag :end-2char-comment-open) "2") ((eq flag :start-2char-comment-close) "3") ((eq flag :end-2char-comment-close) "4") ((eq flag :for-comment-sequence-b) "b") (t (error "Unrecognized flag" flag)))) flags)))) (defun ^-syntax-table-comment-ender () ">")
(setq structurizr-mode-syntax-table (let ((syntax-table (make-syntax-table))) (modify-syntax-entry ?/ (^-syntax-table-punctuation :start-2char-comment-open :end-2char-comment-open :end-2char-comment-close) syntax-table) (modify-syntax-entry ?* (^-syntax-table-punctuation :end-2char-comment-open :start-2char-comment-close :for-comment-sequence-b)) (modify-syntax-entry ?\n (^-syntax-table-comment-ender) syntax-table) syntax-table))
31.4. Associate with Extension
dsl
is certainly not unambiguously Strucutrizr files,
but it's the only association I'm currently expecting.
(add-to-list 'auto-mode-alist '("\\.dsl\\'" . structurizr-mode))
32. Programming Support
This is currently copied forward but past configurations but not revisited. It looks as though this is the beginning of seeking to support code collapsing.
(add-hook 'prog-mode-hook hs-minor-mode) (bind-keys :map prog-mode-map ("C-M-_". hs-toggle-hiding))
32.1. C
(add-to-list 'auto-mode-alist '("\\.c\\'" . c-mode))
33. Configure Lilypond
This is the very beginning of support for Lilypond mode, which I think I started to copy from the Lilypond source code years ago. This is unlikely to provide any valuable in its current form and so will either be built upon further later or removed.
(defvar LilyPond-mode-map () "Keymap used in `LilyPond-mode' buffers.") (defun LilyPond-mode () "Major mode for editing LilyPond music files. This mode knows about LilyPond keywords and line comments, not abou indentation or block comments. It features easy compilation, error finding and viewing of a LilyPond source buffer or region. COMMANDS \\{LilyPond-mode-map} VARIABLES LilyPond-command-alist\t\talist from name to command" (interactive) ;; set up local variables (kill-all-local-variables)) (add-to-list 'auto-mode-alist '("\\.ly$" . LilyPond-mode)) (add-to-list 'auto-mode-alist '("\\.ily$" . LilyPond-mode)))
34. Configure Gnus
I'm currently using Gnus to read my mail but have not yet dug into it again - this like many others reflect inherited previous configuration options.
;; This should be cleaned up a bit but was just implemented in the most ;; obvious way for now. (defun gnus-group-read-50 () (interactive) (gnus-group-read-group 50)) (use-package gnus :bind (:map gnus-group-mode-map ("M-g" . gnus-group-get-new-news-this-group) ("M-<down>" . gnus-group-read-50) :map gnus-summary-mode-map ("M-<down>" . gnus-summary-next-page) ("C-c m" . gnus-summary-move-article)))
35. Configure EWW
On Linux I may explore EAF but for now I'll be using EWW since EAF is currently Linux only and isn't working particularly well as configured on my Sway installation.
(use-package eww :bind (:map eww-mode-map ("C-c w" . eww-copy-page-url))) (setq browse-url-browser-function 'eww-browse-url)