Posts in category "Emacs"

expando.el v1.6

1 min read; 9 GFI

Recently I've had an odd problem with Emacs: occasionally, and somewhat randomly, as I wrote code, and only when I wrote Emacs Lisp code, and only when working in emacs-lisp-mode, I'd find that the buffer I was working in would disappear. Not just fully disappear, but more like if I'd used quit-window. Worse still, once this started happening, it wouldn't go away unless I turned Emacs off and on again.

Very un-Emacs!

Normally this would happen when I'm in full flow on something, so I'd just restart Emacs and crack on with the thing I was writing; because of this I wasn't diagnosing what was actually going on.

Then, today, as I was writing require in some code, and kept seeing the buffer go away when I hit q, it dawned on me.

Recently, when I cleaned up expando.el, I added the ability to close the window with q.

--- a/expando.el
+++ b/expando.el
@@ -58,7 +58,8 @@ Pass LEVEL as 2 (or prefix a call with \\[universal-argument] and
   (let ((form (preceding-sexp)))
     (with-current-buffer-window "*Expando Macro*" nil nil
       (emacs-lisp-mode)
-      (pp (funcall (expando--expander level) form)))))
+      (local-set-key (kbd "q") #'quit-window)
+      (pp (funcall (expando--expander level) form)))))

 (provide 'expando)

So, after opening a window for the purposes of displaying the expanded macro, switch to emacs-lisp-mode, locally set the binding so q will call on quit-window, and I'm all good.

Except... not, as it turns out.

To quote from the documentation for local-set-key:

The binding goes in the current buffer’s local map, which in most cases is shared with all other buffers in the same major mode.

D'oh!

Point being, any time I used expando-macro, I was changing the meaning of q in the keyboard map for emacs-lisp-mode. :-/

And so v1.6 of expando.el is now a thing, in which I introduce a derived mode of emacs-lisp-mode and set q in its keyboard map. In fact, I keep the keyboard map nice and simple.

(defvar expando-view-mode-map
  (let ((map (make-sparse-keymap)))
    (define-key map (kbd "q") #'quit-window)
    map)
  "Mode map for `expando-view-mode'.")

(define-derived-mode expando-view-mode emacs-lisp-mode "expando"
  "Major mode for viewing expanded macros.

The key bindings for `expando-view-mode' are:

\\{expando-view-mode-map}")

From now on I should be able to code in full flow state without the worry that my window will disappear at any given moment...

blogmore.el v4.2

2 min read; 11 GFI

Another wee update to blogmore.el, with a bump to v4.2.

After adding the webp helper command the other day, something about it has been bothering me. While the command is there as a simple helper if I want to change an individual image to webp -- so it's not intended to be a general-purpose tool -- it felt "wrong" that it did this one specific thing.

So I've changed it up and now, rather than being a command that changes an image's filename so that it has a webp extension, it now cycles through a small range of different image formats. Specifically it goes jpeg to png to gif to webp.

With this change in place I can position point on an image in the Markdown of a post and keep running the command to cycle the extension through the different options. I suppose at some point it might make sense to turn this into something that actually converts the image itself, but this is about going back and editing key posts when I change their image formats.

Another change is to the code that slugs the title of a post to make the Markdown file name. I ran into the motivating issue yesterday when posting some images on my photoblog. I had a title with an apostrophe in it, which meant that it went from something like Dave's Test (as the title) to dave-s-test (as the slug). While the slug doesn't really matter, this felt sort of messy; I would prefer that it came out as daves-test.

Given that wish, I modified blogmore-slug so that it strips ' and " before doing the conversion of non-alphanumeric characters to -. While doing this, for the sake of completeness, I did a simple attempt at removing accents from some characters too. So now the slugs come out a little tidier still.

(blogmore-slug "That's Café Ëmacs")
"thats-cafe-emacs"

The slug function has been the perfect use for an Emacs Lisp function I've never used before: thread-last. It's not like I've been avoiding it, it's just more a case of I've never quite felt it was worthwhile using until now. Thanks to it the body of blogmore-slug looks like this:

(thread-last
  title
  downcase
  ucs-normalize-NFKD-string
  (seq-filter (lambda (char) (or (< char #x300) (> char #x36F))))
  concat
  (replace-regexp-in-string (rx (+ (any "'\""))) "")
  (replace-regexp-in-string (rx (+ (not (any "0-9a-z")))) "-")
  (replace-regexp-in-string (rx (or (seq bol "-") (seq "-" eol))) ""))

rather than something like this:

(replace-regexp-in-string
 (rx (or (seq bol "-") (seq "-" eol))) ""
 (replace-regexp-in-string
  (rx (+ (not (any "0-9a-z")))) "-"
  (replace-regexp-in-string
   (rx (+ (any "'\""))) ""
   (concat
    (seq-filter
     (lambda (char)
       (or (< char #x300) (> char #x36F)))
     (ucs-normalize-NFKD-string
      (downcase title)))))))

Given that making the slug is very much a "pipeline" of functions, the former looks far more readable and feels more maintainable than the latter.

blogmore.el v4.1

1 min read; 10 GFI

Following on from yesterday's experiment with webp I got to thinking that it might be handy to add a wee command to blogmore.el that can quickly swap an image's extension from whatever it is to webp.

So v4.1 has happened. The new command is simple enough, called blogmore-webpify-image-at-point; it just looks to see if there's a Markdown image on the current line and, if there is, replaces the file's extension with webp no matter what it was before.

If/when I decide to convert all the png files in the blog to webp I'll obviously use something very batch-oriented, but for now I'm still experimenting, so going back and quickly changing the odd image here and there is a nicely cautious approach.

I have, of course, added the command to the transient menu that is brought up by the blogmore command.

One other small change in v4.1 is that a newly created post is saved right away. This doesn't make a huge difference, but it does mean I start out with a saved post that will be seen by BlogMore when generating the site.

boxquote.el v2.4

2 min read; 11 GFI

boxquote.el is another of my oldest Emacs Lisp packages. The original code itself was inspired by something I saw on Usenet, and writing my own version of it seemed like a great learning exercise; as noted in the thanks section in the commentary in the source:

Kai Grossjohann for inspiring the idea of boxquote. I wrote this code to mimic the "inclusion quoting" style in his Usenet posts. I could have hassled him for his code but it was far more fun to write it myself.

While I never used this package to quote text I was replying to in Usenet posts, I did use it a lot on Usenet, and in mailing lists, and similar places, to quote stuff.

The default use is to quote a body of text; often a paragraph, or a region, or perhaps even Emacs' idea of a defun.

,----
| `boxquote.el` provides a set of functions for using a text quoting style
| that partially boxes in the left hand side of an area of text, such a
| marking style might be used to show externally included text or example
| code.
`----

Where the package really turned into something fun and enduring, for me, was when I started to add the commands that grabbed information from elsewhere in Emacs and added a title to explain the content of the quote. For example, using boxquote-describe-function to quote the documentation for a function at someone, while also showing them how to get at that documentation:

,----[ C-h f boxquote-text RET ]
| boxquote-text is an autoloaded interactive native-comp-function in
| ‘boxquote.el’.
|
| (boxquote-text TEXT)
|
| Insert TEXT, boxquoted.
`----

Or perhaps getting help with a particular key combination:

,----[ C-h k C-c b ]
| C-c b runs the command boxquote (found in global-map), which is an
| interactive native-comp-function in ‘boxquote.el’.
|
| It is bound to C-c b.
|
| (boxquote)
|
| Show a transient for boxquote commands.
|
|   This function is for interactive use only.
|
| [back]
`----

Or figuring out where a particular command is and how to get at it:

,----[ C-h w fill-paragraph RET ]
| fill-paragraph is on fill-paragraph (M-q)
`----

While I seldom have use for this package these days (mainly because I don't write on Usenet or in mailing lists any more) I did keep carrying it around (always pulling it down from Melpa) and had all the various commands bound to some key combination.

(use-package boxquote
  :ensure t
  :bind
  ("<f12> b i"   . boxquote-insert-file)
  ("<f12> b M-w" . boxquote-kill-ring-save)
  ("<f12> b y"   . boxquote-yank)
  ("<f12> b b"   . boxquote-region)
  ("<f12> b t"   . boxquote-title)
  ("<f12> b h f" . boxquote-describe-function)
  ("<f12> b h v" . boxquote-describe-variable)
  ("<f12> b h k" . boxquote-describe-key)
  ("<f12> b h w" . boxquote-where-is)
  ("<f12> b !"   . boxquote-shell-command))

Recently, with the creation of blogmore.el, I moved the boxquote commands off the b prefix (because I wanted that for blogging) and onto an x prefix. Even then... that's a lot of commands bound to a lot of keys that I almost never use but still can't let go of.

Then I got to thinking: I'd made good use of transient in blogmore.el, why not use it here too? So now boxquote.el has acquired a boxquote command which uses transient.

The boxquote transient in action

Now I can have:

(use-package boxquote
  :ensure t
  :bind
  ("C-c b" . boxquote))

and all the commands are still easy to get to and easy to (re)discover. I've also done my best to make them context-sensitive too, so only applicable commands should be usable at any given time.

slstats.el v1.11

1 min read; 14 GFI

Yet another older Emacs Lisp package that has had a tidy up. This one is slstats.el, a wee package that can be used to look up various statistics about the Second Life grid. It's mainly a wrapper around the API provided by the Second Life grid survey.

When slstats is run, you get an overview of all of the information available.

An overview of the grid

There are also various commands for viewing individual details about the grid in the echo area:

  • slstats-signups - Display the Second Life sign-up count
  • slstats-exchange-rate - Display the L$ -> $ exchange rate
  • slstats-inworld - Display how many avatars are in-world in Second Life
  • slstats-concurrency - Display the latest-known concurrency stats for Second Life
  • slstats-grid-size - Display the grid size data for Second Life

There is also slstats-region-info which will show information and the object and terrain maps for a specific region.

Region information for Da Boom

As with a good few of my older packages: it's probably not that useful, but at the same time it was educational to write it to start with, and it can be an amusement from time to time.

wordcloud.el v1.4

1 min read; 13 GFI

I think I'm mostly caught up with the collection of Emacs Lisp packages that need updating and tidying, which means yesterday evening's clean-up should be one of the last (although I would like to revisit a couple and actually improve and extend them at some point).

As for what I cleaned up yesterday: wordcloud.el. This is a package that, when run in a buffer, will count the frequency of words in that buffer and show the results in a fresh window, complete with the "word cloud" differing-font-size effect.

Word cloud in action

This package is about 10 years old at this point, and I'm struggling to remember why I wrote it now. I know I was doing something -- either writing something or reviewing it -- and the frequency of some words was important. I also remember this doing the job just fine and solving the problem I needed to solve.

Since then it's just sat around in my personal library of stuff I've written in Emacs Lisp, not really used. I imagine that's where it's going back to, but at least it's cleaned up and should be functional for a long time to come.

quiz.el v1.7

1 min read; 7 GFI

I wondered yesterday:

...those question headers are displaying differently, with the background colour no longer spanning the width of the window. I'd like to understand why.

Turns out it was pretty straightforward:

diff --git a/quiz.el b/quiz.el
index 2dbe45d..c1ba255 100644
--- a/quiz.el
+++ b/quiz.el
@@ -40,7 +40,8 @@
 (defface quiz-question-number-face
   '((t :height 1.3
        :background "black"
-       :foreground "white"))
+       :foreground "white"
+       :extend t))
   "Face for the question number."
   :group 'quiz)

and so v1.7 has happened.

Quiz with reinstated header look

It looks like, perhaps, at some point in the past, :extend was t by default, but it no longer is? Either way, explicitly setting it to t has done the trick.

fasta.el v1.1

1 min read; 9 GFI

Today's Emacs Lisp package tidy-up is of a package I first wrote a couple of employers ago. While working on code I often found myself viewing FASTA files in an Emacs buffer and so I thought it would be fun to use this as a reason to knock up a simple mode for highlighting them.

fasta.el was the result.

An example FASTA file

While I doubt it was or is of much use to others, it helped me better understand simple font-locking in Emacs Lisp, and also made some buffers look a little less boring when I was messing with test data.

As for this update: it's the usual stuff of cleaning up deprecated uses of setf, mostly.

If bioinformatics-related Emacs Lisp code written by a non-bioinformatician is your thing, you might also find 2bit.el of interest too. Much like fasta.el it too probably doesn't have a practical use, but it sure was fun to write and taught me a few things along the way; it also sort of goes hand-in-hand with fasta.el too.

quiz.el v1.6

1 min read; 11 GFI

A quick little refresh of one of my old packages, this time quiz.el. This is a nice little distraction when you're working in Emacs, letting you spin up a quick trivia quiz in a buffer.

Quiz in action

It's backed by the Open Trivia Database, so there's a good few subjects, questions, and levels of difficulty to play with.

The only changes I've made to it in this release are the usual clean-ups of the deprecated uses of setf, plus I've added q as a binding to the quiz window to quickly quit the quiz.

I might have to come back and revisit it soon, as it looks like the default face choices could probably do with a rethink, and I can see at the moment that the attempt at a window-wide "header" for each question isn't working any longer. For comparison, here's how the package looked when running back when I first wrote it back in 2017:

How it originally looked

Leaving aside the fact that I was still running a very light Emacs then, those question headers are displaying differently, with the background colour no longer spanning the width of the window. I'd like to understand why.

expando.el v1.5

1 min read; 11 GFI

While I have been doing a lot of hacking on blogmore.el, I haven't forgotten my plan to revisit and refresh some of my older personal packages. This evening I've paid some attention to expando.el.

This started life a long time ago, as part of my grab-bag of handy functions that got carried around and copied from machine to machine, until I did a big tidy-up of everything back in 2017 and turned various things into packages that I managed via a self-hosted (well, GitHub pages hosted) package index.

It's a pretty simple but very useful bit of code that lets me quickly macroexpand a sexp at point and pretty print it into a display window. I've often found it indispensable when it came to writing my own macros.

The package in action

This release simply adds a lexical-binding header to the file, and also adds a q key binding to the resulting view window so that it can be quickly and easily closed.

Also, as with all my other personal packages, I've swapped away from using Delpa to simply using :vc to pull it in.

(use-package expando
  :vc (:url "https://github.com/davep/expando.el" :rev :newest)
  :bind
  ("C-c e" . expando-macro))

Or perhaps I should say...

(progn
  (use-package-vc-install
   '(expando (:url "https://github.com/davep/expando.el") nil) nil)
  (defvar use-package--warning69
    #'(lambda (keyword err)
        (let
            ((msg
              (format "%s/%s: %s" 'expando keyword
                      (error-message-string err))))
          (display-warning 'use-package msg :error))))
  (condition-case err
      (progn
        (if (fboundp 'expando-macro) nil
          (autoload #'expando-macro "expando" nil t))
        (let*
            ((name "C-c e") (key [3 101])
             (kmap
              (or (if (and nil (symbolp nil)) (symbol-value nil) nil)
                  global-map))
             (kdesc
              (cons (if (stringp name) name (key-description name))
                    (if (symbolp nil) nil 'nil)))
             (binding (lookup-key kmap key)))
          (require 'bind-key)
          (let
              ((entry (assoc kdesc personal-keybindings))
               (details
                (list #'expando-macro (if (numberp binding) nil binding))))
            (if entry (setcdr entry details)
              (add-to-list 'personal-keybindings (cons kdesc details))))
          (define-key kmap key #'expando-macro)))
    ((debug error) (funcall use-package--warning69 :catch err))))