Journal

2024-02-17 Opening external drives in Dired

I use external drives pretty often – for backups, for moving files between machines, and for storing mp4 files, for example. I’ve been using UDisks for quite some time now. It automounts an external drive under the /run/media/$USER/VolumeName directory (where VolumeName is different for each drive, of course).

I also use Dired as my main file manager. As most Emacsers know, it’s far from shiny, but it’s incredibly powerful, especially combined with some other Emacs features.

One problem I have is that when I insert a drive into one of the USB ports, I’d like to be able to open it in Dired. I could of course create a bookmark pointing to it, but I don’t want to maintain a seperate bookmark for every drive I use. I could also bookmark the /run/media/mbork directory, but then I’d have to press one more key to get to the VolumeName directory. I figured that I could create a function which opens Dired in the root directory of my drive, assuming that it’s the only mounted one. (If there are more mounted drives – which means more directories under /run/media/mbork – the function should allow me to select one of them.) Of course, I could probably go even further, detect that a drive was mounted and open its directory in Dired automatically, but that might be a bit too much. Sometimes I don’t want to do anything with the drive (for example, when I use it only to make my daily backups), and sometimes I might be doing something else (for example reading or writing), and Emacs suddenly switching to another buffer would not be helpful.

Anyway, here’s some Lisp code I wrote in about 10 minutes.

(defcustom automount-directory (format "/run/media/%s" user-login-name)
  "Directory under which drives are automounted.")

(defun automount-open-in-dired ()
  "Open the automounted drive in Dired.
If there is more than one, let the user choose."
  (interactive)
  (let ((dirs (directory-files automount-directory nil "^[^.]")))
    (dired (file-name-concat
            automount-directory 
            (cond ((null dirs)
                   (error "No drives mounted at the moment"))
                  ((= (length dirs) 1)
                   (car dirs))
                  (t
                   (completing-read "Open in dired: " dirs nil t)))))))

And of course, if you want to learn to code your own convenience commands like this, as usual I recommend Introduction to programming in Emacs Lisp by the late Robert J. Chassell as an appetizer, and my book, Hacking your way around in Emacs, as the next course.

CategoryEnglish, CategoryBlog, CategoryEmacs

Comments on this page

2024-02-12 Finding Bible quotes

I often have the need to find some particular quote of the Bible – either I am reading some religious text or I want to link to some passage on my blog. What I miss is the ability to quickly see the relevant passage, open it in a browser and create an Org mode link to it.

Me being me, I’d like to do all of this from the comfort of Emacs (naturally). I found a few Emacs packages with similar functionality, but they all have the same main issue: they do not support the Catholic Bible. (And even if they had, they do not seem to have the features I’d like to have.)

Of course, I set out to write my own. It is a very simple package, with one command, nab-open-place. It asks for the Bible book name (with autocompletion) and the chapter number; with a prefix argument, it also asks for the verse number. It then opens the selected place in the browser, puts the link to it on the top of the kill ring, and optionally stores an Org mode link in org-stored-links so that it can be inserted using org-insert-link (C-c C-l).

Note that it doesn’t have any error checking, and will happily create a link to the 10000th chapter (or 10000th verse) etc. – this does not bother me, and it’s quite a bit of work to add that (I’d basically need a database containing precise information about the number of chapters in every book and the number of verses in every chapter). I hope that even this very simple package will make my life easier (and the next opportunity to use it will happen quite soon when I write the next post at Crimson Eleven Delight Petrichor).

One last thought is that I’d like to have a similar tool for the most popular Polish translation of the Bible, the Millennium Bible. Unfortunately, links to various books and chapters there don’t follow a simple pattern like the New American Bible, so I put that on a backburner. Maybe some other day…

CategoryEnglish, CategoryBlog, CategoryEmacs, CategoryOrgMode, CategoryFaith

Comments on this page

2024-01-22 From the kill ring to a register

I am probably one of the five or six people on this planet who use Emacs registers. I mostly use them for text insertion (especially when I need to insert some boilerplate – and especially more than one piece of it – several times, but it’s not general enough to be put into Yasnippet), but I sometimes store window configurations in them, and I might have used them for other purposes once or twice.

Sometimes, though, I kill some text to yank it elsewhere and only then realize that I’ll need to kill/yank some other fragments, too. Using the kill ring to do this one time is fine (with M-y or with browse-kill-ring); using it more times is rather inconvenient. Hence I decided to write a little command to copy the text from the kill ring to a specified register. And here it is.

(defun shorten-string (string length &optional suffix)
  "Shorten STRING to LENGTH and append SUFFIX if it is longer.
If LENGTH is too short, output the first character of STRING and
SUFFIX."
  (setq suffix (or suffix "..."))
  (if (<= (length string) length)
      string
    (format "%s%s"
            (substring string 0 (max (- length (length suffix))
                                     1))
            suffix)))

(defun copy-kill-to-register (register n)
  "Copy text from the kill ring to REGISTER.
With prefix argument N, use the Nth kill ring entry, but don't
move the last yank pointer to it."
  (interactive (let* ((n (prefix-numeric-value current-prefix-arg)))
                 (list (register-read-with-preview
                        (format
                         "Kill at position %s (%s) to register: "
                         n
                         (shorten-string (current-kill (1- n) t) 40)))
                       n)))
  (set-register register (current-kill (1- n) t)))

As you can see, I decided to be a bit fancy – for example, I display the text to be copied in the prompt, but make it no longer than 40 characters. It took me about half an hour to put all this together, but a working prototype (without the prompt shenanigans) took less than 10 minutes. This is the power of Emacs in action – modifying your Emacs to better meet your needs is a very frictionless experience, you just open some Elisp buffer, write a command, press C-M-x and it just works.

Note that the above code is not DRY – the (current-kill (1- n) t) fragment is repeated twice. The reason is that I wanted to have it in both the interactive clause and in the function body. As far as I know, there is no way to set a temporary variable so that it is available in both places. There are, however, two ways of circumventing this (neither very elegant). One is to declare a global variable for that purpose and setq it within he scope of interactive:

(defvar copy-kill-to-register-text nil
  "A variable set in `copy-kill-to-register'.
This is an internal variable `setq' in the `interactive' clause
of `copy-kill-to-register' so that it can be accessed later, in
the body of that command.")

(defun copy-kill-to-register (register n)
  "Copy text from the kill ring to REGISTER.
With prefix argument N, use the Nth kill ring entry, but don't
move the last yank pointer to it."
  (interactive (let* ((n (prefix-numeric-value current-prefix-arg)))
                 (setq text (current-kill (1- n) t))
                 (list (register-read-with-preview
                        (format
                         "Kill at position %s (%s) to register: "
                         n
                         (shorten-string text 40)))
                       n)))
  (set-register register text))

Definitely not elegant, but at least we don’t repeat ourselves. If we had some more complicated code (or more time-consuming to compute one, like a result of some complex calculations or of a network request, or one with side effects), this may be the way to go.

Another trick would be not to use interactive to get the value of text. We could set register to nil within interactive and then, if it is nil, ask the user for its value in the body of the function:

(defun copy-kill-to-register (register n)
  "Copy text from the kill ring to REGISTER.
With prefix argument N, use the Nth kill ring entry, but don't
move the last yank pointer to it."
  (interactive (list nil (prefix-numeric-value current-prefix-arg)))
  (let ((text (current-kill (1- n) t)))
    (setq register (or register
                       (register-read-with-preview
                        (format
                         "Kill at position %s (%s) to register: "
                         n
                         (shorten-string text 40)))))
    (set-register register text)))

This way, if our function is used in Elisp (and register is a character), it will just copy the n​th entry on the kill ring to the given register, and if used interactively, the interactive clause will set register to nil and register-read-with-preview will be evaluated. Still not beautiful, but at least it works (and we don’t have to type (current-kill (1- n) t) twice). It is up to you which variant to use – personally, I’ll still stick to the first one, and if I really wanted (or needed) to avoid evaluating the same expression twice, I would probably go with the “temporary global variable” variant.

As usual, let me remind you that if you want to learn to write code making your life in Emacs simpler, you should really read Introduction to programming in Emacs Lisp by the late Robert J. Chassell, and you may find my book, Hacking your way around in Emacs, a nice next step. And since I am currently working – slowly but steadily – on improving said book, if you have any suggestions, I might consider writing one more chapter!

CategoryEnglish, CategoryBlog, CategoryEmacs

Comments on this page

More...