Marcin Borkowski: Converting words and sentences to identifiers

Preview: Some time ago I had a need to “convert” a phrase, or even a whole sentence, into an identifier. By “converting to an identifier” I mean lower-casing the whole thing and changing non-letter characters into underscores. For example, “Hello, world!” should become hello_world. I came up with some simple code to do that – but it turned out that there were some pitfalls I did not expect.
-1:-- Converting words and sentences to identifiers (Post)--L0--C0--October 03, 2022 09:07 PM

Sacha Chua: 2022-10-03 Emacs news

Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, r/planetemacs, Hacker News, lobste.rs, planet.emacslife.com, YouTube, the Emacs NEWS file, Emacs Calendar, emacs-devel, and lemmy/c/emacs.

-1:-- 2022-10-03 Emacs news (Post Sacha Chua)--L0--C0--October 03, 2022 05:25 PM

Protesilaos Stavrou: Emacs: introduction to the ef-themes “frost” theme

I just added ef-frost to my ef-themes collection. It is a legible light theme with cool colours ranging from blue to teal with touches of purple.

[ The ef-themes are a collection of light and dark themes for GNU Emacs whose goal is to provide colourful (“pretty”) yet legible options for users who want something with a bit more flair than the modus-themes (also designed by me). ]

Some screen shots are provided below, though you can find pictures of all the items in the collection here: https://protesilaos.com/emacs/ef-themes-pictures.

Note that some themes in the collection are designed as pairs. Those have -light or -dark in their name. The others are standalone. Regardless, the user can specify any two themes in the value of the user option ef-themes-to-toggle and then switch between them with the command ef-themes-toggle. Or just use the other available commands: ef-themes-select for minibuffer completion and the ef-themes-load-random to load one from the collection (with C-u limit the effect to light or dark themes). The official manual explains more.

ef-frost

ef-frost theme sample

ef-frost theme code sample

ef-frost theme listing sample

Comparison between ef-light and ef-frost

There is no other light theme quite like ef-frost. The closest is ef-light, which is more colourful as those pictures demonstrate (each theme in the collection has a unique colour mapping and its own colour palette).

ef-light theme sample

ef-frost theme sample

ef-light theme code sample

ef-frost theme code sample

ef-light theme listing sample

ef-frost theme listing sample

Coming in version 0.7.0 (next stable release)

The ef-frost theme is in development. I think its design is final, though I might tweak it further. Expect this and other updates to be widely available via GNU ELPA some time in mid October.

-1:-- Emacs: introduction to the ef-themes “frost” theme (Post)--L0--C0--October 03, 2022 12:00 AM

Jeremy Friesen: Migration Plan for Org-Roam Notes to Denote

Laying Out a Game Plan

Building on Exploring the Denote Emacs Package, I want to layout a migration plan. For the migration to be successful, I have the following criteria:

Glossary Migration
When I pull glossary entries into Take on Rules I expect the ./data/glossary.yml to be functionally identical to it’s current state. That is to say no nodes dropped nor added and keys being similar. I would go so far as to say after the migration the pull event should result in no change to ./data/glossary.yml.
Blog Export
I am able to export an Org-Mode 📖 format Denote file for publication on Take on Rules.
Data Structures
I am able to organize and discover notes via different attributes.
Keywords and Controlled Vocabularies
I spent time establishing the tags I use for blogging; I’d want assistance in adhering to that exercise.
Other Stuff
As I write this document, I’m certain other things will emerge.

Feature Tests

These are my guiding “feature tests.” Revisiting Exploring the Denote Emacs Package, you’ll notice that I’ve already introduced feature creep. Such is the life of any project, but especially a software project.

Glossary Migration

I need to consider how I’m handling abbr: and abbr-plural: links; due to the newness of this functionality I don’t have many to consider. This does require navigating the export of Hugo shortcode fragments.

I will need to migrate the Rake 📖 task that I wrote for pulling data into my Hugo 📖 data/glossary.yml file. This should be relatively straight-forward and easier by introducing the convention of a glossary directory.

Yes, I Org-Roam 📖 I could have created a custom directory and template, but I was relying on the search function as my intermediary. In hindsight, having a specific directory for Glossary is something I’m now sensing I want.

Given that I’m uncertain I want to migrate to Denote, it makes sense to move my glossary items to a glossary directory.

Blog Export

I’ll need to revisit the Extending the Ox section of my Emacs configuration. With this document, I did a called jf/export-org-to-tor to see what happened. The main thing I observed was that the Ox-Hugo front-matter I inject into this document was placed at the top of the file.

That clobbers the Denote convention of it’s front-matter at the top of the file. The fix is somewhat straight-forward; I know what to do but not yet how I’ll go about it.

There likely exists a function to detect the entirety of a Denote document’s front-matter. Use that to find where to insert the Ox-Hugo front matter.

Another thing I noticed was that I was adding an :ID: to a properties drawer. This is not necessary as Denote has the :identifier: front-matter entry. This does, however, remind me that I’ll need to consider migrating nodes.

In the last week, I recall reading a post regarding exporting Org-Roam to Denote; I’ll want to go track that down.

I found it: bitspook/notes-migrator: Migrate your notes b/w different note-taking software (only org-roam to denote supported for now)

Data Structure

One thing I love thinking about, and all too often forget to think about, is the data structure.

And Denote, by it’s conventions, has me thinking about the data structure; in part because of it’s opinions regarding front-matter. In this case there are two data structure components to consider:

  • File System Conventions
  • Document Metadata

File System Conventions

As a refresher, Denote uses the file name to encode:

  • Identifier
  • Title
  • Keywords (aka Tags)

There is further consideration about the directory structure. There is guidance on how to isolate directories (e.g. “work”, “personal”, “volunteer”) such that they are siloed from each other. See Section 3.3 “Maintain separate directories for notes” of Denote’s manual for further details on separate directories. However, directories need not be siloed. My aforementioned glossary is something I might not want siloed; after all these terms are useful for many contexts.

I do wonder how I might have three directories: glossary, employer, personal. I would want the glossary available to the employer and personal but disallow employer and personal “knowledge” of each other.

To put a name to metadata role of directory, I think of namespace or domain. Namespace feels too generic. Let’s go with domain, and lean on Webster’s 1913 definition:

The territory over which dominion or authority is exerted; the possessions of a sovereign or commonwealth, or the like. Also used figuratively.

I also like overlaying/amalgamating the definition with the Mathematical concept of domain:

The domain of a function is the set of its possible inputs, i.e., the set of input values where for which the function is defined.

With domain I have four pieces of metadata: title, identifier, keywords, and domain.

I should probably consider what are my implicit domains:

Glossary
I reference many terms.
Epigraphs
I collect passages and reference them in posts.
Blog Posts
I’ve written a lot of blog posts; should I move a note into this domain when I publish an internal note?

Do all of these warrant their own directory? Before I get carried away, I should do some preliminary exploration. As previously mentioned, the Glossary domain is a good experiment for adoption.

Document Metadata

I wrote about this quite a bit already, but will reiterate; because Denote allows writing in Org-Mode format, I have access to Org-Mode’s property syntax; though I’ll need to use a different method than I’ve previously used.

Keywords and Controlled Vocabularies

On I migrated the tags of my Blog; compressing 378 tags into 59. I checked Changelog // Take on Rules to see when I made this unceremonious switch. How to Use Tags spurred me to revisit my tagging. This migration inter-relates with my glossary migration; the glossary asserts what tags are allowed on Take on Rules.

To my knowledge, Org-Roam, does not have a concept of a controlled vocabulary for tags. And since I’m using Org-Roam as the mechanism for the initial composition of my blog, I’m butting up against tagging; namely I have to be disciplined about how I tag things.

In my quick read of the documentation, denote-infer-keywords provides the ability to limit tags to only those explicitly set in denote-known-keywords.

Further Considering and Pondering

One thing rattling around in my brain is how I’ve been using my agenda.org file. For my work at Scientist.com I use it as a TODO list and my monthly timesheet. When the month is done, I archive my timesheet.

What if I rethink things just a bit; each month get’s a journal.org file. Then I amend my org-agenda=files variable to point to the timesheet? This would mean I’m not archiving entries but instead removing them from my agenda workflow.

This is a future tangent but one that I’m pondering and considering. And none of this is really dependent on Denote; it is simply a byproduct of thinking about my organization system.

But I do like the idea of not archiving the entries. Why?

I use Git 📖 for version control of my Org-Mode files; and moving a subtree from one file to another gives me trepedations.

Conclusion

This document is about helping me think through a potential migration. And in writing this all down, I’m thinking about what it means to go through a migration.

-1:-- Migration Plan for Org-Roam Notes to Denote (Post Jeremy Friesen (jeremy@takeonrules.com))--L0--C0--October 02, 2022 10:09 PM

Irreal: Letters With Org-mod

If you’re like me and want to do all your writing from within Org-mode, one problem you need to solve is how to write letters—especially professional letters. I use a LaTeX template and write the letter in LaTeX but mostly it’s just text so it’s pretty easy. Still, it would be nice to move all that to Org so that I could just use, say, a Yasnippet to include the LaTeX boiler plate and then write in the comfortable Org environment.

Ravi Sagar has a short video on how he’s solved this problem. One of the problems he needed to solve was how to produce a letterhead. That turns out to be pretty easy once you have the letterhead graphic. He has all his LaTeX boilerplate in a separate file and just includes it in the individual letters. It works well and is pretty easy.

If you’d like to move even your formal letters to Org-mode, take a look at Sagar’s video to see how to set it up. It’s worth doing this if only to free yourself from the tyranny of Word and its ugly siblings. As this week’s Red Meat Friday hinted, you’d do well to avoid hitching yourself to the Microsoft wagon.

-1:-- Letters With Org-mod (Post jcs)--L0--C0--October 02, 2022 04:45 PM

Garjola Dindi: A workaround for an annoying EXWM behavior

I have been using the Emacs X Window Manager, EXWM, for a couple of years now and I have come to depend on it for an efficient and friendly workflow for most of what I do at the computer. Before that, I had been using StumpWM for 4 or 5 years and I was already sold on tiling window managers. For someone like me who does everything in Emacs except for browsing sites that need Javascript or watching videos, EXWM seems to be the perfect setup.

The pain point with EXWM is that its development and maintenance suddenly stopped after the creator and maintainer went AWOL. There have been discussions here and there to continue the development, but, to the best of my knowledge, no initiative has really taken off.

Some well known EXWM users in the community started migrating to other WMs, like StumpWM, Herbstluftwm or or QTile. I also tried to get back to StumpWM, since I had used it for a long time, but I found the experience inferior to EXWM: having M-x everywhere and not having to use different key bindings when talking to the WM or to Emacs is a real comfort.

However, some time ago, floating windows started behaving annoyingly. For instance, when saving a file in Firefox, the file chooser application would be too high and the Save button would be below the bottom of the screen. I then would have to move the window up so that I could click. Not knowing anything about X, I wasn't able to diagnose the issue.

I ended writing a little function to resize a floating window:

(defun my/resize-floating-frame (width height)
  "Resize a floating exwm frame to WIDTHxHEIGHT"
  (interactive "nWidth: \nnHeight: ")
    (let ((floating-container (frame-parameter exwm--floating-frame
                                               'exwm-container)))
      (exwm--set-geometry floating-container nil nil width height)
      (exwm--set-geometry exwm--id nil nil width height)
      (xcb:flush exwm--connection)))

and bound it to a key to call it with a size that would fit:

(exwm-input-set-key (kbd "s-x r") (lambda () (interactive) (my/resize-floating-frame 600 800)))

So now, instead of grabbing the mouse and manually moving the window, I could just use s-x r and hit enter, since the Save button is selected by default. This is a big win, but after a couple of days, I became tired of this and thought that this could be automated. Also, the arbitrary fixed size may not be appropriate depending on the monitor I am using.

So with a little more elisp, I wrote a function that computes the size of the window and works with a hook which is called by EXWM and the end of the setup of the floating window:

(defun my/adjust-floating-frame-size ()
  "Ensure that the current floating exwm frame does not exceed the size of the screen"
  (let* ((frame (selected-frame))
         (width (frame-pixel-width frame))
         (height (frame-pixel-height frame))
         (w1 (elt (elt exwm-workspace--workareas 0) 2))
         (h1 (elt (elt exwm-workspace--workareas 0) 3))
         (w2 (elt (elt exwm-workspace--workareas 1) 2))
         (h2 (elt (elt exwm-workspace--workareas 1) 3))
         (max-width (round (* 0.75 (min w1 w2))))
         (max-height (round (* 0.75 (min h1 h2))))
         (final-height (min height max-height))
         (final-width (min width max-width)))
    (set-frame-size frame final-width final-height t)))
(add-hook 'exwm-floating-setup-hook #'my/adjust-floating-frame-size 100)

All this may seem complex and one could think that too much elisp-foo is needed to do all this. This is absolutely not the case. With a little effort and time, it is not difficult to navigate the source code and test things interactively to see how they behave. In this particular case, searching for hooks in the EXWM package (M-x describe-variable) and trying little code snippets on the *scratch* buffer got me there rather quickly.

This is yet another demonstration of the power of Emacs: you can modify its behavior interactively and all source code and documentation is immediately accessible. You don't need to be a skilled programmer to do that.

That's user freedom, the real meaning of software freedom.

-1:-- A workaround for an annoying EXWM behavior (Post)--L0--C0--October 02, 2022 03:20 PM

Protesilaos Stavrou: Emacs: introduction to the ef-themes “bio” theme

I just added ef-bio to my ef-themes collection. It is a legible dark theme with green, teal, blue, purple colors.

[ The ef-themes are a collection of light and dark themes for GNU Emacs whose goal is to provide colourful (“pretty”) yet legible options for users who want something with a bit more flair than the modus-themes (also designed by me). ]

Some screen shots are provided below, though also check all the themes here: https://protesilaos.com/emacs/ef-themes-pictures.

ef-bio

ef-bio theme sample

ef-bio theme code sample

ef-bio theme listing sample

Comparison of ef-bio, ef-night, ef-trio-dark

Just a sample with two other dark themes from the ef-themes collection.

ef-bio theme sample

ef-night theme sample

ef-trio-dark theme sample

ef-bio theme code sample

ef-night theme code sample

ef-trio-dark theme code sample

ef-bio theme listing sample

ef-night theme listing sample

ef-trio-dark theme listing sample

Coming in version 0.7.0 (next stable release)

The ef-bio theme is in development. It will probably not change much, though I might make some further tweaks to it. Expect this and other updates to be widely available via GNU ELPA some time in mid October.

-1:-- Emacs: introduction to the ef-themes “bio” theme (Post)--L0--C0--October 02, 2022 12:00 AM

Irreal: Yasnippet for Prose Writing

When you think about it, the title of this post doesn’t appear to make sense. Yasnippet is a template system for adding customized boiler plate to an Emacs buffer. It’s raison d’être is to enable shortcuts for various programming constructs such as the C for loop.

Still, it’s a flexible system so it finds all sorts of uses. If you’re writing in LaTeX, it can help with things like the environment boilerplate. Similarly, it’s great for things like blog headers.

Erik L. Arneson has a post that describes how he uses Yasnippet for his writing. After explaining how to install Yasnippet, Arneson describes his uses. That turns out to be mostly things like blog headers and Org headers for certain other structured files. You can read his post for the details.

My personal use is almost entirely things like that. I never think to use it when I’m programming even though I do have the appropriate snippets loaded so the snippets I use are mostly for prose and various record keeping applications where the snippet provides a sort of form that I can easily fill out by tabbing through the fields.

The takeaway is that technology like Yasnippet doesn’t have a lot of application for writing prose except for headers and other boilerplate and perhaps for markup if you’re using something like LaTeX. Still, even those seemingly minimal applications can save a lot of time.

-1:-- Yasnippet for Prose Writing (Post jcs)--L0--C0--October 01, 2022 06:12 PM

Alvaro Ramirez: Plain Org v1.5 released

01 October 2022 Plain Org v1.5 released

If you haven't heard of Plain Org, it gives you access to org files on iOS while away from your beloved Emacs.

Hadn't had time to post, but v1.5 has been available on the App Store for a couple of weeks now. The update is mostly a bugfix release, primarily addressing inline editing issues that appeared on iOS 16, along with a few other changes:

  • Render form feeds at end of headings at all times.
  • Fixes new files not recognized by org-roam.
  • Fixes share sheet saving from cold launch.
  • Fixes inline editing on iOS 16.

po.png

I love org markup, but we (iPhone + org users) are a fairly niche bunch. If you're finding Plain Org useful, please help support this effort by getting the word out. Tell your friends, tweet, or blog about it. Or just support via the App Store :)

-1:-- Plain Org v1.5 released (Post)--L0--C0--October 01, 2022 06:00 PM

Jeremy Friesen: Exploring the Denote Emacs Package

Some Analsysis of Features

As of I use Org-Roam 📖 for writing and linking. Those notes can be glossary entries, blog posts, people, epigraphs, or just about anything text.

Org-Roam utilizes a SQLite database for storing and linking metadata. This is a feature that I consider a nice to have.

An emerging note taking utility is Denote; in this document I want to explore my usage of Org-Roam; what I’ve come to expect (e.g. consider a “must have”) and what I’ve come to appreciate (e.g. consider a “nice to have”). I then want to look at how to achieve that in Denote.

The foundational tooling that I want:

Org-Mode aware
Note taking that “understands” Org-Mode 📖; or more appropriately that Org-Mode tooling can understand.
Tags
Tag each note.
Quick filing
Insert a new note with minimal thought of where it goes.
Linking
Link to other notes.
Metadata
Add metadata that can power my epigraphs and glossary.
Search
Prompt for notes by searching title and tags and other metadata.
Export Links
When I link to a node and export, I want to export the world facing URL.

The above definitions are my “feature list”; I’ll reference those later.

A nice to have feature would be prompting for a file by more advanced searching. An example of that would be as follows:

  • Filter on the programming tag
  • Search the resulting filter set for the words Hello World

Another nice to have feature is collision detection; I don’t want two notes to have the same glossary key, nor alias. Right now I believe Org-Roam enforces unique aliases but not glossary keys.

Diving into Denote

You might be wondering why this document? There’s a two-fold reason. First, I want a place to think about what this change would mean. Second, I want to remember and be able to later evaluate all of the code I’m writing and loading into Emacs 📖 without adding it to my Emacs Configuration.

In away, this post is my sandbox exploration of the Denote package.

With this quick establishment of my note-taking requirements I’m going to explore Denote.

Why explore Denote?

First, I found myself reading the Denote documentation for pleasure. It is one well-documented package; accessible and helpful at learning not just Denote but also Emacs Lisp as well.

More importantly (maybe?) is a statement I recall from a seminar with Neil Jeffries regarding the Oxford Common File Layout (OCFL 📖). The statement was along the lines of favoring Posix, the Unix file system. It is a consistent and underpinning technology that is very likely to continue as other technologies light the stage and fade away. In other words, it has attributes that are ideal for “preservation” of digital objects. Too Long; Didn't Read 📖 Favor it over any other system of preservation.

I’ve long worked in the fields adjacent to digital preservation concerns and have felt the anguish of Fedora Commons wending a path to, from, and back to the sensible consideration of storing things on a file system.

In other words, the sensibility of Denote echoes my wariness of too many tools. However, there’s quite a bit of tooling in Org-Roam that uses SQLite to help facilitate; but the fundamentals of portability remain.

The key consideration is that Org-Roam has one more external dependency than Denote.

Reading Denote’s documentation on Portability, I see common consideration:

Notes are plain text and should remain portable. The way Denote writes file names, the front matter it includes in the note’s header, and the links it establishes must all be adequately usable with standard Unix tools. No need for a database or some specialised software.

Protesilaos Stavrou (aka Prot), the maintainer and originator of Denote, stewards many Emacs packages. A consistent attribute of those packages is fantastic documentation; both inline and of the “README” variety.

Denote’s documentation exemplifies quality documentation.

The Code I Load

When I first started, I had the minimal package declaration (use-package denote :straight t). As I explored I amended that basic declaration to the following code block.

(use-package denote
  :straight t
  :commands (denote-directory)
  :bind ("H-c a" . jf/denote-create-abbreviation)
  :custom ((denote-directory "~/git/org/denote")
	   ;; These are the minimum viable prompts for notes
	   (denote-prompts '(title keywords))
	   ;; I love org-mode format; reading ahead I'm setting this
	   (denote-file-type 'org)
	   ;; And `org-read-date' is an amazing bit of tech
	   (denote-date-prompt-denote-date-prompt-use-org-read-date t)))

(cl-defun jf/denote-create-abbreviation
    (&key
     (title (denote--title-prompt))
     (abbr (read-from-minibuffer "Abbreviation: ")))
  "Create a `denote' entry for the given TITLE and ABBR.

NOTE: At present there is no consideration for uniqueness."
  (interactive)
  (let ((template (concat "#+GLOSSARY_KEY: glossary---" abbr "\n"
			  "#+ABBR: " abbr "\n")))
    (denote title
	    '("glossary" "abbreviation")
	    'org
	    (f-join (denote-directory) "glossary")
	    nil
	    template)))

Exploration Notes

I arrived at the above code-block via the following exploration.

Customizing denote-directory

Prior to configuration my denote directory was ~/git/org/main/notes; I’m unclear why it chose this but it was a good guess as my org-roam-directory is ~/git/org and I have a capture template that writes to ~/git/org/main/.

With the configuration I’m partially sequestering my playground and will begin exploring.

Creating jf/denote-create-abbreviation

I had originally started exploring the denote-templates but chose to pursue a more explicit pathway which, based on my knowledge of Lisp, was straight forward and very quick.

There were a few turns that I took. An existing implementation for much of my tooling is that I’m assuming an Org-Mode properties drawer for the GLOSSARY_KEY and ABBR properties.

In Org-Roam the properties for the node go above the TITLE property; however by convention that is not how Denote is structured to work. I made some revisions.

I tested the above by creating a new Denote node for “Digital Humanities” with the abbreviation of “DH”.

Pause To Review Requirements and Reflect

At this point, I have verified checked off 4 of the 7 requirements. And reading ahead of the documentation I see that there are considerations for Linking and Exporting Links as well as searching.

  • Org-Mode aware
  • Tags
  • Quick filing
  • Linking
  • Metadata
  • Search
  • Export Links

At this point, what I really like is the interface to creating a note. The denote function does the magic and allows for me to pass parameters that override the default methods.

Contrast with Org-Roam, where I provide the title/text and then say what the template shouldl be.

I’m also liking the ease at which I could create a function for creating glossary entries. To do that in Org-Roam via capture is certainly doable.

The thing I need to check is how moving from the properties drawer approach I’ve used in Org-Roam varies from using keywords. Most of my interactions with properties are via Org-Mode’s API.

This sounds like the next pathway to explore.

Investigating Moving from Property Drawer to Keywords

Before I get too much further, I need to verify that I can continue to get properties from my notes.

The following function verifies that I can retrieve a property for the Denote note I made.

(cl-defun jf/denote-org-property-from-id (&key id property)
  "Given an ID and PROPERTY return it's value or nil.

Return nil when:

- is not a denote file
- ID is not an `org-mode' file
- property does not exist on the file"
  (when-let ((filename (denote-get-path-by-id id)))
    (when (string= (file-name-extension filename) "org")
      (with-current-buffer (find-file-noselect filename)
	(cadar (org-collect-keywords (list property)))))))

(message "%s" (jf/denote-org-property-from-id :id "20220930T215235"
					       :property "ABBR"))

Let’s Look at Linking

For this, I’ll need another node. I now have two nodes: 20220930T221757 and 20220930T215235. Using denote-link I create a link in 20220930T221757 to 20220930T215235.

Then in 20220930T221757 I call denote-link-backlinks. The backlink buffer is an enumeration of links. Whereas in Org-Roam the backlink and reference buffer includes the surrounding context; a nice feature but not something I consider mandatory.

And below is my must haves:

  • Org-Mode aware
  • Tags
  • Quick filing
  • Linking
  • Metadata
  • Search
  • Export Links

I need to further explain what I mean by this. I am accustomed to using my own jf/org-roam-find-node which wraps org-roam-find-node. I can filter by tags and title. With denote, I have to consider directory structure.

Let’s see about leveraging the Consult package.

(bind-key "H-f" 'jf/denote-find-file)
(defun jf/denote-find-file ()
  "Find file in `denote-directory'"
  (interactive)
  (require 'consult-projectile)
  (require 'denote)
  (consult-projectile--file (denote-directory)))

This provides me with the comparable functionality, but requires some reimaginging. However, courtesy of Section 5. The file-naming scheme of Denote documentation I can use the naming convention for tag and filename search.

Prefix the search with - for a tag and and _ for a word. This matches the functionality of what I have.

Conclusion

Reading and testing Denote, I have established feature parity in my functional needs.

What does that mean?

I am prepared to further pursue what it might mean to migrate my some 2800 Org-Roam notes to denote. I just completed a migration of my Hugo 📖 ./data/glossary.yml file to Org-Roam, so I know that it’s not an arduous process to migrate. Read about this data migration in On Storing Glossary Terms in Org Roam Nodes. I already identified the need to move from property drawers to properties that are positioned in the file after the Denote front-matter. I’d need to revisit the Ox-Hugo export process I’ve developed. As well as how I’m exporting and creating links.

I would also want to look at different directories. I like separating the different concerns (e.g. glossary, epigraph) and the ease at which I could set this up.

There is quite a bit more to consider regarding this migration. But it is an interesting (to me) exercise of consideration.

-1:-- Exploring the Denote Emacs Package (Post Jeremy Friesen (jeremy@takeonrules.com))--L0--C0--October 01, 2022 04:11 PM

Alvaro Ramirez: dwim-shell-command usages: pdftotext and scp

01 October 2022 dwim-shell-command usages: pdftotext and scp

dwim-shell-command is a little Emacs package I wrote to enable crafting more reusable shell commands. I intended to use it as an async-shell-command alternative (and I do these days). The more surprising win was bringing lots of command-line utilities (sometimes with complicated invocations) and making them quickly accessible. I no longer need to remember their respective parameters, order, flags, etc.

I've migrated most one-liners and scripts I had to dwim-shell-command equivalents. They are available at dwim-shell-commands.el. Having said that, it's great to discover new usages from dwim-shell-command users.

Take u/TiMueller's Reddit comment, showcasing pdftotext. Neat utility I was unaware of. It does as it says on the tin and converts a pdf to text. Can be easily saved to your accessible repertoire with:

( defun  dwim-shell-commands-pdf-to-txt ()
   "Convert pdf to txt."
  ( interactive)
  (dwim-shell-command-on-marked-files
    "pdf to txt"
    "pdftotext -layout '  ' ' .txt '"
    :utils  "pdftotext"))

pdf-to-txt_x2.webp

tareefdev wanted a quick command to secure copy remote files to a local directory. Though this use-case is already covered by Tramp, I suspect a DWIM command would make it a little more convenient (async by default). However, Tramp paths aren't usable from the shell unless we massage them a little. We can use dwim-shell-command-on-marked-files's :post-process-template to drop the "/ssh:" prefix.

( defun  dwim-shell-commands-copy-remote-to-downloads ()
  ( interactive)
  (dwim-shell-command-on-marked-files
    "Copy remote to local Downloads"
    "scp '  ' ~/Downloads/"
    :utils  "scp"
    :post-process-template
   ( lambda (script file)
      ;;  Tramp file path start with "/ssh:". Drop it.
     (string-replace file
                     (string-remove-prefix  "/ssh:" file)
                     script))))
-1:-- dwim-shell-command usages: pdftotext and scp (Post)--L0--C0--October 01, 2022 02:42 PM

Emacs TIL: A trick to speed up projectile-find-file in a large monorepo

When working on a large monorepo (like a ten-year-old Ruby on Rails app), we can use projectile-find-file to go to files but it be slow when the file list is long.

From projectile documentation, we can set the indexing method to alien to speed up the find file command:

(setq projectile-indexing-method 'alien)

This saves seconds when going to files and makes my action much crispier!

-1:-- A trick to speed up projectile-find-file in a large monorepo (Post Junji Zhi)--L0--C0--October 01, 2022 02:27 AM

Protesilaos Stavrou: Modus themes 2.7.0 for GNU Emacs

I just published the latest stable release of the Modus themes. The change log entry is reproduced below. For any questions, feel welcome to contact me.

I will now prepare the patch for emacs.git (currently Emacs 29) which will then trickle down to GNU ELPA (the modus-themes is a :core package). Thank you for your patience!


Support for packages or faces

  • Reinstated support for centaur-tabs. I had removed it in commit 2235ce5 (done on 2022-08-02) for version 2.5.0 of the modus-themes. At the time I wrote:

    centaur-tabs has a bug where it cannot read the value of a face if it
    uses the standard `:inherit` attribute.  I have sent a patch to fix it,
    but have received no response since February:
    <https://github.com/ema2159/centaur-tabs/pull/179>.
    
    Relevant reports:
    
    - <https://github.com/protesilaos/modus-themes/issues/30>
    - <https://gitlab.com/protesilaos/modus-themes/-/issues/288>
    - <https://github.com/protesilaos/modus-themes/issues/15>
    
    I am happy to reinstate support for centaur-tabs as soon as the package
    gets the maintenance it needs.
    

    My patch/pull-request is now merged and the package is actively maintained once again. Hence the decision to bring back support for it, as promised.

  • Applied styles for the icon-button face of Emacs 29.

  • Styled the log-edit-headers-separator face of Emacs 29 (it was introduced upstream by a patch of mine).

  • Made the gnus-summary-low-unread face inherit from the italic face like the rest of that subgroup of faces. This helps differentiate it from the gnus-summary-high-unread face. Thanks to Mark Simpson for pointing out the possibility of conflating those two faces: https://lists.sr.ht/~protesilaos/modus-themes/%3Cm2r0zszc2z.fsf@gmail.com%3E.

  • Covered the read-multiple-choice-face by adding a noticeable background colour to it. The default attributes it has, which look like other key bindings (bold and blue) plus an underline are technically okay, though the context of this face is in the echo area which is one line tall. Moreover, the highlighted keys are inlined with other text. These make it difficult to spot the highlights without some extra spacing. I use the addition of a background in Org’s export dispatcher interface which also has some unique requirements (the org-dispatcher-highlight face). The principle is to have theme-wide consistency (e.g. “all key bindings must look the same”) EXCEPT when the specifics require a different set of styles in the interest of usability.

  • Extended the coverage of the auctex package’s faces to include the font-latex-underline-face. Thanks to Luis Miguel Castañeda for reporting a typo I made which caused an error: https://lists.sr.ht/~protesilaos/modus-themes/%3C7h7d2oudpb.fsf@imaginarymagnitude.net%3E

  • Added support for crontab-mode. Thanks to Antonio Ruiz for the patch: https://lists.sr.ht/~protesilaos/modus-themes/patches/35080. It is below the ~15 line threshold and thus requires no copyright assignment to the Free Software Foundation.

  • Extended support for the company package’s company-scrollbar-bg and company-scrollbar-fg faces.

  • Added support for the spell-fu package. Thanks to Antonio Ruiz for the patch: https://lists.sr.ht/~protesilaos/modus-themes/%3C87fshnq7uv.fsf%40purelymail.com%3E. Same as further above for Antonio’s copyright status.

  • Moved the selectrum-prescient faces to the prescient group, to be consistent with changes in the respective upstream packages. Thanks to okamsn for the contribution, which was done in pull request 41 on the GitHub mirror: https://github.com/protesilaos/modus-themes/pull/41. The user okamsn has assigned copyright assignment to the Free Software Foundation, although this patch is within the allowed limits.

Change to ‘fill-column-indicator’

Made the fill-column-indicator face more noticeable. It is what the display-fill-column-indicator-mode uses to draw a line on where the fill-column is.

This change is in response to private messages I received as well as this, at parts impolite and toxic, thread that I refrained from participating in: https://lists.gnu.org/archive/html/help-gnu-emacs/2022-08/msg00255.html.

[ I do not follow that mailing list, by the way. All my projects have multiple communication channels and I always reply in a timely fashion. Social media, fora about Emacs, generic mailing lists, etc. are not among those channels. https://protesilaos.com/codelog/2022-07-24-report-issues-official-channels/. ]

The core idea is that the previous design was (1) considered “invisible” and (2) it prevented the customisation of the user option display-fill-column-indicator-character.

I am addressing point 1, but point 2 puts us in an awkward spot as we would then not be allowed to use a background and a height value. Not doing so produces a dashed line by default, with the dashes further apart the greater the line-spacing is (especially in, say, Org headings that can have a greater height than paragraph text). It looks broken and I keep getting requests to fix what is not the themes’ fault. So no, the themes will remain opinionated in this regard by ignoring display-fill-column-indicator-character through the styling they apply to make the line contiguous.

For context, also read Emacs bug#57424 and please don’t take my words in a private message out of context. If I need to state my opinion in a public setting, I know how to do it. https://debbugs.gnu.org/cgi/bugreport.cgi?bug=57424.

Refinement to modus-vivendi ‘bg-diff-focus-removed’ colour

Made the default removed diff background slightly more luminant. The colour is seen in diff-mode, ediff, and the Magit focused diff hunk.

When the user option modus-themes-diffs is set to either bg-only or desaturated, this colour is used to highlight word-wise (“refined”) changes. The increased luminance lets it stand out more compared to the more subtle backdrop.

Thanks to Kévin Le Gouguec for bringing this issue to my attention and for discussing it with me: https://lists.sr.ht/~protesilaos/modus-themes/%3C87bks4i9tg.fsf@gmail.com%3E

Note about ‘goto-address-mode’

Quote from the manual:

The built-in 'goto-address-mode' uses heuristics to identify URLs and
email addresses in the current buffer.  It then applies a face to them
to change their style.  Some packages, such as 'notmuch', use this
minor-mode automatically.

The faces are not declared with 'defface', meaning that it is better
that the theme does not modify them.  The user is thus encouraged to
consider including (or equivalent) this in their setup:

    (setq goto-address-url-face 'link
          goto-address-url-mouse-face 'highlight
          goto-address-mail-face 'link
          goto-address-mail-mouse-face 'highlight)

My personal preference is to set 'goto-address-mail-face' to nil, as
it otherwise adds too much visual noise to the buffer (email addresses
stand out more, due to the use of the uncommon '@' character but also
because they are often enclosed in angled brackets).

Changes to the manual

-1:-- Modus themes 2.7.0 for GNU Emacs (Post)--L0--C0--October 01, 2022 12:00 AM

Alvaro Ramirez: dwim-shell-command usages: pdftotext and scp

01 October 2022 dwim-shell-command usages: pdftotext and scp

dwim-shell-command is a little Emacs package I wrote to enable crafting more reusable shell commands. I intended to use it as an async-shell-command alternative (and I do these days). The more surprising win was bringing lots of command-line utilities (sometimes with complicated invocations) and making them quickly accessible. I no longer need to remember their respective parameters, order, flags, etc.

I've migrated most one-liners and scripts I had to dwim-shell-command equivalents. They are available at dwim-shell-commands.el. Having said that, it's great to discover new usages from dwim-shell-command users.

Take u/TiMueller's Reddit comment, showcasing pdftotext. Neat utility I was unaware of. It does as it says on the tin and converts a pdf to text. Can be easily saved to your accessible repertoire with:

(defun  dwim-shell-commands-pdf-to-txt ()
   "Convert pdf to txt."
  (interactive)
  (dwim-shell-command-on-marked-files
    "pdf to txt"
    "pdftotext -layout '  ' ' .txt '"
    :utils  "pdftotext"))

pdf-to-txt_x2.webp

tareefdev wanted a quick command to secure copy remote files to a local directory. Though this use-case is already covered by Tramp, I suspect a DWIM command would make it a little more convenient (async by default). However, Tramp paths aren't usable from the shell unless we massage them a little. We can use dwim-shell-command-on-marked-files's :post-process-template to drop the "/ssh:" prefix.

(defun  dwim-shell-commands-copy-remote-to-downloads ()
  (interactive)
  (dwim-shell-command-on-marked-files
    "Copy remote to local Downloads"
    "scp '  ' ~/Downloads/"
    :utils  "scp"
    :post-process-template
   (lambda (script file)
      ;;  Tramp file path start with "/ssh:". Drop it.
     (string-replace file
                     (string-remove-prefix  "/ssh:" file)
                     script))))

dwim-shell-command is available on MELPA (531 downloads as of 2022-10-01).

-1:-- dwim-shell-command usages: pdftotext and scp (Post)--L0--C0--September 30, 2022 11:00 PM

Protesilaos Stavrou: Emacs: denote version 1.0.0

Denote is a simple note-taking tool. It is based on the idea that notes should follow a predictable and descriptive file-naming scheme. The file name must offer a clear indication of what the note is about, without reference to any other metadata. Denote basically streamlines the creation of such files while providing facilities to link between them.

Below are the release notes.


This is the first major release of Denote. A part of the changes documented herein is for advanced users or developers who wish to extend Denote with their custom code. Though we first cover what applies to everyone.

Changes for all users

  • The custom Org hyperlink type of denote: can be visited from outside the denote-directory. We now provide the necessary glue code that Org needs to store these denote: links. Storing them can be done with an org-capture template or via the command org-store-link. Use this to, for example, capture a TODO that references one of your notes.

    denote: links work for as long as the referenced file is somewhere in the denote-directory or one of its subdirectories.

    Thanks to Marc Fargas for the contribution. Marc did not need to assign copyright to the Free Software Foundation, as the patch was within the ~15 line limit that is permissible.

    The contribution was discussed on the mailing list: https://lists.sr.ht/~protesilaos/denote/patches/35137. A prior exchange took place in issue 104 over at the GitHub mirror: https://github.com/protesilaos/denote/issues/104.

    Some further tweaks were made to the relevant function. Thanks to Elias Storms for reporting on the mailing list a bug which revealed a regression I introduced to the Org link storing mechanism: https://lists.sr.ht/~protesilaos/denote/%3C15D55F4B-64D1-4083-AD5E-B5BACA8F1909%40ap.be%3E.

  • Following from above, the command denote-link-find-file finds files reliably, regardless of where the link is stored. All it needs is for the target file to be inside the denote-directory.

    I discovered this while exchanging views with Marc Fargas regarding the aforementioned patch: https://lists.sr.ht/~protesilaos/denote/patches/35137.

  • The command denote-link-buttonize-buffer, which “buttonizes” denote: links in plain text and Markdown files, now performs its task regardless of where the current file is stored. Those links work for as long as the file they reference is somewhere inside the denote-directory.

  • The commands denote-link-after-creating, denote-link-or-create provide a convenience for users who need to create link to notes that may not exist yet. The idea is that one is expounding on a given topic and wants to create a link to a relevant issue. They are not sure if they have written anything about it yet, so they invoke the relevant command. Consult their doc strings or read the manual: https://protesilaos.com/emacs/denote#h:9e41e7df-2aac-4835-94c5-659b6111e6de.

    Thanks to user sienic for suggesting the idea and for testing the prototypes. And thanks to Juanjo Presa for participating in the discussion to share the view that this functionality should be part of denote.el. This happened in issue 96 over at the GitHub mirror: https://github.com/protesilaos/denote/issues/96.

  • The command denote-open-or-create offers the convenience of visiting a file, if it exists, else prompting for its creation. Thanks to Alan Schmitt for the contribution. The patch was sent on the mailing list: https://lists.sr.ht/~protesilaos/denote/%3C87fsgvddny.fsf%40protesilaos.com%3E. It is within the limit of what is allowed without assigning copyright to the Free Software Foundation, though Alan has done the relevant paperwork.

  • The manual expands on two sections: (1) Variants of denote-open-or-create, (2) Variants of denote-link-or-create. They show how one can use the above “do or create” commands with different permutations of the Denote prompts for new note creation.

  • The manual includes a section titled “Create a note with the region’s contents”. Quote:

    Sometimes it makes sense to gather notes in a single file and later review it to make multiple notes out of it. With the following code, the user marks a region and then invokes the command my-denote-create-new-note-from-region: it prompts for a title and keywords and then uses the region’s contents to fill in the newly created note.

    This is not part of denote.el, though we provide it in the manual for users that may need it. Thanks to sundar bp for suggesting the idea. This was done via a private channel and the information is shared with permission.

  • The manual has another entry titled “Split an Org subtree into its own note”, which is similar to the above idea of using the region’s contents but has some extra niceties provided by Org. Quote:

    With Org files in particular, it is common to have nested headings which could be split off into their own standalone notes. In Org parlance an entry with all its subheadings is a “subtree”. With the following code, the user places the point inside the heading they want to split off and invokes the command my-denote-split-org-subtree. It will create a note using the heading’s text and tags for the new file. The contents of the subtree become the contents of the new note and are removed from the old one.

    Thanks to Sven Seebeck for suggesting the idea and for testing my prototypes. This information is shared with permission, as it was provided via a private channel.

  • The manual describes how a user can leverage the built-in dired-virtual-mode to perform arbitrary sorting of their list of notes. It also includes code for Eshell to quickly “export” a command’s output into a dedicated buffer (which can then be used to derive a “virtual” Dired). Thanks to Yi Liu for asking the question that inspired this entry: https://lists.sr.ht/~protesilaos/denote/%3C1C75FF01-EC76-49DF-9AEB-ED718A2795FF@gmail.com%3E.

  • The denote-faces-broken-link has been removed. It was used for Org links. The idea was to apply a different style if the link was broken. However, the way fontification works means that there may be a performance penalty as Org tries to check again and again if the link is broken or note. As denote: links are robust (unless the user tries to break them), this penalty is unacceptable. Thanks to Peter Prevos for reporting the issue and discussing it with me on the mailing list: https://lists.sr.ht/~protesilaos/denote/%3C87k05umyyo.fsf%40prevos.net%3E.

  • The “denote” group in Custom UI buffers now provides a link to the Info manual that is shipped with the package. To read the manual, evaluate (info "(denote) Top"). Else visit the official web page: https://protesilaos.com/emacs/denote.

  • Fixed a case where an internal check for a note would throw an error if the buffer was not visiting a file. Thanks to Hilde Rhyne was the patch: it is below the ~15 line threshold and thus does not require copyright assignment to the Free Software Foundation. The issue was discussed on the mailing list and was pushed to users as version 0.6.1: https://lists.sr.ht/~protesilaos/denote/%3Cm035d7nq22.fsf%40disroot.org%3E.

  • When linking to a file that has no front matter, Denote tries to use the TITLE component of the file name (per our file-naming scheme) as the link’s descriptive text. We now make this look a bit better, by capitalising only the first letter while dehyphenating the text, converting this-is-a-test to This is a test. Before, we would capitalise all words. Thanks to Clemens Radermacher for the patch. It was sent via a private channel. Clemens has assigned copyright to the Free Software Foundation.

Changes for developers or advanced users

Lots of functions and variables which once were for “private” use (the presence of double hyphens in the symbol) are now made public. Concretely this means that they no longer have double hyphens in their name and we pledge to support them henceforth. “Support” means that we (i) consider them stable, (ii) document them properly, (iii) will record any changes made to them such as in a change log, a blog post on my website, and via make-obsolete.

The manual provides a complete reference of what is on offer. The section is titled “For developers or advanced users”: https://protesilaos.com/emacs/denote#h:c916d8c5-540a-409f-b780-6ccbd90e088e.

Normally, we do not support private forms and can delete/modify them without notice. However, I decided to write obsoletion aliases for all forms I made public or otherwise revised, in an effort not to break any existing custom code. The following table covers all obsolete symbols and their new counterparts. PLEASE UPDATE YOUR CODE as those aliases will be removed in the near future.

Index Old symbol New symbol
1 denote–id-format denote-id-format
2 denote–id-regexp denote-id-regexp
3 denote–title-regexp denote-title-regexp
4 denote–keywords-regexp denote-keywords-regexp
5 denote–punctuation-regexp denote-excluded-punctuation-regexp
6 denote-punctuation-excluded-extra-regexp denote-excluded-punctuation-extra-regexp
7 denote–sluggify denote-sluggify
8 denote–sluggify-and-join denote-sluggify-and-join
9 denote–sluggify-keywords denote-sluggify-keywords
10 denote–desluggify denote-desluggify
11 denote–only-note-p denote-file-is-note-p
12 denote–file-has-identifier-p denote-file-has-identifier-p
13 denote–file-supported-extension-p denote-file-has-supported-extension-p
14 denote–writable-and-supported-p denote-file-is-writable-and-supported-p
15 denote–file-name-relative-to-denote-directory denote-get-file-name-relative-to-denote-directory
16 denote-link–id-from-string denote-extract-id-from-string
17 denote–directory-files denote-directory-files
18 denote–subdirs denote-directory-subdirectories
19 denote–get-note-path-by-id denote-get-path-by-id
20 denote–directory-files-matching-regexp denote-directory-files-matching-regexp
21 denote–retrieve-read-file-prompt denote-file-prompt
22 denote–extract-keywords-from-path denote-extract-keywords-from-path
23 denote–keywords-prompt denote-keywords-prompt
24 denote–retrieve-filename-identifier denote-retrieve-filename-identifier
25 denote–file-name-id denote-retrieve-or-create-file-identifier
26 denote–retrieve-filename-title denote-retrieve-filename-title
27 denote–retrieve-title-value denote-retrieve-title-value
28 denote–retrieve-title-line denote-retrieve-title-line
29 denote–retrieve-keywords-value denote-retrieve-keywords-value
30 denote–retrieve-keywords-line denote-retrieve-keywords-line
31 denote–format-file denote-format-file-name
32 denote–barf-duplicate-id denote-barf-duplicate-id
33 denote–title-prompt denote-title-prompt
34 denote–file-type-prompt denote-file-type-prompt
35 denote–date-prompt denote-date-prompt
36 denote–subdirs-prompt denote-subdirectory-prompt
37 denote–template-prompt denote-template-prompt
38 denote–filetype-heuristics denote-filetype-heuristics
39 denote–rename-file denote-rename-file-and-buffer
40 denote–rename-file-prompt denote-rename-file-prompt

If you are writing code that extends Denote and feel that something is either missing or has remained private, please contact us on the mailing list, the GitHub/GitLab mirror, or send me an email directly. I always respond in a timely fashion.

Open to everyone

The most common feedback I get about Denote is that its documentation is good. As you can tell from these change logs, the plan is to continue on this path.

Please note that the communication channels for Denote (mailing list, mirrors, my personal email) are open to users of all levels. Do not hesitate to contact us/me.

Thanks again to everyone for their contributions, direct or indirect, either in the form of code or the discussion of ideas. Quoting from the “Acknowledgements” section of the manual (all my packages have such a section):

Denote is meant to be a collective effort. Every bit of help matters.

  • Author/maintainer: Protesilaos Stavrou.

  • Contributions to code or the manual: Abin Simon, Alan Schmitt, Benjamin Kästner, Clemens Radermacher, Colin McLear, Damien Cassou, Eshel Yaron, Hilde Rhyne, Jack Baty, Jean-Philippe Gagné Guay, Jürgen Hötzel, Kaushal Modi, Kyle Meyer, Marc Fargas, Peter Prevos, Philip Kaludercic, Quiliro Ordóñez, Stefan Monnier.

  • Ideas and/or user feedback: Abin Simon, Alan Schmitt, Alfredo Borrás, Benjamin Kästner, Colin McLear, Damien Cassou, Elias Storms, Frank Ehmsen, Hanspeter Gisler, Jack Baty, Juanjo Presa, Kaushal Modi, M. Hadi Timachi, Paul van Gelder, Peter Prevos, Shreyas Ragavan, Summer Emacs, Sven Seebeck, Taoufik, Yi Liu, Ypot, atanasj, hpgisler, pRot0ta1p, sienic, sundar bp.

Special thanks to Peter Povinec who helped refine the file-naming scheme, which is the cornerstone of this project.

Special thanks to Jean-Philippe Gagné Guay for the numerous contributions to the code base.

-1:-- Emacs: denote version 1.0.0 (Post)--L0--C0--September 30, 2022 12:00 AM

Irreal: Video On The Birth of Emacs

I just ran across a video on the birth of Emacs from last year. It’s by Lars Brinkhoff, Irreal’s go to guy for questions on the history of Emacs. As I’ve written before, he maintains a repository of historical Emacs code all the way back to TECO.

The video is a series of demonstrations of some of that ancient code. It starts with showing what a pain it was to use TECO. It did, however, have a macro language that was fairly capable and Emacs famously began life as a set of macros for TECO. It’s where the name Emacs comes from: Editing MACroS. It was a sort of compilation of the TECO macros that individual users at MIT’s AI labs had written. The idea was that then everyone would be working in the same environment.

Since Emacs was originally written as TECO macros, it wasn’t very portable so it was rewritten in various languages such as LISP and C. The video gives a demonstration of some of those old systems. If nothing else, watching them will show you how good we have it now.

Now, of course, Emacs is synonymous with GNU Emacs, which is available on essentially any platform that matters. Brinkhoff tells a nice story. What began as a way of letting hackers share someone else’s TECO session has blossomed into what many of us consider the world’s best editor. Not everyone agrees with that last assessment but none but the ignorant doubt that it’s certainly a contender.

The video is about 41 and a half minutes long so you’ll need to schedule some time. If you’re interested in old Emacs implementations and how things used to be, take a look at the video.

-1:-- Video On The Birth of Emacs (Post jcs)--L0--C0--September 29, 2022 05:40 PM

Protesilaos Stavrou: Emacs: new ‘agitate’ package (extras for VC Git and friends)

Before I explain what this is, here is the dictionary entry for AGITATE: Another Git Interface Trying to Agitate Tranquil Emacsers. 🙃

agitate is a work-in-progress collection of commands or potentially useful functions that expand on the available version control features of Emacs. Those are meant to complement a workflow that relies on the built-in Version Control (VC) framework and its accoutrements: diff-mode.el, log-view.el, log-edit.el, vc-git.el, and potentially others.

The package is available only on the GNU-devel ELPA for the time being. I shall release a stable version when the time is right. I explain all about it the “devel” archive and relevant topics here: https://protesilaos.com/codelog/2022-05-13-emacs-elpa-devel/.

At this stage, the intent is to iterate on the agitate code in order to surface the best practices and, perhaps, discover the most common missing requirements of VC users. The emphasis is on the Git backend, as that is what I am using, but there is no reason to limit the code to it. I do want to learn about other version control systems.

Unlike most of my packages, agitate has a maximalist scope by design. Instead of, say, adding only to vc-git.el it takes a broader view of the entire workflow of VC, which ties together processes from different libraries (e.g. on Emacs 29, C-x v v (vc-next-action) in a diff-mode buffer will produce a log-edit-mode buffer to commit the changes).

-1:-- Emacs: new ‘agitate’ package (extras for VC Git and friends) (Post)--L0--C0--September 29, 2022 12:00 AM

Irreal: Emacs As A Bash IDE

Torstein Johansen has an interesting video on using Emacs as an IDE for shell programming—especially Bash shell programming. We, or at least I, don’t usually think of shell programming as requiring an IDE but as Johansen shows, a bit of Emacs configuration can significantly ease the task.

He starts off with sh-mode and shows how to adjust the indentation. That gives you syntax highlighting and automatic indentation. Add autocompletion (with Hippie Expand) to help with entering code and navigation (mostly with Projectile) to make finding functions—even if they’re in a different file—easy and you’ve got a good basic Emacs setup for working on shell files but Johansen doesn’t stop there.

He adds LSP and flycheck-mode for advanced navigation and code linting. He shows how to run your script right from Emacs and even how to output an execution trace. He also shows how to run unit tests and perform debugging. Those require adding separate packages and binaries, of course, but if you do a lot of shell programming it’s well worth the effort and space on your system.

Finally, he covers some of the yasnippets that he uses and gives a quick demonstration of git-change-markers. Neither is particular to shell scripts, of course, but they can be useful.

Again, if you regularly work with shell scripts, this video is worth your time. It’s about 18 and a quarter minutes so plan accordingly.

WEATHER UPDATE [2022-09-28 Wed 14:03]: Ian came ashore near Ft. Myers (about 100 miles south of Tampa). Lots of flooding there but things in Tampa are still calm except for moderate, continuous rain. We’re still expecting strong winds and lots more rain as Ian moves up the peninsula.

-1:-- Emacs As A Bash IDE (Post jcs)--L0--C0--September 28, 2022 06:14 PM

Mario Jason Braganza: Restart Emacs on System Startup

This post was originaly published on 2022-09-27.
Updated 2022-09-28, to include the improved script.


It’s been a year, since I figured out the hack that finally let me use my Compose key in Emacs.1
Without it, I am unable to type any kind of quotation marks or umlauts in emacs.
A year in, and I’m tired of restarting the service everytime the machine comes on.
The computer can do that for me.

So I whipped up this tiny bash script

1
2
3
4
5
6
#!/usr/bin/env bash
if [[ $(( $(pidof -s emacs) )) -le 10000  ]]; then
	systemctl --user restart emacs; 
else
	:;
fi

It just arbitrarily checks for the Emacs service’s PID and then if it’s lesser than or equal to 10000, it goes ahead and restarts the service.
This is just some superstitious thing I did, because when I was troubleshooting all those months ago, I assumed that if Emacs started at an ID in the low thousands, it must not have been loading something and restarting it to get a higher process ID meant what ever it needed must have loaded.

I learnt a couple of things doing it this way too.

  1. The output of a bash command is a string.
  2. I use $(( some_numerical_string )) to convert said string into an integer
  3. : means pass in bash-speak

What I ought to do is to check for the existence of a PID and if there is one, to then go ahead and restart emacs. I’ll do that some other time. Because right now, I put this script into my system startup and it just works.


Update 2022-09-28

I obviously am incapable, of leaving well enough alone.
As I was in the bath this morning, it struck me that I was restarting the service using systemctl restart …
So I could just check for the exit status of a systemctl status command, and then depending on what I got, do something. If it was running, I could just restart it.
I checked on the command line and sure enough, if a service is running, the exit status is 0 and if it isn’t, the status is 3 .2
So … then I monkeyed with the script to this

1
2
3
4
5
6
7
#!/usr/bin/env bash

if systemctl --user status emacs > /dev/null; then
    systemctl --user restart emacs;
else
    :;
fi;

If the emacs service is running, restart it, else do nothing.
This ought to quiet the monkey brain for a while.
I hope 🙂


P.S. Subscribe to my mailing list!
Forward these posts and letters to your friends and get them to subscribe!
P.P.S. Feed my insatiable reading habit.


  1. all i have to do is restart my Emacs service as soon as the system boots up ↩︎

  2. I don’t know if it is always 3. It could probably vary. But it suffices in my case that it isn’t 0. ↩︎

-1:-- Restart Emacs on System Startup (Post)--L0--C0--September 28, 2022 05:30 AM

Mario Jason Braganza: 11

As is our wont, we spent the day gallivanting in a jungle and having an accident.
And then crying for a while and then laughing about what a story it would be for the rest of our lives.

Much like our marriage.
We make the best stories, don’t we?
And I am who I am, because of you.
Eleven years, and it still feels like only yesterday. 1
I love you.



Like the old hymn goes …

You alone are my strength, my shield … to you alone may my spirit yield
You alone are my hearts desire … 🎵



  1. We still compare our wedding pics with other folks, unilaterally declaring ours the much better set. ↩︎

-1:-- 11 (Post)--L0--C0--September 27, 2022 02:39 PM

Mario Jason Braganza: Blocks in Org Mode

I remember, when I first learned it, the Org Manual mentioning I could have code, quotes, poetry and sundry self structured blocks of text, where the text in that block would flow like I wanted it to. I could have indentation or line breaks as I pleased.
And then I promptly forgot about it.

The only thing I did remember were code blocks.
And that I needed to do a #+begin_src and then a #+end_src and put my code in the middle. And all this while, I would keep typing it in, by hand.

Until I tired of keeping on doing that shit, because there are more and more notes that are now going into my zettelkasten and decided well, the computer can do that for me.
So, I went back to the Org Mode documentation and discovered Structure Templates.
And now, I am at peace!

Turns out, all I needed was (org-insert-structure-template) aka C-c C-,
Here’s some code I’ve selected in a document


Now I hit C-c C-,, which brings up a whole host of options, with a prompt!
Do I want this block of text to be a quote? Or some verse?
Well, this is just boring code, so I choose source with the s key


And tada! The block is surrounded by the begin and end tags, and is now a source block! I could add the programming language after #+begin_src to get highlighting as well, but that’s a story for another day.

I’m practicing this, so that I get C-c C-, into my muscle memory, because be it verse, quote, code, example or exports, I know I will be making heavy use of Structure Templates.


P.S. Subscribe to my mailing list!
Forward these posts and letters to your friends and get them to subscribe!
P.P.S. Feed my insatiable reading habit.

-1:-- Blocks in Org Mode (Post)--L0--C0--September 27, 2022 08:37 AM

Jeremy Friesen: On Storing Glossary Terms in Org Roam Nodes

Hacking on Org-Mode

This post started as a place for me to perform analaysis on my existing blogging ecosystem. The goal is to consolidate where I’m “storing” information. The strategy is moving elements of my blogging ecosystem into my Personal Knowledge Management (PKM 📖) system; and then exporting those back to my blog.

Writing to Think Through the Problem

At present I store my glossary in a YML file. Table 240: Take on Rules's Glossary Entity Attributes describles the attributes.

Table 240: Take on Rules’s Glossary Entity Attributes
AttributeStatusDescription
abbrOptionalA short-hand means of referencing the entity; it’s abbreviation
auto_mentionTo RemoveUsed by `glossary:autoMention` rake task
content_disclaimersOptionalConnects this entry to a content disclaimer
describedTo RemoveHave we already described this node; useful for preventing repeat querying of Wikidata
descriptionOptionalA terse explanation of the entry; often imported from Wikidata
gameOptionalIndicates the entry is a game; the value should be the entry’s key
itemidOptionalA URL that disambiguates this entry ; From https://schema.org
itemtypeOptionalFurther specification on the classification of this entry; From https://schema.org
keyRequiredHow we programatically reference this entry
mention_asOptionalWhen we add a mentionAs attribute use this value; From https://schema.org
offerOptionalThe URL from which you can buy the item; From https://schema.org
plural_abbrOptionalThe plural form of the abbreviation
plural_titleOptionalThe plural form of the title
same_asOptionalConnect the item to a more verbose description (e.g. a Wikipedia article)
tagOptionalWhen used as a tag, add a "mention" node
titleRequiredHow we linguistically reference this entry
verbose_titleOptionalA more expressive title

Considerations for Migrating Glossary into Org Ecosystem

What would it look like to move the glossary into my Org ecosystem?

Let’s consider the following:

  • What would be the benefit?
  • What would be the storage strategy?
  • How to map the YAML entry to the Node’s data structure?
  • What are the ways in which Take on Rules references the glossary?

What Would be the Benefit?

The primary benefit that I see is in consolidation. Right now the benefits of the glossary are only available in my blogging ecosystem. And the “capture” process for those entries is outside of my normal capture process.

Further, in consolidation there is the process of thinking through the “problem” and designing a solution. The very thought exercise itself is enriching.

What Would Be the Storage Strategy?

I see two options: ./glossary or ./refs. The advantages of ./glossary is crisp demarkation. However I envision “promotion” or “drift” of refs into glossary items.

Decision
Store nodes in ./refs and add tag :glossary:

How to map the YAML entry to the Node’s data structure?

There is an analogy between my Epigraphs and my Glossary setup. An Epigraph has properties and it’s textual content (see Even if you decide never to write a single…).

In the case of a Glossary entry, the body of the node would be my additional notes. The node’s #+title: line would be it’s title property.

What Are the Ways in Which Take on Rules References the Glossary?

The following short-codes, part of the TakeOnRules Hugo Theme reference the glossary data:

themes/hugo-tufte/layouts/shortcodes/blockquote.html
when quoting a game, I add a purchase link to the citation.
themes/hugo-tufte/layouts/partials/header/contentDisclaimers.html
when a post has a glossary tag that has a content disclaimer, I look that up.
themes/hugo-tufte/layouts/shortcodes/glossary.html
this is where the major heavy liftin occurs; a glossary entry can be an abbreviation, mention, link, etc.

themes/hugo-tufte/layouts/_default/list.html :

themes/hugo-tufte/layouts/_default/single.html :

My assumption is that I would generate the ./data/glossary.yml file from my Org ecosystem.

To continue to leverage the ./data/glossary.yml as-is I would want to have means in my Org ecosystem to declare each of these.

Design

There are two major design considerations:

  • Registering New Link Types in Org-Mode
  • Data Migration

I’ve been working on this for a few days. During that time, and separate from this work, Charanjit Singh wrote Extending org-mode to handle youtube links.

Org-Mode 📖 has different link handlers. One example is the roam: scheme; a scheme added by Org-Roam 📖.

The org-link-set-parameters exposes a means of registering new schemes.

I have two “links” that I want to add: abbr: and abbr-plural:. The goal is to use my glossary to “link” to an entry that has an abbreviation and then at the point of export expand that abbreviation.

Reading the org-link-parameters documentation there are functions I want to set for both abbr: and abbr-plural::

:complete
What are the possible entries I have that meet the criteria? (e.g. have an abbreviation?)
:export
When I export this link, what should logic should I use? (e.g. for HTML 📖, I should use an ABBR-tag, for other things the title (abbreviation) is perhaps best)
:follow
When I “click” on the link in my Org-Mode document, how should it resolve? Where should it go?

In my blog I’ve used the DFN-tag; and that could be useful. But there’s always a matter of flow regarding a term and it’s definition.

Data Migration

I want to export my ./data/glossary.yml to Org-Mode. There are some glossary entries that already have nodes in Org-Roam; for those I need to reconcile and adjust. For the others, I need to create a node for each glossary entry.

Once that is complete, I will stop adding and editing entries in ./data/glossary.yml; instead I will export them from my org repository to that file. An ideal test is that when I complete the round trip, my ./data/glossary.yml is unchanged.

I already do this for Epigraphs, so follow a similar “public” api process.

Conclusion

After a bit of time and exploration, I have enriched my understanding of the Org-Mode / Org-Roam ecosystem and further worked towards treating my blog as a “consumer” of my PKM system.

I’ve also solidified my love of cl-defun, &key parameters, and providing defaults. This helps me repurpose functions and think about their interactions. It’s a pattern I’ve also applied in my Ruby code.

An example perhaps? The following is a method signature: (cl-defun jf/org-roam-external-url-for (&key node (scheme "http"))

To call that method I write the following: (jf/org-roam-external-url-for :node the_node); where the_node is an Org-Roam node. By default I’m looking for URLs that start with the http scheme.

Instead of burying the default http in the method definition, I parameterize it and specify the default.

All of this is in service to sharpening my tools.

-1:-- On Storing Glossary Terms in Org Roam Nodes (Post Jeremy Friesen (jeremy@takeonrules.com))--L0--C0--September 27, 2022 01:11 AM

Manuel Uberti: Update on Eglot and jdtls

This is just a quick update on the status of Eclipse JDT Language Server support in Eglot.

Since my Python skills are close to none, Roland Grunberg kindly stepped forward1 to make sure that jdtls can take care of setting up everything in order to avoid ugly workarounds in Emacs2 and, I guess, other text editors and IDEs as well.

The fix will be available on the next Eclipse JDT milestone3. However, nothing stops you from grabbing the latest snapshot build4 and M-x eglot away in your Java projects.

Even though I am not working with Java these days, it’s good to know that I could do it in Emacs with the nice features LSP offers.


-1:-- Update on Eglot and jdtls (Post)--L0--C0--September 27, 2022 12:00 AM

Sacha Chua: 2022-09-26 Emacs news

Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, r/planetemacs, Hacker News, lobste.rs, planet.emacslife.com, YouTube, the Emacs NEWS file, Emacs Calendar, emacs-devel, and lemmy/c/emacs.

-1:-- 2022-09-26 Emacs news (Post Sacha Chua)--L0--C0--September 26, 2022 01:39 PM

TAONAW - Emacs: MS Word Wins… For Now

I tried folks, I really tried, but for now, I can’t use Emacs for my technical writing, as much as I’d like to. This is far from over though. I’m planning a comeback sooner than later… Here are some of my thoughts so far.

The first, obvious reason I’m working in Word is: Everyone has it. We all have Office 365 accounts at work, which means everyone has either the full version of Word, access to the lighter online version, or even the app.

As I mentioned, I can paste HTML code directly from Emacs, but the problem is that KB articles are static. As I work with different subject matter experts (SME/SMEs in techcomm lingo) and technicians, Word’s ability to add comments to highlighted lines is critical. Indeed, when I work on a draft, it makes sense to start with a Word document first and worry about copy-pasting the article itself to ServiceNow later.

Ah yes, copy-pasting. The bread and butter of text editing outside of Emacs. Yes, I know how horrible Word’s HTML code is when you paste it directly, but it seems to litter less junk through ServinceNow’s WYSIWYG editor. That is, if I skip inserting HTML code directly (which is what I do with Emacs) and simply copy-paste from Word, the headers, links, and even the images are pasted and uploaded to the KB I’m working on automatically.

That last part is big. Working with images in Emacs is cumbersome. If I want to keep things organized, I need to attach images to the org file I’m working on (with org-attach) and attach the image file (with C-u C-c C-l) to see them in the org file1. I then need to individually upload the images into ServiceNow and place them where they belong in the text again, since copy-pasting from an HTML file does not copy over the images into ServiceNow.

Besides the easy copy-paste from Word and having comments in the article, There are plugins. For example, we might use Grammarly, which plugs into Word directly2. Of course, Word also plays nice with Outlook and Teams, and it’s easy to attach and share documents and work with OneDrive, all part of the Office family that is available to and used by all.

Am I happy? No. But I’m satisfied for now. I’ve created great workflows in org-mode. Checklists, clear steps with fewer images that show numbers and arrows for those steps3, helpful links that link to other related entries using my custom IDs, Footnotes, and more. These are great for my own notes and wikis, where I keep using org-mode for hours every day.

Also, Word sucks. I complained about it time and time again. It simply doesn’t make sense. The ribbon has a way of adjusting to the Window’s width, hiding important buttons (which I learned to recognize by the icon) until I adjust its size. Office also decided that since I use a dark theme for Windows, I will also want a dark theme for Word, and the color pallet for it is terrible; comments highlighting is nearly invisible, for example. It has this annoying feature that changes the direction of the mouse cursor to point right (as if I chose the left-handed cursor) whenever I edit a document, for whatever reason. It also decided to lay pages side by side for me, if my Window is wide enough, confusing my reading order. It takes forever to launch and has mysterious hangs that I’m trying to resolve. It… sigh. I’ll say it one more time: it sucks. The fact that everyone uses it does not make it better, but it does mean, unfortunately, that you have to know how to use it at least.

For all of its annoyances, Word has “features” Emacs doesn’t have. And you know what..? I’m fine with that. All of these things are terrible in Word, and I can’t even imagine how horrid they’d be in Emacs.

Still, this is just a setback. I figure I’ll learn to use Word again to the point I can get it to do what I need, and then turn around and see how I can make it work with Emacs without sacrificing efficiency. This will happen sometime down the line.

Comments?

Reply to this post on Mastodon,Twitter, or you can always email me: taonaw<at>protonmail<dot>ch (for GPG, click the lock icon on the navbar to the left).

Footnotes


  1. Another issue with org-mode and my new technical writing workflow is placing an image in a numbered list. I often need to do this as I write instructions. The image has to come at the end of the line of text of that step, without a line break, or the org-mode will consider the line break as the next step or otherwise break out of the list structure. In the org file itself, this is not a big problem, the images will still show under the text (I usually work in a narrow frame). However, when you convert the file to HTML, it will mess up the text. This means placing images with text requires more work. ↩︎

  2. Yes, alternative methods exist. I discussed langtool in Emacs, for example, which I use several times a week. While language tool is a good choice for my personal writing though, it is not the kind of service that offers the level of support Grammarly does. To me, it’s like comparing Gimp and Photoshop. ↩︎

  3. To see an example of what I mean, take a look at my post about customizing a theme. The image at the bottom of the post has arrows and numbers. I use these for my notes often, one image that summaries several steps described in the text above. This method however is not as straightforward enough when you need to share instructions. ↩︎

-1:-- MS Word Wins… For Now (Post)--L0--C0--September 26, 2022 12:00 AM

Charanjit Singh: Extending org-mode to handle youtube links

Requirement

  1. I want to open youtube links in my notes in mpv instead of opening the browser
  2. When exporting to HTML, youtube links should become embedded videos instead of hyperlinks

Implementation

Org has a concept of 'link types'. We can add a new type of link and have it behave the way we want.

  • org-link-parameters

    This variable contains link types and how they behave on follow (i.e when someone opens the link with C-c C-o), export etc. A type of a link is determined by the string before first ":" in it. e.g "https://bitspook.in" has https type, "file:///etc" has file type.

    Items in this list has 2 main components:

    1. First element is string representing the link type
    2. Key-value pairs of behavior name (e.g :follow, :export) and function which handles that behavior

    For example:

    (("yt" :follow spook-org--follow-yt-link :export spook-org--export-yt-link)
     ("eww" :follow org-eww-open :store org-eww-store-link))
    
  • org-link-set-parameters

    This function is used to add new link-types (and also to add new behavior to existing ones). You can check the docs for org-link-parameters (with C-h v org-link-parameters) to see arguments provided to each type of callback.

Here's the code I've added in my emacs config:

(defun spook-org--follow-yt-link (path prefix)
  (let* ((url (format "https:%s" path))
         (proc-name (format "*yt://%s*" url)))
    (if (and prefix (executable-find "mpv"))
        (browse-url url)
      (make-process :name proc-name :buffer proc-name :command `("mpv" ,url))
      (message "Launched mpv in buffer: %s" proc-name))))

(defun spook-org--export-yt-link (path desc backend)
  (when (eq backend 'html)
    (let* ((video-id (cadar (url-parse-query-string path)))
           (url (if (string-empty-p video-id) path
                  (format "//youtube.com/embed/%s" video-id))))
      (format
       "<iframe width=\"560\" height=\"315\" src=\"%s\" title=\"%s\" frameborder=\"0\" allowfullscreen></iframe>"
       url desc))))

(org-link-set-parameters "yt" :follow #'spook-org--follow-yt-link :export #'spook-org--export-yt-link)

make-process will create a background buffer named *yt://<url>* which allow monitoring and killing the mpv process.

Update: Use make-process instead of async-shell-command for launching mpv. Thanks to nv-elisp on /r/emacs

Result

  1. C-c C-o (i.e org-open-at-point) on a yt:// link opens the youtube video in mpv
  2. C-u C-c C-o (i.e org-open-at-point with an prefix argument) on a yt:// link opens the video in browser
  3. When exported to HTML, all yt:// links file are exported as embedded youtube videos

Bonus gains 💪

Recently I've been building a tool (cl-ownpress) which to make blogging a zero-effort activity for me. Since I have already built a habit of judicious note taking, publishing a subset of my notes will enable maintaining an active blog without doing any "work".

I've also been making thickly-accented-awkardly-narrated youtube videos. These are almost always tldr; videos for my blog posts. So they get embedded in my blog posts.

'Blog with notes + embed youtube videos' become easier to do with this little tinkering. Since cl-ownpress uses my running Emacs to publish my posts, I don't need to make any change in my blogging setup. I can embed my tldr; videos in my blog posts by just prefixing youtube links with yt:// instead of https://.

I am pretty happy that I got zero work blogging-setup, and zero work extending-the-blogging-setup as well.

-1:-- Extending org-mode to handle youtube links (Post Charanjit Singh)--L0--C0--September 26, 2022 12:00 AM

Andrey Listopadov: Reproducible Research with Org Mode, Fennel, and LÖVE

Today I would like to talk about three separate tools, and how we can combine them with the power of Emacs.

Our tools for today are:

  • Fennel - a Lisp-like surface syntax for Lua that makes programming in Lua fun and more robust.
  • Org Mode - a markup language and an application inside Emacs, which allows its users to create documents that include interactive pieces of code.
  • LÖVE - a game engine that allows programming games in Lua.

This post is itself a program, describing how you could create similar Org documents and explore literate programming/reproducible research with Fennel, or other languages for that matter. It’s possible thanks to Org Mode and my new package ob-fennel, which implements necessary functions for sending Fennel code blocks to the REPL process. For a better experience, you should read this in Emacs, and you can get the original .org file from here. If you want to follow the instructions, make sure to install Fennel and LÖVE for this to work.

Bootstrapping LÖVE

First, we need to bootstrap LÖVE so it could understand Fennel code:

local fennel = require "fennel"
table.insert(package.loaders or package.searchers, fennel.searcher)

We’ve required fennel the library and registered the fennel.searcher function as one of the functions responsible to loading files. With that, theoretically, we no longer need to write any more Lua code, and instead, we can write in Fennel, and then use plain Lua’s require to load the files. Let’s create a REPL (Read Eval Print Loop) that will allow us to do interactive programming in LÖVE via its thread and event systems.

First, let’s require fennel once again, and also require love.event. Next, we’ll do a little trick that I’ll explain later, but for now, we’ll only need to capture the module-scope value of ... into variables event and channel:

(local fennel (require :fennel))
(require :love.event)

(local (event channel) ...)

This will make sense in a moment. Now, we need a REPL itself. REPL consists of the prompt that asks for more input, and the loop that calls the prompt which reads some input and sends it to a channel. Let’s define the prompt function first:

(fn prompt [next-line?]
 (io.write (if next-line? "" ">> ")) (io.flush) (io.read))

Now, we can create the infinite loop, that will read from stdin and push the read value into LÖVE’s event system. That’s where we’ll use the captured event variable, however, we’ll need to run this loop only in a LÖVE thread, so we’ll check if the channel was bound:

(when channel
 ((fn loop [input]
 (love.event.push event input)
 (let [[event-type &as message] (channel:demand)]
 (match message
 [:data output]
 (each [_ ret (ipairs output)]
 (print ret))
 [:error kind & data]
 (print (string.format
 "%s error: %s" kind
 (accumulate [msg "" _ message (ipairs data)]
 (.. msg (tostring message))))))
 (loop (prompt (= event-type :next-line)))))
 (prompt)))

Once data appears in the channel, this function exterminates what pattern it received. It is always a table, where the first element means the kind of data, and the rest of the table is the data itself. That’s the protocol we’re going to write in a moment.

The loop check if the event-type is either :data or :error and acts accordingly. In addition to that, there’s a third event-type :next-line which represents the situation when the REPL receives incomplete input and waits for more lines. Let’s implement this protocol:

(let [thread (-> "repl.fnl"
 love.filesystem.read
 (fennel.compileString nil)
 (love.filesystem.newFileData :repl)
 love.thread.newThread)
 io-channel (love.thread.newChannel)
 coro (coroutine.create fennel.repl)]
 (->> {:readChunk (fn [{: stack-size}]
 (when (> stack-size 0)
 (io-channel:push [:next-line]))
 (coroutine.yield))
 :onValues (fn [data] (io-channel:push [:data data]))
 :onError (fn [kind ...] (io-channel:push [:error kind ...]))}
 (coroutine.resume coro))
 (thread:start :eval io-channel)
 (set love.handlers.eval #(coroutine.resume coro $)))

This is an asynchronous REPL, made with Lua’s coroutines. You may wonder, how it will start the loop function if it was under the when channel guard, but that’s exactly what this piece of code does.

First, it loads itself via love.filesystem.read and compiles Fennel code to Lua via fennel.compileString function. Next, this compiled representation is used to create a FileData object, that is used to start a LÖVE thread with the love.thread.newThread function. So this module will be executed twice - first when main.lua loads the file, and second, when LÖVE spawns the thread, but were not there, yet.

After we’ve created the thread we create an io-channel and the REPL coroutine. Next, we start the coroutine by passing it a table of functions, each of which will push data to the io-channel. Importantly, the readChunk function pauses the coroutine after every read, allowing it to run asynchronously within the main thread.

Finally, we start the thread by passing :eval which will be bound to the event variable, and io-channel which will be bound to channel. This thread spins the loop function, which blocks the thread waiting for the input, while the main LÖVE thread continues to run.

With all of that, we can finally require the repl.fnl file in the main.lua like this:

require "repl"

Also, let’s disable vsync while we’re at it. For some reason, it makes the REPL slow on my machine. We won’t be needing it anyway:

function love.conf(t)
 t.window.vsync = 0
end

Now we can start hacking in real time!

Drawing plots

Since we’re done with the literate programming example, let’s look at how the reproducible research can be organized. I’m sure you should be familiar with the concept of a notebook, like R Markdonw or Jupiter. We’re going to achieve something like that.

If you’re reading this file in Emacs, which you should, press the C-c C-v t shortcut, which will run the org-babel-tangle function. After executing it, Emacs will create three files main.lua, conf.lua, and repl.fnl in the same directory as this file. If you’re not reading this file in Emacs, then, well, get Emacs, or just believe me - it works.

Let’s plot a function via LÖVE2D and Fennel. I’m not sure if there’s a library for drawing plots with LÖVE, but it’s ain’t fun to use something like that anyways, so let’s build our own! First, we’ll need a Canvas object to draw to:

(local canvas (love.graphics.newCanvas 640 480))

Put the cursor in this source code block and press C-c C-c shortcut (or call the org-babel-execute-src-block function with M-x) The empty LÖVE window will appear, and Emacs will display a message in the prompt area, with the contents something like “Please re-evaluate once Fennel is initialized”. We won’t be needing this window, so you can minimize it - we’ll be drawing off-screen onto a canvas and then saving it as an image. Org-mode provides us with all we’ll need to preview results without leaving Emacs.

Once Fennel is loaded, and all threads were spawned, hit C-c C-c again, and you should see "nil" in the echo area. This means that the code was executed successfully and the canvas variable was created.

Now, let’s write a function that will draw a grid to the active canvas. First, we’ll need a function that will calculate the grid step based on the zoom level. Our zoom level is represented in percentages, which we’ll later convert to values by dividing by 100. However, for our grid, we will be doing an infinite zoom with finite details.

This function computes the step between each grid line based on current zoom level.

(fn calc-step [zoom]
 (let [gscale (^ 10 (bit.bor (math.log zoom 10) 0))]
 (* zoom 100 (/ gscale))))

It’s a bit convoluted, but the key here is the bitwise or operator - that’s what makes our grid cycle between zoom levels properly. We now can define the function that computes and draws the grid itself:

(fn draw-grid [zoom]
 (let [(w h) (: (love.graphics.getCanvas) :getDimensions)
 step (calc-step zoom)]
 (love.graphics.setColor [1 1 1 1])
 (love.graphics.rectangle :fill 0 0 w h)
 (love.graphics.setLineWidth 1)
 (each [_ [step color]
 (ipairs [[(/ step 10) [0.7 0.7 0.7 (/ (% step 1000) 1000)]]
 [(* step 1) [0.9 0.9 0.9 (- 1 (/ (% step 1000) 1000))]]])]
 (let [grid []]
 (fcollect [x (+ (/ w 2) step) w step :into grid] [x 0 x h])
 (fcollect [x (- (/ w 2) step) 0 (- step) :into grid] [x 0 x h])
 (fcollect [y (+ (/ h 2) step) h step :into grid] [0 y w y])
 (fcollect [y (- (/ h 2) step) 0 (- step) :into grid] [0 y w y])
 (love.graphics.setColor color)
 (each [_ line (ipairs grid)]
 (love.graphics.line line))))))

This function actually draws two grids - one for unit size, and one for 1/10 of the unit size, and applies dynamic transparency based of the zoom level. This way, we can create an illusion of the infinite zoom level, that It’s a bit verbose, but all it really does is create a table of line segments from the center of the canvas in all four directions with the calculated step interval.

This only draws the grid, though, we also need to draw the X and Y axis, with some markings indicating the zoom level:

(fn draw-axis [zoom]
 (let [(w h) (: (love.graphics.getCanvas) :getDimensions)
 step (calc-step zoom)]
 (love.graphics.setLineWidth 1)
 (love.graphics.setColor [0.3 0.3 0.3 1])
 (love.graphics.line [(/ w 2) 0 (/ w 2) h])
 (love.graphics.line [0 (/ h 2) w (/ h 2)])
 (let [h (/ h 2) w (/ w 2)]
 (for [x step w step]
 (love.graphics.line [(+ x w) h (+ x w) (+ h 2)])
 (love.graphics.print (string.format "%.1f" (/ x zoom)) (+ x w) (+ h 5)))
 (for [x (- step) (- w) (- step)]
 (love.graphics.line [(+ x w) h (+ x w) (+ h 2)])
 (love.graphics.print (string.format "%.1f" (/ x zoom)) (+ x w) (+ h 5)))
 (for [y step h step]
 (love.graphics.line [w (+ y h) (+ w 2) (+ y h)])
 (love.graphics.print (string.format "%.1f" (- (/ y zoom))) (+ w 5) (+ y h)))
 (for [y (- step) (- h) (- step)]
 (love.graphics.line [w (+ y h) (+ w 2) (+ y h)])
 (love.graphics.print (string.format "%.1f" (- (/ y zoom))) (+ w 5) (+ y h)))
 (love.graphics.print 0 (+ w 5) (+ 5 h)))))

This function works similarly to the draw-grid one, except it only draws two lines intersecting at the canvas’ center, and the numbers, indicating units of measure. Each number also has a small mark at the axis for better readability.

Finally, let’s write a function plot that will accept a function fun which it will plot on the canvas, and some additional arguments, like from, to, step. Also, did I mention that you should press C-c C-c on each code block in this section? It will send the code to the running LÖVE process, and we will be able to see the results dynamically. But before we do that, let’s write a function that transforms so-called world coordinates to screen coordinates:

(fn world->screen-coordinates [[x y] zoom]
 (let [(w h) (: (love.graphics.getCanvas) :getDimensions)]
 [(+ (* x zoom) (/ w 2)) (+ (- (* y zoom)) (/ h 2))]))

Oh, and we also need a function for generating a unique file name for the output image:

(fn unique-fname [name suffix]
 (let [(base ext) (string.match name "(.*)%.(.-)$")]
 (if (love.filesystem.getInfo
 (.. base (or suffix "") "." ext))
 (unique-fname name (+ (or suffix 0) 1))
 (.. base (or suffix "") "." ext))))

Alright, here’s the plot function:

(fn plot [{:fun f : from : to : step : zoom}]
 (let [image (unique-fname "result.png")]
 (canvas:renderTo
 #(let [zoom (/ (or zoom 100) 100)
 (w h) (: (love.graphics.getCanvas) :getDimensions)]
 (draw-grid zoom)
 (draw-axis zoom)
 (love.graphics.setColor [1 0 0 1])
 (love.graphics.setLineWidth 1)
 (let [points (fcollect [x from to (or step 0.1)]
 (world->screen-coordinates [x (f x)] zoom))]
 (each [i [x1 y1] (ipairs points)]
 (match (. points (+ i 1))
 [x2 y2] (love.graphics.line x1 y1 x2 y2))))))
 (: (canvas:newImageData) :encode :png image)
 (print (.. (love.filesystem.getSaveDirectory) "/" image))))

This function uses the renderTo method of the canvas object. It accepts an anonymous function, which draws the gird, the axis, sets the color of the plot to red, and finally draws the plot with love.graphics.points. We can execute it like that, by pressing C-c C-c on the next code block:

(plot {:fun #(* $ $) :from -50 :to 50 :step 0.01 :zoom 5000})

This produces an image, which you should see if you call the org-toggle-inline-images function. As you can see, it plots a simple \(x^2\) function, but we can plot any other, like \(\tan(\sin(x))\):

(plot {:fun #(math.tan (math.sin $)) :from -10 :to 10 :zoom 5000})

Or a more complex example would be generating a square wave via this formula \(\sum_{k=1,3,5,7,...,29}^{} \frac{sin(x\mul{k})}{k}\):

(plot {:fun #(accumulate [res (math.sin $)
 _ k (ipairs (fcollect [k 3 29 2] k))]
 (+ res (/ (math.sin (* $ k)) k)))
 :from -500 :to 500 :step 0.01 :zoom 8000})

You get the idea.

Additionally, we can create a function that plots Org tables, like this one:

x y
0 0
2 2
5 7
6 3
8 12
15 20
17 -2
19 -7
25 0

The function is mostly similar to plot except instead of calling a function it just goes through each table row, and builds line segments to draw:

(fn plot-table [{: data : zoom}]
 (let [image (unique-fname "result.png")]
 (canvas:renderTo
 #(let [zoom (/ (or zoom 1000) 100)
 (w h) (canvas:getDimensions)]
 (draw-grid zoom)
 (draw-axis zoom)
 (love.graphics.setColor [1 0 0 1])
 (love.graphics.setLineWidth 1)
 (each [i row (ipairs data)]
 (let [[x1 y1] (world->screen-coordinates row zoom)]
 (match (. data (+ i 1))
 next-row
 (let [[x2 y2] (world->screen-coordinates next-row zoom)]
 (love.graphics.line x1 y1 x2 y2)))))))
 (: (canvas:newImageData) :encode :png (string.format image))
 (print (.. (love.filesystem.getSaveDirectory) "/" image))))

We can plot it like this, passing the table via the :var data=lines header property:

(plot-table {: data :zoom 1000})

You can build more complex functions, that, for example, draw several graphs on the same canvas, or draw entirely different types of graphs altogether. Though this requires defining functions for all these kinds of visualizations, and I guess this is why people tend to use other languages for the task, like R.

Inline evaluation

Well, graphs are cool and all, but what about supplying values into the text directly? We can do that too, and although, we don’t really need LÖVE for that, that’s a great way to showcase the ability to run multiple instances of Fennel, using completely different environments!

For example, suppose we have some kind of a formula, that, for example, calculates the Ackermann function:

(fn A [m n]
 (if (= n 0) 0
 (= m 0) (* 2 n)
 (= n 1) 2
 (A (- m 1)
 (A m (- n 1)))))

We can define the function \(n^2\) using the (fn square [n] (A 1 n)), and use it like (square 4) producing 16 as a result. Or, we can define a function that computes \(2\uparrow\uparrow{n}\) like this: (fn two⇈n [n] (A 2 n)). Calling it as (two⇈n 4) gives us 65536.

These code blocks are running in a separate session, using a regular Lua interpreter, instead of our LÖVE REPL. This is done by specifying the :session header argument, and giving it a name, which instructs ob-fennel to spawn a new process. Upon exporting, the results of the inline evaluation would update automatically which eliminates the problem of stale data in the paper. The results always reflect the actual code they were computed with.

The power of Org

The examples above are just the tip of the iceberg of what you can do with the Org package. Back when I wasn’t an Emacs user, I often found threads on the topic “why should I use Emacs?” and each of these threads contained at least one answer which just said “Org Mode”. I never understood why - what’s so good about Org that I can’t get from, say, Markdown?

Since then, I’ve switched to Emacs and started this blog, using Org Mode, Hugo, and the ox-hugo package. Org mode has a built-in exporting engine, which can export .org files to PDF, ODT, LaTeX, Markdown, or HTML files. The ox-hugo package adds another backend for this engine, which exports to Hugo compatible markdown, which is then used by Hugo to build this website.

And when I was starting this blog, I wasn’t sure that I made the right investment - I’ve just switched to Emacs a couple of months before, and I’m using this Org thing for writing. I was uneasy because if I would stop enjoying writing in this setup I might lose all motivation for writing altogether. But thankfully, I’ve liked the Org+Hugo combination, and it became the reason why I haven’t left Emacs since - it’s just too good as a writing experience.

Literate programming was the way I learned Org. I’ve used it to configure Emacs because Org Mode provides a way to load .org files as Emacs Lisp files, and you can replace your init.el with init.org if you want to. But to be honest, it’s a fun experiment, but I don’t find literate programming to be that practical. It’s even harder to consider, given that there aren’t a lot of tools besides Emacs, that allows writing programs in this style using any language. And you can’t just make your coworkers use Emacs all of a sudden. Yes, there are solutions specific to a particular language, but they lack usability in my opinion.

Reproducible research was a hot topic lately, and the growing popularity of Jupiter notebooks is a great indication. Org Mode might not provide all the fancy UI elements, that Jupiter notebook has, but theoretically speaking, nothing prevents you from writing a UI in a standalone tool, like the game engine, and using it as a rendering frontend from the comfort of Emacs. This post partly shows this, although, I went a more static route with embedded images. But still, anything is possible if you have the dedication to do it.

I hope this post was an interesting read, and you’ve become interested in Emacs and Org Mode, or even in Fennel and LÖVE combo. Feel free to contact me if you have any questions on the topic. See you soon!

-1:-- Reproducible Research with Org Mode, Fennel, and LÖVE (Post)--L0--C0--September 25, 2022 09:57 PM

Irreal: Describe Symbol

A short post for a lazy Sunday. Grant Rettke over at Wisdom and Wonder has a quick tip. He recommends trying describe-symbol for invoking Emacs builtin documentation. He says that it’s almost always what you want when you invoke describe-function or describe-variable. As far as I can see, it’s the same information. I looked at the source code but it was hard to tell if it adds extra data or not.

You invoke it with Ctrl+h o so if nothing else you need remember only one shortcut for your documentation needs.

UPDATE [2022-09-25 Sun 16:31]: Grant remarks in the comments that one of the reasons he likes describe-symbol is because it tells you when there is a variable and function with the same name.

-1:-- Describe Symbol (Post jcs)--L0--C0--September 25, 2022 05:38 PM

Mario Jason Braganza: Links to Writing of Interest

Been meaning to write up a short note to some of my writing to point folks to.

So here goes. This serves as a mélange of the things I write and interest me.

  1. I’ve coauthored a book on linux, Linux for You and Me
  2. My blog at Janusworx
  3. I have written long form articles, to serve as teaching material for Linux Users’ Group of Durgapur

P.S. Subscribe to my mailing list!
Forward these posts and letters to your friends and get them to subscribe!
P.P.S. Feed my insatiable reading habit.

-1:-- Links to Writing of Interest (Post)--L0--C0--September 25, 2022 11:51 AM

Magnus: Annotate projects in Emacs

Every now and then I've wished to write comments on files in a project, but I've never found a good way to do that. annotate.el and org-annotate-file both collect annotations in a central place (in my $HOME), while marginalia puts annotations in files next to the source files but in a format that's rather cryptic and tends to be messed up when attached to multiple lines. None of them is ideal, I'd like the format to be org-mode, but not in a central file. At the same time having one annotation file per source file is simply too much.

I tried wrapping org-annotate-file, setting org-annotate-file-storage-file and taking advantage of elisp's dynamic binding. However, it opens the annotation file in the current window, and I'd really like to split the window and open the annotations the right. Rather than trying to sort of "work it out backwards" I decided to write a small package and use as much of the functionality in org-annotate-file.el as possible.

First off I decided that I want the annotation file to be called projectile-annotations.org.

(defvar org-projectile-annotate-file-name "projectile-annotations.org"
  "The name of the file to store project annotations.")

Then I wanted a slightly modified version of org-annotate-file-show-section, I wanted it to respect the root of the project.

(defun org-projectile-annotate--file-show-section (storage-file)
  "Add or show annotation entry in STORAGE-FILE and return the buffer."
  ;; modified version of org-annotate-file-show-section
  (let* ((proj-root (projectile-project-root))
         (filename (file-relative-name buffer-file-name proj-root))
         (line (buffer-substring-no-properties (point-at-bol) (point-at-eol)))
         (annotation-buffer (find-file-noselect storage-file)))
    (with-current-buffer annotation-buffer
      (org-annotate-file-annotate filename line))
    annotation-buffer))

The main function can then simply work out where the file with annotations should be located and call org-projectile-annotate--file-show-section.

(defun org-projectile-annotate ()
  (interactive)
  (let ((annot-fn (file-name-concat (projectile-project-root)
                                    org-projectile-annotate-file-name)))
    (set-window-buffer (split-window-right)
                       (org-projectile-annotate--file-show-section annot-fn))))

When testing it all out I noticed that org-store-link makes a link with a search text. In my case it would be much better to have links with line numbers. I found there's a hook to modify the behaviour of org-store-link, org-create-file-search-functions. So I wrote a function to get the kind of links I want, but only when the project annotation file is open in a buffer.

(defun org-projectile-annotate-file-search-func ()
  "A function returning the current line number when called in a
project while the project annotation file is open.

This function is designed for use in the hook
'org-create-file-search-functions'. It changes the behaviour of
'org-store-link' so it constructs a link with a line number
instead of a search string."
  ;; TODO: find a way to make the link description nicer
  (when (and (projectile-project-p)
             (get-buffer-window org-projectile-annotate-file-name))
    (number-to-string (line-number-at-pos))))

That's it, now I only have to wait until the next time I want to comment on a project to see if it improves my way of working.

-1:-- Annotate projects in Emacs (Post)--L0--C0--September 24, 2022 08:42 PM

Irreal: Extending Org Links and Youtube

Charanjit Singh has an interesting post on extending org-mode to handle youtube links. His goal was two-fold:

  1. Have an Org link type for Youtube videos that opened the video in mpv instead of the browwser
  2. When exporting to HTML the link should result in an embedded video rather than a link to Youtube

Most folks probably aren’t going to have those requirements, of course, but Singh’s post is still useful because it’s a great go-by for how to define and implement special links in Org-mode. The TL;DR is that you define a link type and two actions associated with it. The first action is what to do when the link is followed (Ctrl+c Ctrl+o). The second is how to export the link (Ctrl+c Ctrl+e).

In Singh’s case, the follow action is to display the video with mpv unless the follow is invoked with the universal argument in which case the browser is used. For export, Singh cares only about HTML for which the necessary markup for an embedded video is output.

The code is easy to follow so if you have a case for a special Org link, it’s worthwhile taking a look at Singh’s post to see how things are done.

-1:-- Extending Org Links and Youtube (Post jcs)--L0--C0--September 24, 2022 06:08 PM

Unwound Stack: pin 0.2

New release of my pin utility

-1:-- pin 0.2 (Post Michael (sp1ff@pobox.com))--L0--C0--September 24, 2022 07:00 AM

Manuel Uberti: Working with diff-mode

One Emacs package that has been with me for a very long time is diff-hl1, and the reason is not its on-the-fly coloured fringe markers, but the fact that colours stay there when I visit the same buffer later. This is useful since more often than not I end my work day with something still in progress, so the next morning it’s easier to use diff-hl-next-hunk and diff-hl-previous-hunk to move around my unfinished edits.

However, I have put diff-hl aside because there have been some welcome improvements lately on the built-in diff-mode, especially the possibility to edit its buffer contents and commit straight from it2. I now find myself using C-x v D (vc-root-diff) many times a day, and by using diff-mode more, I have started to appreciate one simple thing that was already available to me but have always ignored, that is the navigation among hunks with diff-hunk-next (n) and diff-hunk-prev (p).

The pattern, already become muscle memory, is the following:

  • C-x p p to enter the project I am working on
  • Visit the file I am interested in
  • Hit C-x v =
  • Move with either n or p to the desired hunk
  • Hit RET to jump to the hunk
  • Hit C-x 1 to hide the diff-mode buffer

Easy and quick. I guess one could even speed up the process by using something like recentf-mode, for instance, but I’ll leave that to you wild ELisp explorers.

By the way, after months with VC instead of Magit3, the combination of vc-dir, vc-git, diff-mode, and shell-command has resulted in one of the most dreaded command any serious Emacs user can think of, one that I cannot even type out of fear of mystical forces coming after me. Let’s just say it involves package-delete and… Dear GNU, they’re already here.


  1. diff-hl

  2. See: ‘C-x v v’ on a diff buffer commits it as a patch (bug#52349)

  3. My actual focus on VC roughly started last year, see: Rebasing with VC

-1:-- Working with diff-mode (Post)--L0--C0--September 24, 2022 12:00 AM

Protesilaos Stavrou: Ef themes 0.6.0 for GNU Emacs

The ef-themes is a collection of light and dark themes for GNU Emacs whose goal is to provide colourful (“pretty”) yet legible options for users who want something with a bit more flair than the modus-themes (also designed by me). Watch the presentation of the original version, which demonstrates the first eight themes in the collection and explains a few technical points: https://protesilaos.com/codelog/2022-08-18-ef-themes-demo/.


New tri-chrome themes

The collection now includes two themes which apply shades of magenta, blue, and teal in most contexts. They are named ef-trio-dark and ef-trio-light. I think some of you will like their aesthetics.

The announcement includes screenshots (and it also links to the page with all the pictures of the themes): https://protesilaos.com/codelog/2022-09-16-ef-themes-trio/.

As part of the development, I initially forgot to register the “trio” themes as part of the collection. Thanks to Sven Seebeck for informing me about it. (This is shared with permission, as it was done via a private channel.)

Changes to faces or face groups

  • Removed the override for the org-modern-label face. We used to affect its properties, which had the unintended effect of breaking the functionality of the user option org-modern-label-border. Users of the org-modern package may thus notice a difference in the effective typography that org-modern-mode produces.

    For the alignment of tables with timestamps (e.g. clocktables), it is recommended to set the user option org-modern-timestamp to nil. The manual covers this information in the section titled “Tweak org-modern timestamps”.

    Users who still need help with this are welcome to contact me in private or via the development channels of the Ef themes (mailing list or GitHub/GitLab mirrors).

    Thanks to Daniel Mendler (the org-modern developer) for explaining the technicalities and correcting errors I made in the previous version of that entry in the manual: https://github.com/protesilaos/ef-themes/commit/79bb1436f2fd8ab07e850c247c5616490f619f52.

  • Refined the backgrounds of regexp groupings. Commands such as re-builder and isearch-forward-regexp apply distinct colours to matching regexp groups. Those are now optimised to stand out more when seen side-by-side.

    Part of this work involved a thorough review of the applicable hues used by the ef-deuteranopia-dark and ef-deuteranopia-light (the two themes which are designed for users with red-green colour deficiency). The announcement on my website shows screenshots, including those which simulate deuteranopia: https://protesilaos.com/codelog/2022-09-18-ef-themes-deuteranopia-regexp/.

  • Added support for display-fill-column-indicator-mode. Thanks to Daniel Mendler for informing me about it (this is shared with permission, as it was done via a private channel).

  • Covered two faces that are available on Emacs 29 (current development target) for Version Control: vc-git-log-edit-summary-max-warning, and vc-git-log-edit-summary-target-warning.

Making use of ef-themes-select in init files

The manual includes improved language on how to use the function ef-themes-select in user init files. This is for users who need to leverage the ef-themes-post-load-hook at startup (using that hook is also explained in the manual). Basically, one loads a theme in their init file with something like:

;; like `load-theme' but also call `ef-themes-post-load-hook'
(ef-themes-select 'ef-summer)

Miscellaneous refinements

  • Simplified how the variable ef-themes-collection gets its value. Thanks to Philip Kaludercic for suggesting the use of seq-union over at the emacs-devel mailing list: https://lists.gnu.org/archive/html/emacs-devel/2022-09/msg01053.html.

    I could not use seq-union because it introduces a dependency on Emacs 28, whereas we support Emacs 27. I went with append instead.

  • Ensured that the background colour of matching delimiters in show-paren-mode is no longer red-tinted. We thus avoid the potential problem of mistaking those highlights for errors of some sort. This affects the themes ef-autumn, ~ef-light, and ef-night.

  • Recorded the colour distance and colour contrast between the base background values across all themes. The data is incorporated in the file contrast-ratios.org, which is part of the Git repository and is basically intended for my own use as part of this project’s development (though you are welcome to consult it).

What you do not see but still need to know about

I worked on two major changes but refrained from implementing them in this version. Whether I will do so in the future remains to be determined. The first is an option to apply “intense” colouration to the region face. The second pertains to a review of the base backgrounds across all dark themes in order to improve their contrast relative to the main background.

The following sections delve into the specifics. To cut the long story short: changing colours is not trivial work.

Considerations for the “intense” region

The following patch is deceptively simple. It defines some new colour values, adds a tiny function, and a user option. Why not add this to the themes, then? The reason is that colours are never that simple. Any new value for the region must (i) work with hl-line-mode, (ii) respect the highlights of show-paren-mode, (iii) complement or otherwise not interfere with isearch and its lazy highlights or any other search utility that can extend the active region highlight, (iv) keep the cursor visible, and (v) remain thematically consistent with the rest of the theme. Running those checks for 14 themes is no mean feat and there is always the chance that the new colours will still have a negative effect in places that are not expected. Furthermore, there is no guarantee that the number of Ef themes will stay at 14 (I have some more ideas, yes).

It is for such reasons that one of the principles of this project is to NOT offer customisation options that influence colours. They do not scale and will make things unmaintainable.

From 30506843a9692c6539de79d819b63f328dbb5638 Mon Sep 17 00:00:00 2001
Message-Id: <30506843a9692c6539de79d819b63f328dbb5638.1663662345.git.info@protesilaos.com>
From: Protesilaos Stavrou <info@protesilaos.com>
Date: Tue, 20 Sep 2022 11:25:15 +0300
Subject: [PATCH] Add user option for intense region

---
 ef-autumn-theme.el             |  1 +
 ef-dark-theme.el               |  1 +
 ef-day-theme.el                |  1 +
 ef-deuteranopia-dark-theme.el  |  1 +
 ef-deuteranopia-light-theme.el |  1 +
 ef-duo-dark-theme.el           |  1 +
 ef-duo-light-theme.el          |  1 +
 ef-light-theme.el              |  1 +
 ef-night-theme.el              |  1 +
 ef-spring-theme.el             |  1 +
 ef-summer-theme.el             |  1 +
 ef-themes.el                   | 20 +++++++++++++++++++-
 ef-trio-dark-theme.el          |  1 +
 ef-trio-light-theme.el         |  1 +
 ef-winter-theme.el             |  1 +
 15 files changed, 33 insertions(+), 1 deletion(-)

diff --git a/ef-autumn-theme.el b/ef-autumn-theme.el
index e4b6e02..cd18137 100644
--- a/ef-autumn-theme.el
+++ b/ef-autumn-theme.el
@@ -127,6 +127,7 @@ (eval-and-compile
       (bg-hover-alt  "#6f345a")
       (bg-hl-line    "#302a3a")
       (bg-region     "#3f1020")
+      (bg-region-intense "#3f5030")
       (bg-paren      "#7f2d40")
       (bg-err        "#361400") ; check with err
       (bg-warning    "#332800") ; check with warning
diff --git a/ef-dark-theme.el b/ef-dark-theme.el
index 2377178..3ff49aa 100644
--- a/ef-dark-theme.el
+++ b/ef-dark-theme.el
@@ -127,6 +127,7 @@ (eval-and-compile
       (bg-hover-alt  "#551f5a")
       (bg-hl-line    "#002435")
       (bg-region     "#25164a")
+      (bg-region-intense "#4f3f3f")
       (bg-paren      "#20577a")
       (bg-err        "#330d09") ; check with err
       (bg-warning    "#332600") ; check with warning
diff --git a/ef-day-theme.el b/ef-day-theme.el
index c27fd63..7faecb9 100644
--- a/ef-day-theme.el
+++ b/ef-day-theme.el
@@ -127,6 +127,7 @@ (eval-and-compile
       (bg-hover-alt  "#febcaf")
       (bg-hl-line    "#f9e2b2")
       (bg-region     "#f0d2df")
+      (bg-region-intense "#d2aaaf")
       (bg-paren      "#8fcfdf")
       (bg-err        "#ffddee") ; check with err
       (bg-warning    "#ffe0aa") ; check with warning
diff --git a/ef-deuteranopia-dark-theme.el b/ef-deuteranopia-dark-theme.el
index 084b11c..816019a 100644
--- a/ef-deuteranopia-dark-theme.el
+++ b/ef-deuteranopia-dark-theme.el
@@ -130,6 +130,7 @@ (eval-and-compile
       (bg-hover-alt  "#003a7f")
       (bg-hl-line    "#2e2e1b")
       (bg-region     "#202d3f")
+      (bg-region-intense "#50557f")
       (bg-paren      "#0f4f9a")
       (bg-err        "#232d09") ; check with err
       (bg-warning    "#332600") ; check with warning
diff --git a/ef-deuteranopia-light-theme.el b/ef-deuteranopia-light-theme.el
index 06d091a..fac544f 100644
--- a/ef-deuteranopia-light-theme.el
+++ b/ef-deuteranopia-light-theme.el
@@ -130,6 +130,7 @@ (eval-and-compile
       (bg-hover-alt  "#afafff")
       (bg-hl-line    "#f3e0d5")
       (bg-region     "#dadadf")
+      (bg-region-intense "#bbaacf")
       (bg-paren      "#8fc0cf")
       (bg-err        "#f0e0aa") ; check with err
       (bg-warning    "#ffe0aa") ; check with warning
diff --git a/ef-duo-dark-theme.el b/ef-duo-dark-theme.el
index bbb25be..148c668 100644
--- a/ef-duo-dark-theme.el
+++ b/ef-duo-dark-theme.el
@@ -130,6 +130,7 @@ (eval-and-compile
       (bg-hover-alt  "#265f4a")
       (bg-hl-line    "#301a4f")
       (bg-region     "#042a50")
+      (bg-region-intense "#4f423f")
       (bg-paren      "#2f608e")
       (bg-err        "#330d09") ; check with err
       (bg-warning    "#332600") ; check with warning
diff --git a/ef-duo-light-theme.el b/ef-duo-light-theme.el
index 423f803..ceaba92 100644
--- a/ef-duo-light-theme.el
+++ b/ef-duo-light-theme.el
@@ -131,6 +131,7 @@ (eval-and-compile
       (bg-hover-alt  "#aaeccf")
       (bg-hl-line    "#f9e8c0")
       (bg-region     "#caeafa")
+      (bg-region-intense "#d0a99f")
       (bg-paren      "#afbfef")
       (bg-err        "#ffdfe6") ; check with err
       (bg-warning    "#ffe5ba") ; check with warning
diff --git a/ef-light-theme.el b/ef-light-theme.el
index 8cf425a..8389dba 100644
--- a/ef-light-theme.el
+++ b/ef-light-theme.el
@@ -127,6 +127,7 @@ (eval-and-compile
       (bg-hover-alt  "#b4cfff")
       (bg-hl-line    "#e4efd8")
       (bg-region     "#bfefff")
+      (bg-region-intense "#c0c0ef")
       (bg-paren      "#efa09f")
       (bg-err        "#ffd5ea") ; check with err
       (bg-warning    "#ffeabb") ; check with warning
diff --git a/ef-night-theme.el b/ef-night-theme.el
index f54689c..f17330c 100644
--- a/ef-night-theme.el
+++ b/ef-night-theme.el
@@ -127,6 +127,7 @@ (eval-and-compile
       (bg-hover-alt  "#664f4a")
       (bg-hl-line    "#002255")
       (bg-region     "#222f40")
+      (bg-region-intense "#2f4b4f")
       (bg-paren      "#703350")
       (bg-err        "#331419") ; check with err
       (bg-warning    "#332613") ; check with warning
diff --git a/ef-spring-theme.el b/ef-spring-theme.el
index 63927a2..3c77ebc 100644
--- a/ef-spring-theme.el
+++ b/ef-spring-theme.el
@@ -127,6 +127,7 @@ (eval-and-compile
       (bg-hover-alt  "#feb5ff")
       (bg-hl-line    "#f9e0e5")
       (bg-region     "#d0e6ff")
+      (bg-region-intense "#cabaef")
       (bg-paren      "#7fddd0")
       (bg-err        "#ffe8e0") ; check with err
       (bg-warning    "#ffecba") ; check with warning
diff --git a/ef-summer-theme.el b/ef-summer-theme.el
index f8d6a7a..664046d 100644
--- a/ef-summer-theme.el
+++ b/ef-summer-theme.el
@@ -127,6 +127,7 @@ (eval-and-compile
       (bg-hover-alt  "#aaeccf")
       (bg-hl-line    "#ffd6e5")
       (bg-region     "#eecfff")
+      (bg-region-intense "#e0b29f")
       (bg-paren      "#9fc0ef")
       (bg-err        "#ffd0e6") ; check with err
       (bg-warning    "#ffe5ba") ; check with warning
diff --git a/ef-themes.el b/ef-themes.el
index 53ae1c6..8eb90dc 100644
--- a/ef-themes.el
+++ b/ef-themes.el
@@ -230,6 +230,18 @@ (defcustom ef-themes-variable-pitch-ui nil
   :type 'boolean
   :link '(info-link "(ef-themes) UI typeface"))

+(defcustom ef-themes-intense-region nil
+  "When non-nil, make the `region' more intense.
+Increase the overall coloration of the `region' background and
+make it override any foreground colors within its boundaries.
+
+If nil (the default), use a more subtle background for the region
+and refrain from overriding foregrounds."
+  :group 'ef-themes
+  :package-version '(ef-themes . "0.6.0")
+  :type 'boolean
+  :link '(info-link "(ef-themes) Intense region"))
+
 ;;; Helpers for user options

 (defun ef-themes--fixed-pitch ()
@@ -242,6 +254,12 @@ (defun ef-themes--variable-pitch-ui ()
   (when ef-themes-variable-pitch-ui
     (list :inherit 'variable-pitch)))

+(defun ef-themes--region (bg bg-intense fg-intense)
+  "Conditional application of `ef-themes-intense-region'."
+  (if ef-themes-intense-region
+      (list :background bg-intense :foreground fg-intense)
+    (list :background bg)))
+
 (defun ef-themes--key-cdr (key alist)
   "Get cdr of KEY in ALIST."
   (cdr (assoc key alist)))
@@ -535,7 +553,7 @@ ;;;;; absolute essentials
     `(cursor ((,c :background ,cursor)))
     `(default ((,c :background ,bg-main :foreground ,fg-main)))
     `(italic ((,c :slant italic)))
-    `(region ((,c :background ,bg-region)))
+    `(region ((,c ,@(ef-themes--region bg-region bg-region-intense fg-intense))))
     `(vertical-border ((,c :foreground ,border)))
 ;;;;; all other basic faces
     `(button ((,c :foreground ,link :underline ,border)))
diff --git a/ef-trio-dark-theme.el b/ef-trio-dark-theme.el
index 0a1b5f4..76df54e 100644
--- a/ef-trio-dark-theme.el
+++ b/ef-trio-dark-theme.el
@@ -127,6 +127,7 @@ (eval-and-compile
       (bg-hover-alt  "#551f5a")
       (bg-hl-line    "#34223f")
       (bg-region     "#16304f")
+      (bg-region-intense "#514438")
       (bg-paren      "#2f605e")
       (bg-err        "#300f06") ; check with err
       (bg-warning    "#332910") ; check with warning
diff --git a/ef-trio-light-theme.el b/ef-trio-light-theme.el
index 015188d..37fb6ae 100644
--- a/ef-trio-light-theme.el
+++ b/ef-trio-light-theme.el
@@ -127,6 +127,7 @@ (eval-and-compile
       (bg-hover-alt  "#b4cfff")
       (bg-hl-line    "#cfe6ff")
       (bg-region     "#eed0ff")
+      (bg-region-intense "#d2b6ff")
       (bg-paren      "#dfadaf")
       (bg-err        "#ffdfe6") ; check with err
       (bg-warning    "#ffe5bf") ; check with warning
diff --git a/ef-winter-theme.el b/ef-winter-theme.el
index e552471..b68275b 100644
--- a/ef-winter-theme.el
+++ b/ef-winter-theme.el
@@ -127,6 +127,7 @@ (eval-and-compile
       (bg-hover-alt  "#600f5a")
       (bg-hl-line    "#003045")
       (bg-region     "#342464")
+      (bg-region-intense "#54363f")
       (bg-paren      "#2f608e")
       (bg-err        "#330d06") ; check with err
       (bg-warning    "#332610") ; check with warning
--
2.37.3

Revising the dark backgrounds

As with the “intense” region, any change to the base backgrounds has far-reaching implications. The following patch is, again, a matter of making small tweaks to colours values whose effect is far greater than what meets the eye. I mean, what can possibly go wrong by changing the colour #1a1a1a to #232323, right? This marginal adjustment requires, among others, that we also adjust (i) the foregrounds in header lines, (ii) the background colours of added and removed lines in Magit focused diff hunks as well as the backgrounds of word-wise, aka “refined”, diff highlights, (iii) the foregrounds of inactive mode lines, (iv) the legibility of inactive tabs in tab-bar-mode, tab-line-mode, (v) the legibility of elements such as the #+begin_src line in Org buffers, and probably many others. A change to one background necessarily needs a change to all others.

This topic is discussed with Alan Schmitt who has one monitor that does not reproduce black properly: https://lists.sr.ht/~protesilaos/ef-themes/%3C87leqe2tji.fsf%40m4x.org%3E.

My suggestion is to calibrate hardware, if possible, such as by relying on those resources:

  1. http://www.lagom.nl/lcd-test/black.php
  2. http://www.lagom.nl/lcd-test/white.php

[ Yes, I once did spend 8 hours calibrating my monitor. It was crazy. ]

From 35f3ee6b9c5a8f15615be1ef75e58c8b27e3b633 Mon Sep 17 00:00:00 2001
Message-Id: <35f3ee6b9c5a8f15615be1ef75e58c8b27e3b633.1663904476.git.info@protesilaos.com>
From: Protesilaos Stavrou <info@protesilaos.com>
Date: Fri, 23 Sep 2022 06:07:12 +0300
Subject: [PATCH] Revise base backgrounds in all dark themes (DRAFT)

---
 contrast-ratios.org           | 420 +++++++++++++++++-----------------
 ef-autumn-theme.el            |   8 +-
 ef-dark-theme.el              |   8 +-
 ef-deuteranopia-dark-theme.el |   8 +-
 ef-duo-dark-theme.el          |   8 +-
 ef-night-theme.el             |   8 +-
 ef-trio-dark-theme.el         |   8 +-
 ef-winter-theme.el            |   8 +-
 8 files changed, 238 insertions(+), 238 deletions(-)

diff --git a/contrast-ratios.org b/contrast-ratios.org
index 0860495..bef9817 100644
--- a/contrast-ratios.org
+++ b/contrast-ratios.org
@@ -47,35 +47,35 @@ ** Base colours
 :CUSTOM_ID: h:85f29c2d-ae5c-4bb8-94bf-ac43543c8539
 :END:

-| Name           |         | #0f0e06 | #1f1b19 | #36322f | #14130a |
+| Name           |         | #0f0e06 | #262422 | #342e2a | #17150f |
 |----------------+---------+---------+---------+---------+---------|
-| fg-main        | #cfbcba |   10.64 |    9.40 |    6.99 |   10.25 |
-| fg-dim         | #887c8a |    4.88 |    4.31 |    3.20 |    4.70 |
-| fg-alt         | #70a89f |    7.18 |    6.34 |    4.71 |    6.91 |
-| red            | #ef656a |    6.21 |    5.48 |    4.07 |    5.98 |
-| red-warmer     | #f26f25 |    6.52 |    5.76 |    4.28 |    6.28 |
-| red-cooler     | #f07f7f |    7.41 |    6.55 |    4.86 |    7.14 |
-| red-faint      | #d08f72 |    7.23 |    6.39 |    4.75 |    6.97 |
-| green          | #2fa526 |    6.02 |    5.31 |    3.95 |    5.79 |
-| green-warmer   | #64aa0f |    6.72 |    5.94 |    4.41 |    6.47 |
-| green-cooler   | #00b066 |    6.82 |    6.02 |    4.47 |    6.57 |
-| green-faint    | #5f9f6f |    6.15 |    5.43 |    4.04 |    5.92 |
-| yellow         | #c48702 |    6.28 |    5.54 |    4.12 |    6.04 |
-| yellow-warmer  | #d0730f |    5.66 |    5.00 |    3.71 |    5.45 |
-| yellow-cooler  | #df8f6f |    7.64 |    6.75 |    5.02 |    7.36 |
-| yellow-faint   | #cf9f7f |    8.21 |    7.26 |    5.39 |    7.91 |
-| blue           | #379cf6 |    6.68 |    5.90 |    4.38 |    6.43 |
-| blue-warmer    | #6a88ff |    6.06 |    5.35 |    3.98 |    5.84 |
-| blue-cooler    | #029fff |    6.82 |    6.03 |    4.48 |    6.57 |
-| blue-faint     | #6a84af |    5.10 |    4.50 |    3.34 |    4.91 |
-| magenta        | #d570af |    6.23 |    5.50 |    4.09 |    6.00 |
-| magenta-warmer | #e580ea |    7.93 |    7.00 |    5.20 |    7.64 |
-| magenta-cooler | #af8aff |    7.28 |    6.43 |    4.78 |    7.01 |
-| magenta-faint  | #c590af |    7.35 |    6.49 |    4.83 |    7.08 |
-| cyan           | #4fb0cf |    7.78 |    6.87 |    5.10 |    7.49 |
-| cyan-warmer    | #6fafff |    8.52 |    7.53 |    5.59 |    8.21 |
-| cyan-cooler    | #3dbbb0 |    8.23 |    7.27 |    5.40 |    7.92 |
-| cyan-faint     | #82a0af |    7.00 |    6.18 |    4.59 |    6.74 |
+| fg-main        | #cfbcba |   10.64 |    8.51 |    7.36 |   10.04 |
+| fg-dim         | #887c8a |    4.88 |    3.90 |    3.37 |    4.60 |
+| fg-alt         | #70a89f |    7.18 |    5.74 |    4.96 |    6.77 |
+| red            | #ef656a |    6.21 |    4.96 |    4.29 |    5.86 |
+| red-warmer     | #f26f25 |    6.52 |    5.21 |    4.51 |    6.15 |
+| red-cooler     | #f07f7f |    7.41 |    5.93 |    5.12 |    6.99 |
+| red-faint      | #d08f72 |    7.23 |    5.78 |    5.00 |    6.83 |
+| green          | #2fa526 |    6.02 |    4.81 |    4.16 |    5.68 |
+| green-warmer   | #64aa0f |    6.72 |    5.38 |    4.65 |    6.34 |
+| green-cooler   | #00b066 |    6.82 |    5.45 |    4.71 |    6.43 |
+| green-faint    | #5f9f6f |    6.15 |    4.92 |    4.25 |    5.80 |
+| yellow         | #c48702 |    6.28 |    5.02 |    4.34 |    5.92 |
+| yellow-warmer  | #d0730f |    5.66 |    4.52 |    3.91 |    5.34 |
+| yellow-cooler  | #df8f6f |    7.64 |    6.11 |    5.28 |    7.21 |
+| yellow-faint   | #cf9f7f |    8.21 |    6.57 |    5.68 |    7.75 |
+| blue           | #379cf6 |    6.68 |    5.34 |    4.62 |    6.30 |
+| blue-warmer    | #6a88ff |    6.06 |    4.84 |    4.19 |    5.72 |
+| blue-cooler    | #029fff |    6.82 |    5.45 |    4.72 |    6.44 |
+| blue-faint     | #6a84af |    5.10 |    4.07 |    3.52 |    4.81 |
+| magenta        | #d570af |    6.23 |    4.98 |    4.31 |    5.88 |
+| magenta-warmer | #e580ea |    7.93 |    6.34 |    5.48 |    7.48 |
+| magenta-cooler | #af8aff |    7.28 |    5.82 |    5.03 |    6.87 |
+| magenta-faint  | #c590af |    7.35 |    5.88 |    5.08 |    6.94 |
+| cyan           | #4fb0cf |    7.78 |    6.22 |    5.38 |    7.34 |
+| cyan-warmer    | #6fafff |    8.52 |    6.81 |    5.89 |    8.04 |
+| cyan-cooler    | #3dbbb0 |    8.23 |    6.58 |    5.69 |    7.76 |
+| cyan-faint     | #82a0af |    7.00 |    5.59 |    4.84 |    6.60 |
 #+TBLFM: $3='(Λ $2 @1$3);%.2f :: $4='(Λ $2 @1$4);%.2f :: $5='(Λ $2 @1$5);%.2f :: $6='(Λ $2 @1$6);%.2f

 ** Special colours against the modeline
@@ -95,8 +95,8 @@ ** Distance and contrast between main backgrounds
 # bg-main / bg-dim, bg-main / bg-alt
 | #0f0e06 | distance | contrast |
 |---------+----------+----------|
-| #1f1b19 |     2279 |     1.13 |
-| #36322f |    13351 |     1.52 |
+| #262422 |     5361 |     1.25 |
+| #342e2a |    10815 |     1.45 |
 #+TBLFM: $2='(Δ $1 @1$1) :: $3='(Λ $1 @1$1);%.2f

 * ef-dark
@@ -109,35 +109,35 @@ ** Base colours
 :CUSTOM_ID: h:bdc5d5b7-4d1a-4e3d-8333-01a96164f4d8
 :END:

-| Name           |         | #000000 | #1a1a1a | #2b2b2b | #0c0c0c |
+| Name           |         | #000000 | #232323 | #2e2e2e | #101010 |
 |----------------+---------+---------+---------+---------+---------|
-| fg-main        | #d0d0d0 |   13.62 |   11.28 |    9.18 |   12.68 |
-| fg-dim         | #807f9f |    5.45 |    4.52 |    3.68 |    5.08 |
-| fg-alt         | #89afef |    9.44 |    7.83 |    6.37 |    8.80 |
-| red            | #ef6560 |    6.70 |    5.55 |    4.52 |    6.24 |
-| red-warmer     | #f47360 |    7.47 |    6.19 |    5.04 |    6.96 |
-| red-cooler     | #ff5a7a |    7.00 |    5.80 |    4.72 |    6.52 |
-| red-faint      | #d56f72 |    6.35 |    5.26 |    4.28 |    5.91 |
-| green          | #0faa26 |    6.80 |    5.63 |    4.58 |    6.33 |
-| green-warmer   | #6aad0f |    7.60 |    6.30 |    5.12 |    7.08 |
-| green-cooler   | #00a692 |    6.87 |    5.69 |    4.63 |    6.40 |
-| green-faint    | #61a06c |    6.75 |    5.60 |    4.55 |    6.29 |
-| yellow         | #c48032 |    6.48 |    5.37 |    4.37 |    6.04 |
-| yellow-warmer  | #d1843f |    7.08 |    5.87 |    4.78 |    6.60 |
-| yellow-cooler  | #df8f5a |    8.21 |    6.81 |    5.54 |    7.65 |
-| yellow-faint   | #cf9f8f |    9.01 |    7.47 |    6.07 |    8.39 |
-| blue           | #3f95f6 |    6.84 |    5.67 |    4.61 |    6.37 |
-| blue-warmer    | #6a9fff |    8.02 |    6.64 |    5.40 |    7.47 |
-| blue-cooler    | #029fff |    7.41 |    6.14 |    4.99 |    6.90 |
-| blue-faint     | #7a94df |    7.13 |    5.91 |    4.81 |    6.64 |
-| magenta        | #d369af |    6.41 |    5.31 |    4.32 |    5.97 |
-| magenta-warmer | #e580ea |    8.61 |    7.13 |    5.80 |    8.02 |
-| magenta-cooler | #af85ff |    7.62 |    6.32 |    5.14 |    7.10 |
-| magenta-faint  | #c57faf |    7.03 |    5.83 |    4.74 |    6.55 |
-| cyan           | #4fbaef |    9.60 |    7.96 |    6.47 |    8.94 |
-| cyan-warmer    | #6fafff |    9.25 |    7.67 |    6.24 |    8.62 |
-| cyan-cooler    | #1dbfcf |    9.41 |    7.80 |    6.34 |    8.76 |
-| cyan-faint     | #8aa0df |    8.17 |    6.77 |    5.51 |    7.61 |
+| fg-main        | #d0d0d0 |   13.62 |   10.19 |    8.80 |   12.34 |
+| fg-dim         | #807f9f |    5.45 |    4.08 |    3.53 |    4.94 |
+| fg-alt         | #89afef |    9.44 |    7.07 |    6.11 |    8.56 |
+| red            | #ef6560 |    6.70 |    5.01 |    4.33 |    6.07 |
+| red-warmer     | #f47360 |    7.47 |    5.59 |    4.83 |    6.77 |
+| red-cooler     | #ff5a7a |    7.00 |    5.24 |    4.52 |    6.34 |
+| red-faint      | #d56f72 |    6.35 |    4.75 |    4.10 |    5.75 |
+| green          | #0faa26 |    6.80 |    5.09 |    4.40 |    6.16 |
+| green-warmer   | #6aad0f |    7.60 |    5.69 |    4.91 |    6.88 |
+| green-cooler   | #00a692 |    6.87 |    5.14 |    4.44 |    6.22 |
+| green-faint    | #61a06c |    6.75 |    5.05 |    4.37 |    6.12 |
+| yellow         | #c48032 |    6.48 |    4.85 |    4.19 |    5.87 |
+| yellow-warmer  | #d1843f |    7.08 |    5.30 |    4.58 |    6.42 |
+| yellow-cooler  | #df8f5a |    8.21 |    6.15 |    5.31 |    7.44 |
+| yellow-faint   | #cf9f8f |    9.01 |    6.74 |    5.83 |    8.16 |
+| blue           | #3f95f6 |    6.84 |    5.12 |    4.42 |    6.20 |
+| blue-warmer    | #6a9fff |    8.02 |    6.00 |    5.18 |    7.26 |
+| blue-cooler    | #029fff |    7.41 |    5.54 |    4.79 |    6.71 |
+| blue-faint     | #7a94df |    7.13 |    5.34 |    4.61 |    6.46 |
+| magenta        | #d369af |    6.41 |    4.80 |    4.14 |    5.81 |
+| magenta-warmer | #e580ea |    8.61 |    6.44 |    5.57 |    7.80 |
+| magenta-cooler | #af85ff |    7.62 |    5.70 |    4.93 |    6.91 |
+| magenta-faint  | #c57faf |    7.03 |    5.26 |    4.55 |    6.37 |
+| cyan           | #4fbaef |    9.60 |    7.19 |    6.21 |    8.70 |
+| cyan-warmer    | #6fafff |    9.25 |    6.92 |    5.98 |    8.38 |
+| cyan-cooler    | #1dbfcf |    9.41 |    7.04 |    6.08 |    8.52 |
+| cyan-faint     | #8aa0df |    8.17 |    6.12 |    5.29 |    7.41 |
 #+TBLFM: $3='(Λ $2 @1$3);%.2f :: $4='(Λ $2 @1$4);%.2f :: $5='(Λ $2 @1$5);%.2f :: $6='(Λ $2 @1$6);%.2f

 ** Special colours against the modeline
@@ -157,8 +157,8 @@ ** Distance and contrast between main backgrounds
 # bg-main / bg-dim, bg-main / bg-alt
 | #000000 | distance | contrast |
 |---------+----------+----------|
-| #1a1a1a |     6131 |     1.21 |
-| #2b2b2b |    16771 |     1.48 |
+| #232323 |    11111 |     1.34 |
+| #2e2e2e |    19193 |     1.55 |
 #+TBLFM: $2='(Δ $1 @1$1) :: $3='(Λ $1 @1$1);%.2f

 * ef-day
@@ -237,35 +237,35 @@ ** Base colours
 yellows.  We just define the entire palette to make it work with the
 overall design of the project.

-| Name           |         | #000a1f | #0f1c2d | #19263a | #071225 |
+| Name           |         | #000a1f | #1a2332 | #2c2c3f | #101625 |
 |----------------+---------+---------+---------+---------+---------|
-| fg-main        | #ddddee |   14.72 |   12.78 |   11.34 |   13.95 |
-| fg-dim         | #7f8797 |    5.47 |    4.75 |    4.21 |    5.18 |
-| fg-alt         | #90afef |    9.00 |    7.81 |    6.93 |    8.52 |
-| red            | #cf8560 |    6.75 |    5.86 |    5.20 |    6.40 |
-| red-warmer     | #e47360 |    6.51 |    5.65 |    5.02 |    6.17 |
-| red-cooler     | #cf7a7a |    6.32 |    5.49 |    4.87 |    5.99 |
-| red-faint      | #b57f82 |    5.95 |    5.16 |    4.58 |    5.63 |
-| green          | #3faa26 |    6.57 |    5.71 |    5.07 |    6.23 |
-| green-warmer   | #7aad0f |    7.35 |    6.38 |    5.66 |    6.96 |
-| green-cooler   | #3fa672 |    6.50 |    5.64 |    5.01 |    6.16 |
-| green-faint    | #61a06c |    6.35 |    5.52 |    4.89 |    6.02 |
-| yellow         | #aa9f32 |    7.26 |    6.30 |    5.59 |    6.87 |
-| yellow-warmer  | #cfaf00 |    9.20 |    7.99 |    7.09 |    8.72 |
-| yellow-cooler  | #bfaf7a |    9.06 |    7.86 |    6.98 |    8.58 |
-| yellow-faint   | #af9a6a |    7.20 |    6.25 |    5.55 |    6.82 |
-| blue           | #3f90f0 |    6.07 |    5.28 |    4.68 |    5.76 |
-| blue-warmer    | #6a9fff |    7.54 |    6.55 |    5.81 |    7.14 |
-| blue-cooler    | #009fff |    6.96 |    6.05 |    5.37 |    6.60 |
-| blue-faint     | #7a94df |    6.71 |    5.82 |    5.17 |    6.35 |
-| magenta        | #b379bf |    6.02 |    5.23 |    4.64 |    5.71 |
-| magenta-warmer | #af80ea |    6.68 |    5.80 |    5.15 |    6.33 |
-| magenta-cooler | #9f95ff |    7.73 |    6.71 |    5.96 |    7.32 |
-| magenta-faint  | #c59fcf |    8.69 |    7.54 |    6.69 |    8.23 |
-| cyan           | #5faaef |    7.98 |    6.93 |    6.15 |    7.56 |
-| cyan-warmer    | #7fafff |    8.91 |    7.74 |    6.87 |    8.45 |
-| cyan-cooler    | #0db0ff |    8.16 |    7.08 |    6.28 |    7.73 |
-| cyan-faint     | #8aa0df |    7.69 |    6.68 |    5.92 |    7.28 |
+| fg-main        | #ddddee |   14.72 |   11.76 |   10.17 |   13.45 |
+| fg-dim         | #7f8797 |    5.47 |    4.37 |    3.78 |    5.00 |
+| fg-alt         | #90afef |    9.00 |    7.19 |    6.21 |    8.22 |
+| red            | #cf8560 |    6.75 |    5.39 |    4.66 |    6.17 |
+| red-warmer     | #e47360 |    6.51 |    5.20 |    4.50 |    5.95 |
+| red-cooler     | #cf7a7a |    6.32 |    5.05 |    4.36 |    5.77 |
+| red-faint      | #b57f82 |    5.95 |    4.75 |    4.11 |    5.43 |
+| green          | #3faa26 |    6.57 |    5.25 |    4.54 |    6.01 |
+| green-warmer   | #7aad0f |    7.35 |    5.87 |    5.08 |    6.71 |
+| green-cooler   | #3fa672 |    6.50 |    5.19 |    4.49 |    5.94 |
+| green-faint    | #61a06c |    6.35 |    5.08 |    4.39 |    5.80 |
+| yellow         | #aa9f32 |    7.26 |    5.80 |    5.01 |    6.63 |
+| yellow-warmer  | #cfaf00 |    9.20 |    7.36 |    6.36 |    8.41 |
+| yellow-cooler  | #bfaf7a |    9.06 |    7.24 |    6.26 |    8.28 |
+| yellow-faint   | #af9a6a |    7.20 |    5.75 |    4.97 |    6.58 |
+| blue           | #3f90f0 |    6.07 |    4.85 |    4.20 |    5.55 |
+| blue-warmer    | #6a9fff |    7.54 |    6.03 |    5.21 |    6.89 |
+| blue-cooler    | #009fff |    6.96 |    5.56 |    4.81 |    6.36 |
+| blue-faint     | #7a94df |    6.71 |    5.36 |    4.63 |    6.13 |
+| magenta        | #b379bf |    6.02 |    4.81 |    4.16 |    5.50 |
+| magenta-warmer | #af80ea |    6.68 |    5.34 |    4.61 |    6.10 |
+| magenta-cooler | #9f95ff |    7.73 |    6.18 |    5.34 |    7.06 |
+| magenta-faint  | #c59fcf |    8.69 |    6.94 |    6.00 |    7.94 |
+| cyan           | #5faaef |    7.98 |    6.38 |    5.51 |    7.29 |
+| cyan-warmer    | #7fafff |    8.91 |    7.12 |    6.16 |    8.15 |
+| cyan-cooler    | #0db0ff |    8.16 |    6.52 |    5.63 |    7.45 |
+| cyan-faint     | #8aa0df |    7.69 |    6.14 |    5.31 |    7.03 |
 #+TBLFM: $3='(Λ $2 @1$3);%.2f :: $4='(Λ $2 @1$4);%.2f :: $5='(Λ $2 @1$5);%.2f :: $6='(Λ $2 @1$6);%.2f

 ** Special colours against the modeline
@@ -285,8 +285,8 @@ ** Distance and contrast between main backgrounds
 # bg-main / bg-dim, bg-main / bg-alt
 | #000a1f | distance | contrast |
 |---------+----------+----------|
-| #0f1c2d |     2353 |     1.15 |
-| #19263a |     6619 |     1.30 |
+| #1a2332 |     4989 |     1.25 |
+| #2c2c3f |    11737 |     1.45 |
 #+TBLFM: $2='(Δ $1 @1$1) :: $3='(Λ $1 @1$1);%.2f

 * ef-deuteranopia-light
@@ -372,35 +372,35 @@ ** Base colours
 blue/cyan and yellow hues.  We just define the entire palette to make it
 work with the overall design of the project.

-| Name           |         | #070019 | #1c1926 | #262230 | #140e1c |
+| Name           |         | #070019 | #211c2b | #2c2836 | #181322 |
 |----------------+---------+---------+---------+---------+---------|
-| fg-main        | #d0d0d0 |   13.31 |   11.19 |   10.06 |   12.27 |
-| fg-dim         | #857f8f |    5.31 |    4.46 |    4.01 |    4.89 |
-| fg-alt         | #89afef |    9.23 |    7.76 |    6.97 |    8.51 |
-| red            | #ef656a |    6.59 |    5.54 |    4.98 |    6.08 |
-| red-warmer     | #f47360 |    7.30 |    6.14 |    5.52 |    6.73 |
-| red-cooler     | #ef798f |    7.63 |    6.41 |    5.76 |    7.03 |
-| red-faint      | #d08f72 |    7.68 |    6.46 |    5.80 |    7.08 |
-| green          | #1fa526 |    6.32 |    5.32 |    4.78 |    5.83 |
-| green-warmer   | #50a22f |    6.40 |    5.38 |    4.84 |    5.90 |
-| green-cooler   | #00b982 |    8.08 |    6.79 |    6.10 |    7.45 |
-| green-faint    | #61a06c |    6.60 |    5.55 |    4.99 |    6.09 |
-| yellow         | #c48702 |    6.66 |    5.60 |    5.03 |    6.14 |
-| yellow-warmer  | #d0730f |    6.00 |    5.05 |    4.54 |    5.54 |
-| yellow-cooler  | #df805f |    7.22 |    6.08 |    5.46 |    6.66 |
-| yellow-faint   | #9f8f6a |    6.46 |    5.44 |    4.88 |    5.96 |
-| blue           | #379cf6 |    7.09 |    5.96 |    5.35 |    6.53 |
-| blue-warmer    | #6f80ff |    6.07 |    5.10 |    4.58 |    5.60 |
-| blue-cooler    | #029fff |    7.24 |    6.09 |    5.47 |    6.68 |
-| blue-faint     | #8a9fdf |    7.92 |    6.66 |    5.99 |    7.31 |
-| magenta        | #d369af |    6.26 |    5.27 |    4.73 |    5.78 |
-| magenta-warmer | #e580ea |    8.41 |    7.08 |    6.36 |    7.76 |
-| magenta-cooler | #af85ff |    7.45 |    6.27 |    5.63 |    6.87 |
-| magenta-faint  | #c57faf |    6.87 |    5.78 |    5.19 |    6.34 |
-| cyan           | #5faaef |    8.29 |    6.97 |    6.27 |    7.65 |
-| cyan-warmer    | #7fafff |    9.26 |    7.79 |    7.00 |    8.54 |
-| cyan-cooler    | #0dafdf |    8.03 |    6.75 |    6.07 |    7.41 |
-| cyan-faint     | #8aa0df |    7.99 |    6.72 |    6.04 |    7.37 |
+| fg-main        | #d0d0d0 |   13.31 |   10.76 |    9.30 |   11.79 |
+| fg-dim         | #857f8f |    5.31 |    4.29 |    3.71 |    4.70 |
+| fg-alt         | #89afef |    9.23 |    7.46 |    6.45 |    8.17 |
+| red            | #ef656a |    6.59 |    5.33 |    4.60 |    5.83 |
+| red-warmer     | #f47360 |    7.30 |    5.90 |    5.10 |    6.47 |
+| red-cooler     | #ef798f |    7.63 |    6.16 |    5.33 |    6.75 |
+| red-faint      | #d08f72 |    7.68 |    6.21 |    5.37 |    6.80 |
+| green          | #1fa526 |    6.32 |    5.11 |    4.42 |    5.60 |
+| green-warmer   | #50a22f |    6.40 |    5.18 |    4.47 |    5.67 |
+| green-cooler   | #00b982 |    8.08 |    6.53 |    5.64 |    7.15 |
+| green-faint    | #61a06c |    6.60 |    5.34 |    4.61 |    5.85 |
+| yellow         | #c48702 |    6.66 |    5.38 |    4.65 |    5.90 |
+| yellow-warmer  | #d0730f |    6.00 |    4.85 |    4.20 |    5.32 |
+| yellow-cooler  | #df805f |    7.22 |    5.84 |    5.05 |    6.40 |
+| yellow-faint   | #9f8f6a |    6.46 |    5.22 |    4.52 |    5.72 |
+| blue           | #379cf6 |    7.09 |    5.73 |    4.95 |    6.28 |
+| blue-warmer    | #6f80ff |    6.07 |    4.90 |    4.24 |    5.37 |
+| blue-cooler    | #029fff |    7.24 |    5.85 |    5.06 |    6.41 |
+| blue-faint     | #8a9fdf |    7.92 |    6.40 |    5.54 |    7.02 |
+| magenta        | #d369af |    6.26 |    5.06 |    4.38 |    5.55 |
+| magenta-warmer | #e580ea |    8.41 |    6.80 |    5.88 |    7.45 |
+| magenta-cooler | #af85ff |    7.45 |    6.02 |    5.21 |    6.60 |
+| magenta-faint  | #c57faf |    6.87 |    5.55 |    4.80 |    6.09 |
+| cyan           | #5faaef |    8.29 |    6.70 |    5.79 |    7.34 |
+| cyan-warmer    | #7fafff |    9.26 |    7.49 |    6.47 |    8.21 |
+| cyan-cooler    | #0dafdf |    8.03 |    6.49 |    5.61 |    7.11 |
+| cyan-faint     | #8aa0df |    7.99 |    6.46 |    5.58 |    7.08 |
 #+TBLFM: $3='(Λ $2 @1$3);%.2f :: $4='(Λ $2 @1$4);%.2f :: $5='(Λ $2 @1$5);%.2f :: $6='(Λ $2 @1$6);%.2f

 ** Special colours against the modeline
@@ -420,8 +420,8 @@ ** Distance and contrast between main backgrounds
 # bg-main / bg-dim, bg-main / bg-alt
 | #070019 | distance | contrast |
 |---------+----------+----------|
-| #1c1926 |     3938 |     1.19 |
-| #262230 |     8235 |     1.32 |
+| #211c2b |     5530 |     1.24 |
+| #2c2836 |    11805 |     1.43 |
 #+TBLFM: $2='(Δ $1 @1$1) :: $3='(Λ $1 @1$1);%.2f

 * ef-duo-light
@@ -562,35 +562,35 @@ ** Base colours
 :CUSTOM_ID: h:2e0d0312-3984-48d8-9adc-1d132c1ab651
 :END:

-| Name           |         | #000e17 | #0f1b29 | #1a2a2f | #0f121f |
+| Name           |         | #000e17 | #18242f | #262e36 | #121522 |
 |----------------+---------+---------+---------+---------+---------|
-| fg-main        | #afbcbf |   10.02 |    8.91 |    7.60 |    9.56 |
-| fg-dim         | #70819f |    4.96 |    4.41 |    3.76 |    4.73 |
-| fg-alt         | #b0a0a0 |    7.80 |    6.93 |    5.92 |    7.44 |
-| red            | #ef656a |    6.27 |    5.57 |    4.76 |    5.98 |
-| red-warmer     | #f47360 |    6.95 |    6.18 |    5.27 |    6.63 |
-| red-cooler     | #ef798f |    7.26 |    6.45 |    5.51 |    6.92 |
-| red-faint      | #d56f72 |    5.90 |    5.25 |    4.48 |    5.63 |
-| green          | #1fa526 |    6.02 |    5.35 |    4.57 |    5.74 |
-| green-warmer   | #50a22f |    6.09 |    5.42 |    4.63 |    5.81 |
-| green-cooler   | #00b672 |    7.38 |    6.56 |    5.60 |    7.04 |
-| green-faint    | #61a06c |    6.28 |    5.59 |    4.77 |    5.99 |
-| yellow         | #c48502 |    6.23 |    5.54 |    4.73 |    5.95 |
-| yellow-warmer  | #e6832f |    7.12 |    6.33 |    5.40 |    6.79 |
-| yellow-cooler  | #df8f6f |    7.72 |    6.86 |    5.86 |    7.36 |
-| yellow-faint   | #cf9f7f |    8.30 |    7.38 |    6.30 |    7.92 |
-| blue           | #379cf6 |    6.74 |    6.00 |    5.12 |    6.43 |
-| blue-warmer    | #6a88ff |    6.12 |    5.44 |    4.65 |    5.84 |
-| blue-cooler    | #029fff |    6.89 |    6.13 |    5.23 |    6.57 |
-| blue-faint     | #7a94df |    6.63 |    5.90 |    5.03 |    6.33 |
-| magenta        | #d570af |    6.29 |    5.60 |    4.78 |    6.01 |
-| magenta-warmer | #e580ea |    8.01 |    7.12 |    6.08 |    7.64 |
-| magenta-cooler | #af8aff |    7.35 |    6.54 |    5.58 |    7.01 |
-| magenta-faint  | #c59faf |    8.33 |    7.40 |    6.32 |    7.95 |
-| cyan           | #4fb0cf |    7.85 |    6.98 |    5.96 |    7.49 |
-| cyan-warmer    | #6fafff |    8.60 |    7.65 |    6.53 |    8.21 |
-| cyan-cooler    | #3dc0b0 |    8.71 |    7.75 |    6.61 |    8.31 |
-| cyan-faint     | #92b4df |    9.13 |    8.12 |    6.93 |    8.71 |
+| fg-main        | #afbcbf |   10.02 |    8.08 |    7.05 |    9.31 |
+| fg-dim         | #70819f |    4.96 |    4.00 |    3.49 |    4.61 |
+| fg-alt         | #b0a0a0 |    7.80 |    6.29 |    5.49 |    7.25 |
+| red            | #ef656a |    6.27 |    5.06 |    4.42 |    5.83 |
+| red-warmer     | #f47360 |    6.95 |    5.61 |    4.89 |    6.46 |
+| red-cooler     | #ef798f |    7.26 |    5.86 |    5.11 |    6.75 |
+| red-faint      | #d56f72 |    5.90 |    4.76 |    4.16 |    5.49 |
+| green          | #1fa526 |    6.02 |    4.86 |    4.24 |    5.60 |
+| green-warmer   | #50a22f |    6.09 |    4.92 |    4.29 |    5.67 |
+| green-cooler   | #00b672 |    7.38 |    5.96 |    5.20 |    6.86 |
+| green-faint    | #61a06c |    6.28 |    5.07 |    4.42 |    5.84 |
+| yellow         | #c48502 |    6.23 |    5.03 |    4.39 |    5.80 |
+| yellow-warmer  | #e6832f |    7.12 |    5.74 |    5.01 |    6.62 |
+| yellow-cooler  | #df8f6f |    7.72 |    6.23 |    5.43 |    7.18 |
+| yellow-faint   | #cf9f7f |    8.30 |    6.69 |    5.84 |    7.71 |
+| blue           | #379cf6 |    6.74 |    5.44 |    4.75 |    6.27 |
+| blue-warmer    | #6a88ff |    6.12 |    4.94 |    4.31 |    5.69 |
+| blue-cooler    | #029fff |    6.89 |    5.56 |    4.85 |    6.41 |
+| blue-faint     | #7a94df |    6.63 |    5.35 |    4.67 |    6.17 |
+| magenta        | #d570af |    6.29 |    5.08 |    4.43 |    5.85 |
+| magenta-warmer | #e580ea |    8.01 |    6.46 |    5.64 |    7.45 |
+| magenta-cooler | #af8aff |    7.35 |    5.93 |    5.18 |    6.84 |
+| magenta-faint  | #c59faf |    8.33 |    6.72 |    5.86 |    7.74 |
+| cyan           | #4fb0cf |    7.85 |    6.34 |    5.53 |    7.30 |
+| cyan-warmer    | #6fafff |    8.60 |    6.94 |    6.06 |    8.00 |
+| cyan-cooler    | #3dc0b0 |    8.71 |    7.03 |    6.14 |    8.10 |
+| cyan-faint     | #92b4df |    9.13 |    7.37 |    6.43 |    8.49 |
 #+TBLFM: $3='(Λ $2 @1$3);%.2f :: $4='(Λ $2 @1$4);%.2f :: $5='(Λ $2 @1$5);%.2f :: $6='(Λ $2 @1$6);%.2f

 ** Special colours against the modeline
@@ -610,8 +610,8 @@ ** Distance and contrast between main backgrounds
 # bg-main / bg-dim, bg-main / bg-alt
 | #000e17 | distance | contrast |
 |---------+----------+----------|
-| #0f1b29 |     2111 |     1.12 |
-| #1a2a2f |     6269 |     1.32 |
+| #18242f |     4853 |     1.24 |
+| #262e36 |     9980 |     1.42 |
 #+TBLFM: $2='(Δ $1 @1$1) :: $3='(Λ $1 @1$1);%.2f

 * ef-spring
@@ -748,35 +748,35 @@ ** Base colours
 :CUSTOM_ID: h:db8a51f5-a28f-422c-a046-b44bc0fdfb24
 :END:

-| Name           |         | #160f0f | #251a23 | #33252d | #1c1416 |
+| Name           |         | #160f0f | #2b2328 | #3a2b35 | #1f171a |
 |----------------+---------+---------+---------+---------+---------|
-| fg-main        | #d8cfd5 |   12.44 |   11.04 |    9.57 |   11.89 |
-| fg-dim         | #908890 |    5.51 |    4.89 |    4.23 |    5.26 |
-| fg-alt         | #afdacf |   12.40 |   11.00 |    9.53 |   11.85 |
-| red            | #f48359 |    7.43 |    6.59 |    5.71 |    7.09 |
-| red-warmer     | #ff7560 |    7.18 |    6.37 |    5.52 |    6.86 |
-| red-cooler     | #ff85aa |    8.28 |    7.35 |    6.37 |    7.91 |
-| red-faint      | #e47f72 |    6.83 |    6.06 |    5.25 |    6.53 |
-| green          | #60b444 |    7.31 |    6.48 |    5.62 |    6.98 |
-| green-warmer   | #a0c27f |    9.48 |    8.41 |    7.29 |    9.06 |
-| green-cooler   | #60bf88 |    8.39 |    7.44 |    6.45 |    8.01 |
-| green-faint    | #61a06c |    6.09 |    5.40 |    4.68 |    5.82 |
-| yellow         | #d4a052 |    8.07 |    7.16 |    6.20 |    7.71 |
-| yellow-warmer  | #ef926f |    8.12 |    7.21 |    6.24 |    7.76 |
-| yellow-cooler  | #ef9680 |    8.42 |    7.47 |    6.47 |    8.05 |
-| yellow-faint   | #c7a07f |    7.90 |    7.01 |    6.07 |    7.55 |
-| blue           | #7fa5f6 |    7.77 |    6.89 |    5.97 |    7.42 |
-| blue-warmer    | #8895ff |    7.02 |    6.23 |    5.40 |    6.71 |
-| blue-cooler    | #72afff |    8.38 |    7.43 |    6.44 |    8.00 |
-| blue-faint     | #7a94df |    6.43 |    5.70 |    4.94 |    6.14 |
-| magenta        | #d37faf |    6.69 |    5.94 |    5.14 |    6.39 |
-| magenta-warmer | #e772df |    7.09 |    6.29 |    5.45 |    6.78 |
-| magenta-cooler | #a698ef |    7.54 |    6.68 |    5.79 |    7.20 |
-| magenta-faint  | #c9addf |    9.49 |    8.42 |    7.29 |    9.07 |
-| cyan           | #8fbaff |    9.59 |    8.50 |    7.37 |    9.16 |
-| cyan-warmer    | #9ac2ff |   10.40 |    9.22 |    7.99 |    9.93 |
-| cyan-cooler    | #8fcfdf |   10.96 |    9.72 |    8.42 |   10.47 |
-| cyan-faint     | #8ac0ef |    9.80 |    8.69 |    7.53 |    9.36 |
+| fg-main        | #d8cfd5 |   12.44 |   10.05 |    8.76 |   11.55 |
+| fg-dim         | #908890 |    5.51 |    4.45 |    3.87 |    5.11 |
+| fg-alt         | #afdacf |   12.40 |   10.01 |    8.72 |   11.50 |
+| red            | #f48359 |    7.43 |    6.00 |    5.22 |    6.89 |
+| red-warmer     | #ff7560 |    7.18 |    5.80 |    5.05 |    6.66 |
+| red-cooler     | #ff85aa |    8.28 |    6.69 |    5.83 |    7.68 |
+| red-faint      | #e47f72 |    6.83 |    5.52 |    4.81 |    6.34 |
+| green          | #60b444 |    7.31 |    5.90 |    5.14 |    6.78 |
+| green-warmer   | #a0c27f |    9.48 |    7.66 |    6.67 |    8.80 |
+| green-cooler   | #60bf88 |    8.39 |    6.77 |    5.90 |    7.78 |
+| green-faint    | #61a06c |    6.09 |    4.92 |    4.28 |    5.65 |
+| yellow         | #d4a052 |    8.07 |    6.51 |    5.68 |    7.48 |
+| yellow-warmer  | #ef926f |    8.12 |    6.56 |    5.71 |    7.54 |
+| yellow-cooler  | #ef9680 |    8.42 |    6.80 |    5.93 |    7.81 |
+| yellow-faint   | #c7a07f |    7.90 |    6.38 |    5.56 |    7.33 |
+| blue           | #7fa5f6 |    7.77 |    6.27 |    5.46 |    7.20 |
+| blue-warmer    | #8895ff |    7.02 |    5.67 |    4.94 |    6.51 |
+| blue-cooler    | #72afff |    8.38 |    6.76 |    5.89 |    7.77 |
+| blue-faint     | #7a94df |    6.43 |    5.19 |    4.52 |    5.96 |
+| magenta        | #d37faf |    6.69 |    5.40 |    4.71 |    6.21 |
+| magenta-warmer | #e772df |    7.09 |    5.73 |    4.99 |    6.58 |
+| magenta-cooler | #a698ef |    7.54 |    6.08 |    5.30 |    6.99 |
+| magenta-faint  | #c9addf |    9.49 |    7.66 |    6.68 |    8.80 |
+| cyan           | #8fbaff |    9.59 |    7.74 |    6.74 |    8.89 |
+| cyan-warmer    | #9ac2ff |   10.40 |    8.40 |    7.31 |    9.65 |
+| cyan-cooler    | #8fcfdf |   10.96 |    8.85 |    7.71 |   10.17 |
+| cyan-faint     | #8ac0ef |    9.80 |    7.91 |    6.89 |    9.09 |
 #+TBLFM: $3='(Λ $2 @1$3);%.2f :: $4='(Λ $2 @1$4);%.2f :: $5='(Λ $2 @1$5);%.2f :: $6='(Λ $2 @1$6);%.2f

 ** Special colours against the modeline
@@ -796,8 +796,8 @@ ** Distance and contrast between main backgrounds
 # bg-main / bg-dim, bg-main / bg-alt
 | #160f0f | distance | contrast |
 |---------+----------+----------|
-| #251a23 |     2130 |     1.13 |
-| #33252d |     6358 |     1.30 |
+| #2b2328 |     4367 |     1.24 |
+| #3a2b35 |    10115 |     1.42 |
 #+TBLFM: $2='(Δ $1 @1$1) :: $3='(Λ $1 @1$1);%.2f

 * ef-trio-light
@@ -872,35 +872,35 @@ ** Base colours
 :CUSTOM_ID: h:76e0b621-7872-4597-8bdc-6c007e43aff5
 :END:

-| Name           |         | #0f0b15 | #161926 | #202234 | #14121a |
+| Name           |         | #0f0b15 | #1d2030 | #292b3d | #18151d |
 |----------------+---------+---------+---------+---------+---------|
-| fg-main        | #b8c6d5 |   11.19 |   10.05 |    9.01 |   10.68 |
-| fg-dim         | #807c9f |    4.91 |    4.41 |    3.96 |    4.69 |
-| fg-alt         | #bf8f8f |    6.99 |    6.28 |    5.63 |    6.67 |
-| red            | #f47359 |    6.90 |    6.20 |    5.56 |    6.58 |
-| red-warmer     | #ef6560 |    6.21 |    5.58 |    5.00 |    5.92 |
-| red-cooler     | #ff6a7a |    7.04 |    6.32 |    5.67 |    6.71 |
-| red-faint      | #d56f72 |    5.88 |    5.28 |    4.74 |    5.61 |
-| green          | #29a444 |    6.01 |    5.40 |    4.84 |    5.74 |
-| green-warmer   | #6aad0f |    7.04 |    6.32 |    5.67 |    6.72 |
-| green-cooler   | #00a392 |    6.17 |    5.54 |    4.97 |    5.88 |
-| green-faint    | #61a06c |    6.26 |    5.62 |    5.04 |    5.97 |
-| yellow         | #c48052 |    6.08 |    5.46 |    4.89 |    5.80 |
-| yellow-warmer  | #d1803f |    6.37 |    5.72 |    5.13 |    6.07 |
-| yellow-cooler  | #df8a88 |    7.53 |    6.77 |    6.07 |    7.19 |
-| yellow-faint   | #c0a38a |    8.20 |    7.37 |    6.60 |    7.82 |
-| blue           | #3f95f6 |    6.34 |    5.70 |    5.11 |    6.05 |
-| blue-warmer    | #6a9fff |    7.43 |    6.67 |    5.98 |    7.09 |
-| blue-cooler    | #029fff |    6.86 |    6.17 |    5.53 |    6.55 |
-| blue-faint     | #7a94df |    6.61 |    5.94 |    5.32 |    6.30 |
-| magenta        | #d369af |    5.94 |    5.34 |    4.78 |    5.67 |
-| magenta-warmer | #e580e0 |    7.87 |    7.07 |    6.34 |    7.51 |
-| magenta-cooler | #af85ea |    6.83 |    6.13 |    5.50 |    6.51 |
-| magenta-faint  | #c57faf |    6.51 |    5.85 |    5.25 |    6.21 |
-| cyan           | #4fbaef |    8.90 |    7.99 |    7.17 |    8.49 |
-| cyan-warmer    | #6fafdf |    8.22 |    7.39 |    6.62 |    7.84 |
-| cyan-cooler    | #35afbf |    7.45 |    6.69 |    6.00 |    7.10 |
-| cyan-faint     | #8aa0df |    7.58 |    6.81 |    6.10 |    7.23 |
+| fg-main        | #b8c6d5 |   11.19 |    9.28 |    8.01 |   10.38 |
+| fg-dim         | #807c9f |    4.91 |    4.07 |    3.52 |    4.56 |
+| fg-alt         | #bf8f8f |    6.99 |    5.79 |    5.00 |    6.48 |
+| red            | #f47359 |    6.90 |    5.72 |    4.94 |    6.40 |
+| red-warmer     | #ef6560 |    6.21 |    5.15 |    4.45 |    5.76 |
+| red-cooler     | #ff6a7a |    7.04 |    5.84 |    5.04 |    6.53 |
+| red-faint      | #d56f72 |    5.88 |    4.88 |    4.21 |    5.45 |
+| green          | #29a444 |    6.01 |    4.98 |    4.30 |    5.57 |
+| green-warmer   | #6aad0f |    7.04 |    5.84 |    5.04 |    6.53 |
+| green-cooler   | #00a392 |    6.17 |    5.11 |    4.41 |    5.72 |
+| green-faint    | #61a06c |    6.26 |    5.19 |    4.48 |    5.80 |
+| yellow         | #c48052 |    6.08 |    5.04 |    4.35 |    5.63 |
+| yellow-warmer  | #d1803f |    6.37 |    5.28 |    4.56 |    5.90 |
+| yellow-cooler  | #df8a88 |    7.53 |    6.25 |    5.39 |    6.98 |
+| yellow-faint   | #c0a38a |    8.20 |    6.80 |    5.87 |    7.60 |
+| blue           | #3f95f6 |    6.34 |    5.26 |    4.54 |    5.88 |
+| blue-warmer    | #6a9fff |    7.43 |    6.16 |    5.32 |    6.89 |
+| blue-cooler    | #029fff |    6.86 |    5.69 |    4.91 |    6.36 |
+| blue-faint     | #7a94df |    6.61 |    5.48 |    4.73 |    6.13 |
+| magenta        | #d369af |    5.94 |    4.92 |    4.25 |    5.51 |
+| magenta-warmer | #e580e0 |    7.87 |    6.53 |    5.64 |    7.30 |
+| magenta-cooler | #af85ea |    6.83 |    5.66 |    4.89 |    6.33 |
+| magenta-faint  | #c57faf |    6.51 |    5.40 |    4.66 |    6.04 |
+| cyan           | #4fbaef |    8.90 |    7.38 |    6.37 |    8.25 |
+| cyan-warmer    | #6fafdf |    8.22 |    6.82 |    5.89 |    7.62 |
+| cyan-cooler    | #35afbf |    7.45 |    6.17 |    5.33 |    6.90 |
+| cyan-faint     | #8aa0df |    7.58 |    6.28 |    5.42 |    7.02 |
 #+TBLFM: $3='(Λ $2 @1$3);%.2f :: $4='(Λ $2 @1$4);%.2f :: $5='(Λ $2 @1$5);%.2f :: $6='(Λ $2 @1$6);%.2f

 ** Special colours against the modeline
@@ -920,6 +920,6 @@ ** Distance and contrast between main backgrounds
 # bg-main / bg-dim, bg-main / bg-alt
 | #0f0b15 | distance | contrast |
 |---------+----------+----------|
-| #161926 |     1745 |     1.11 |
-| #202234 |     5558 |     1.24 |
+| #1d2030 |     4330 |     1.21 |
+| #292b3d |    10225 |     1.40 |
 #+TBLFM: $2='(Δ $1 @1$1) :: $3='(Λ $1 @1$1);%.2f
diff --git a/ef-autumn-theme.el b/ef-autumn-theme.el
index e4b6e02..e82e363 100644
--- a/ef-autumn-theme.el
+++ b/ef-autumn-theme.el
@@ -43,13 +43,13 @@ (eval-and-compile
     '(;; Basic tones
       (bg-main     "#0f0e06")
       (fg-main     "#cfbcba")
-      (bg-dim      "#1f1b19")
+      (bg-dim      "#262422")
       (fg-dim      "#887c8a")
-      (bg-alt      "#36322f")
+      (bg-alt      "#342e2a")
       (fg-alt      "#70a89f")

-      (bg-active   "#46423f")
-      (bg-inactive "#14130a")
+      (bg-active   "#443e3a")
+      (bg-inactive "#17150f")

       ;; Basic hues for foreground values
       (red             "#ef656a")
diff --git a/ef-dark-theme.el b/ef-dark-theme.el
index 15c85a8..043553b 100644
--- a/ef-dark-theme.el
+++ b/ef-dark-theme.el
@@ -43,13 +43,13 @@ (eval-and-compile
     '(;; Basic tones
       (bg-main     "#000000")
       (fg-main     "#d0d0d0")
-      (bg-dim      "#1a1a1a")
+      (bg-dim      "#232323")
       (fg-dim      "#857f8f")
-      (bg-alt      "#2b2b2b")
+      (bg-alt      "#2e2e2e")
       (fg-alt      "#89afef")

-      (bg-active   "#3b3b3b")
-      (bg-inactive "#0c0c0c")
+      (bg-active   "#3d3d3d")
+      (bg-inactive "#101010")

       ;; Basic hues for foreground values
       (red             "#ef6560")
diff --git a/ef-deuteranopia-dark-theme.el b/ef-deuteranopia-dark-theme.el
index 084b11c..238d696 100644
--- a/ef-deuteranopia-dark-theme.el
+++ b/ef-deuteranopia-dark-theme.el
@@ -46,13 +46,13 @@ (eval-and-compile
     '(;; Basic tones
       (bg-main     "#000a1f")
       (fg-main     "#ddddee")
-      (bg-dim      "#0f1c2d")
+      (bg-dim      "#1a2332")
       (fg-dim      "#7f8797")
-      (bg-alt      "#19263a")
+      (bg-alt      "#2c2c3f")
       (fg-alt      "#90afef")

-      (bg-active   "#30354f")
-      (bg-inactive "#071225")
+      (bg-active   "#3c3c4f")
+      (bg-inactive "#101625")

       ;; Basic hues for foreground values
       (red             "#cf8560")
diff --git a/ef-duo-dark-theme.el b/ef-duo-dark-theme.el
index bbb25be..fcda486 100644
--- a/ef-duo-dark-theme.el
+++ b/ef-duo-dark-theme.el
@@ -46,13 +46,13 @@ (eval-and-compile
     '(;; Basic tones
       (bg-main     "#070019")
       (fg-main     "#d0d0d0")
-      (bg-dim      "#1c1926")
+      (bg-dim      "#211c2b")
       (fg-dim      "#857f8f")
-      (bg-alt      "#262230")
+      (bg-alt      "#2c2836")
       (fg-alt      "#89afef")

-      (bg-active   "#363240")
-      (bg-inactive "#140e1c")
+      (bg-active   "#3c3846")
+      (bg-inactive "#181322")

       ;; Basic hues for foreground values
       (red             "#ef656a")
diff --git a/ef-night-theme.el b/ef-night-theme.el
index f54689c..91a98b6 100644
--- a/ef-night-theme.el
+++ b/ef-night-theme.el
@@ -43,13 +43,13 @@ (eval-and-compile
     '(;; Basic tones
       (bg-main     "#000e17")
       (fg-main     "#afbcbf")
-      (bg-dim      "#0f1b29")
+      (bg-dim      "#18242f")
       (fg-dim      "#70819f")
-      (bg-alt      "#1a2a2f")
+      (bg-alt      "#262e36")
       (fg-alt      "#b0a0a0")

-      (bg-active   "#28353f")
-      (bg-inactive "#0f121f")
+      (bg-active   "#363e46")
+      (bg-inactive "#121522")

       ;; Basic hues for foreground values
       (red             "#ef656a")
diff --git a/ef-trio-dark-theme.el b/ef-trio-dark-theme.el
index aaa4eba..8e00d68 100644
--- a/ef-trio-dark-theme.el
+++ b/ef-trio-dark-theme.el
@@ -43,13 +43,13 @@ (eval-and-compile
     '(;; Basic tones
       (bg-main      "#160f0f")
       (fg-main      "#d8cfd5")
-      (bg-dim       "#251a23")
+      (bg-dim       "#2b2328")
       (fg-dim       "#908890")
-      (bg-alt       "#33252d")
+      (bg-alt       "#3a2b35")
       (fg-alt       "#afdacf")

-      (bg-active    "#43353d")
-      (bg-inactive  "#1c1416")
+      (bg-active    "#4a3b45")
+      (bg-inactive  "#1f171a")

       ;; Basic hues for foreground values
       (red             "#f48359")
diff --git a/ef-winter-theme.el b/ef-winter-theme.el
index e552471..02451c5 100644
--- a/ef-winter-theme.el
+++ b/ef-winter-theme.el
@@ -43,13 +43,13 @@ (eval-and-compile
     '(;; Basic tones
       (bg-main      "#0f0b15")
       (fg-main      "#b8c6d5")
-      (bg-dim       "#161926")
+      (bg-dim       "#1d2030")
       (fg-dim       "#807c9f")
-      (bg-alt       "#202234")
+      (bg-alt       "#292b3d")
       (fg-alt       "#bf8f8f")

-      (bg-active    "#353554")
-      (bg-inactive  "#14121a")
+      (bg-active    "#393b4d")
+      (bg-inactive  "#18151d")

       ;; Basic hues for foreground values
       (red             "#f47359")
--
2.37.3
-1:-- Ef themes 0.6.0 for GNU Emacs (Post)--L0--C0--September 23, 2022 12:00 AM

Irreal: Emacs Is Not Just a Text Editor

Over at the Emacs subreddit, analysis230 has a sort of confession. He had long considered the phrase “Emacs is not just a text editor” to be something geezer developers said as they clung desperately to an aging technology. He says he didn’t even know what it meant.

Then one day for unspecified reasons he decided to move from VS Code to Emacs. He was already familiar with the Vim keybindings so he was up and running (with Doom Emacs) in short order. He really liked modal editing and found himself wishing for a keyboard-driven file manager. That’s when he discovered dired and, as he put it, the penny dropped. He finally understood what everyone meant when they said that Emacs is not just a text editor.

He’s since moved on to installing Mu4e but his story makes an important point: it can be hard to understand and appreciate the power of Emacs until you’ve learned some basics and used it for a while. Lots of worthwhile things in life are like that.

Speaking of the “not just a text editor” quote, analysis230 says,

I know I heard people say that but I always thought of them as denial-filled ramblings of people who have sunk a decade into learning a tool that’s slowly fading. Apparently, not the case at all.

His post is, it seems to me, the perfect answer to those who complain that Emacs has a steep learning curve. It may have but the it’s worth the climb.

-1:-- Emacs Is Not Just a Text Editor (Post jcs)--L0--C0--September 22, 2022 04:41 PM

Grant Rettke: You Gotta Try Using ~describe-symbol~

You gotta try out using describe-symbol because it is usually what you really want from describe-function and describe-variable. The most useful part is that it instantly teaches you when there are both a variable and a function with the same name. Sometimes it is surprising and better to know right away. For example M-x describe-symbol … Continue reading "You Gotta Try Using ~describe-symbol~"
-1:-- You Gotta Try Using ~describe-symbol~ (Post grant)--L0--C0--September 21, 2022 06:50 PM

Irreal: Webster 1913 and dictionary.el

Time moves on and things you thought you knew turn out to be not as accurate as you hoped. This happened to me recently in regard to using the Webster 1913 dictionary in Emacs. I recently posted about James Somers’ article on Webster’s 1913 and how to use it from withn Emacs. In it I repeated my advice to follow Marcin Borkowski’s recipe for making this wonderful resource available from within Emacs.

In the comments to that post, acdw and Brian Green note that the dictionary is now available from dict.org and that—as of Emacs 28—you can access it from the built-in dictionary-search. That means that any Emacs user can take advantage of this splendid resource without downloading extra packages or data.

If you call dictionary-search without configuring anything, it will prompt you for the dictionary source with a default of dict.org. As Green says, you can avoid that by simply adding

(setq dictionary-server "dict.org")

to your init.el.

That will give you all the definitions from Weber’s 1913 as well as the other dictionaries located at dict.org. If you’re coming to this post without the benefit of reading the backstory, you should definitely read Somers’ post, You’re probably using the wrong dictionary to see why you should bother with Webster’s 1913 to begin with. It is not, after all, the most modern dictionary but as Somers explains, it is the best if you’re looking for just the right word.

-1:-- Webster 1913 and dictionary.el (Post jcs)--L0--C0--September 21, 2022 05:26 PM

Protesilaos Stavrou: Emacs: notmuch-indicator version 0.1.0

This is a simple package that renders an indicator with an email count of the notmuch index on the Emacs mode line. The underlying mechanism is that of notmuch-count(1), which is used to find the number of items that match the given search terms. In practice, the user can define one or more searches and display their counters. These form a string which realistically is like: @50 😱1000 💕0 for unread messages, bills, and love letters, respectively.

The GNU ELPA package will be built by the server in a few hours from now. Local time is 2022-09-21 07:43 +0300 and I expect it to be done by noon.

Below are the release notes, which are just the contents of the README, given that this is the first stable version.


This is a simple package that renders an indicator with an email count of the notmuch index on the Emacs mode line. The underlying mechanism is that of notmuch-count(1), which is used to find the number of items that match the given search terms.

The indicator is enabled when notmuch-indicator-mode is on.

The user option notmuch-indicator-args provides the means to define search terms and associate them with a given label. The label is purely cosmetic, though it helps characterise the resulting counter.

The value of notmuch-indicator-args is a list of plists (property lists). Each plist consists of one mandarory property and two optional ones:

  1. The :terms, which is required, is a string that holds the command-line arguments passed to notmuch-count(1) (read the Notmuch documentation for the technicalities).

  2. The :label, which is optional, is an arbitrary string that is prepended to the return value of the above. If nil or omitted, no label is displayed.

  3. The face, which is optional, is the symbol of a face that is applied to the :label. It should not be quoted, so like :face bold. Good candidates are bold, italic, success, warning, error, though anything will do. If nil or omitted, no face is used.

Multiple plist lists represent separate notmuch-count(1) queries. These are run sequentially. Their return values are joined into a single string.

For instance, a value like the following defines three searches:

(setq notmuch-indicator-args
      '((:terms "tag:unread and tag:inbox" :label "@")
        (:terms "from:bank and tag:bills" :label "😱")
        (:terms "--output threads tag:loveletter" :label "💕")))

These form a string which realistically is like: @50 😱1000 💕0.

The user option notmuch-indicator-refresh-count determines how often the indicator will be refreshed. It accepts a numeric argument which represents seconds.

The user option notmuch-indicator-force-refresh-commands accepts as its value a list of symbols. Those are commands that will forcefully update the indicator after they are invoked.

The user option notmuch-indicator-hide-empty-counters hides zero counters from the indicator, when it is set to a non-nil value.

-1:-- Emacs: notmuch-indicator version 0.1.0 (Post)--L0--C0--September 21, 2022 12:00 AM

Irreal: Bespoke Notetaking with Org-mode

Matt Young over at Code Thrasher describes his bespoke notetaking workflow based on Org-mode. Rather than depend on packages like Org-roam for things such as backlinks, Young implemented his own version that worked just the way he wanted.

For example, in addition to backlinks, Young implemented a database that linked a person in his database to the any headlines that involved them. That allowed him to answer question like, “Who did I discuss [some topic] with?”

His setup is very idiosyncratic but finely tuned to work exactly the way he finds most useful. Others who don’t want the heavy machinery of things like Org-roam may find his system useful.

Regardless, the main takeaway for me is that his workflow is yet another example of how flexible Emacs is. You can bend it in almost any direction to get just the workflow you want.

-1:-- Bespoke Notetaking with Org-mode (Post jcs)--L0--C0--September 20, 2022 05:41 PM

Lars Ingebrigtsen: 20×10%

Hi!

It feels like I did one of these posts just the other day, but it was a month ago? Time sure does fly…

Since there’s a round number in the title of this blog post, perhaps I should natter on a bit more than usual? Sure! If you insist!

So: This is part of a series of posts where I bloviate on how Emacs development is going, partially through gamification of the Emacs bug tracker. This round, 10% was 205 bugs, so we started at 2050 open bugs, and after closing 10% of those, we’re now at…

Drum roll…

1966 open bugs! Well, that’s a decrease, at least — we had an increase in the previous stretch.

But as you can see, I got my act together and did some actual work.

Most of it was just the normal, eh, whatever, but I also went through all the bugs that were tagged as having a patch, and…

… applied a bunch of those. (But a surprising number, perhaps a quarter of those closed, had already actually been applied, but then discussion had just gone on to other things and nobody remembered to close the door after they left.)

As you can see, it’s been almost a year since the last time somebody took the time to do some triage on the patch reports, and that’s really too long — I found stuff in there that should have been applied a lot speedier, but…

Hm… I wonder how many bugs I’ve closed since I started this silly project back in 2019…

*gulp* 5300 bugs. Dude.

Full chart.

As you can clearly see, by using quadratic linear regression we will be at negative 500 in 2026, and to achieve that at that point, I’ll have to start opening bug reports instead of closing them.

That’s mathematics for you!

Anyway — has Emacs gained any new and exciting functionality over the last month? Er… Let’s look at the NEWS file.

Oh, yeah, there’s new branching commands for VC mode under C-x v b. But more significantly, there’s C-x v v in diff buffers (courtesy of Juri Linkov), which allows you to selectively commit things. It’s very convenient — for instance, if you’re working on something bigger in a file, and then see an unrelated typo in that file, you can now commit that unrelated thing in a separate commit without having to stash anything first. It’s very convenient for us that futz around a lot.

We now interpret OSC escape sequence in compilation buffers (courtesy of Matthias Meulien), which makes output from some newer compilers more pleasant to read.

We have a new package for setting wallpapers on desktops directly from Emacs and image-mode (courtesy of Stefan Kangas), which is quite convenient.

Aaand…

I finally incorporated the image cropping/cutting code I wrote a few years back.

Tada!

I know, right?

But since this is a round-numbered 10% post, let’s look at more stats. Let’s see… Oops! Found a bug in the stats generator, so if I’ve posted charts like this before, they’ve been er wrong.

But here’s the full overview of contributors per month, going all the way back to 1985:

Which is misleading as ever, since the VC (ahem) didn’t really keep that much track of things back in the 80s. But the last few years should be reasonably accurate:

And… It’s looking OK. We’re up from about 45 to about 65 per month over the last five years. (Note: Expert cherry-picking of dates; I’m an ex stock broker after all.)

But what about the number of commits instead?

Yowza. People have been busy. Let’s zoom in:

700 commits per month? We seem to have more than doubled the er development er velocity? I didn’t really know that, actually…

Let’s see… anything more? Oh yeah:

A couple old Emacs stats posts went to HackerNews and reddit, which totally makes the WordPress stats unreadable for normal days for a month. Fie!

And that’s it for this chart-heavy extravaganza.

-1:-- 20×10% (Post larsmagne23)--L0--C0--September 20, 2022 05:30 PM