Custom Emacs functions No. 1 - Baseline

What for?

An extensible, customizable, free/libre text editor […]

What that means for most users – me included – is that you can make Emacs yours. And part of this is adding custom functions.

This might be because there is a functionality missing or just because the builtin way of doing stuff doesn’t compute the same way my head does.

You will be hard pressed to find an Emacs init.el without custom functions. And this is no different with mine.

XKCD - Workflow

In this and a few following posts I will be showcasing my functions and what they do. Some are written by me and some are lifted from other more versed people.

I will also be providing my sources for the code – mostly in the documentation strings of the functions.

Commands and functions

In this post I will be talking about the functions in my module timu-base.el. This contains defaults for my configuration.

timu-base-setv

This macro implements a way to easily customize variables using customize-set-variable – similar to setq.

(defmacro timu-base-setv (&rest args)
  "Handle ARGS like `setq' using `customize-set-variable'.
Credit: https://www.reddit.com/r/emacs/comments/qg8tga/comment/hi4xp8d."
  (let (body)
    (while args
      (let* ((var (pop args)) (val (pop args)))
        (push `(customize-set-variable ',var ,val) body)))
    (macroexp-progn (nreverse body))))

With this the following Emacs Lisp …

(timu-base-setv global-auto-revert-non-file-buffers t
                    load-prefer-newer t)

… would expand to

(progn
  (customize-set-variable 'global-auto-revert-non-file-buffers t)
  (customize-set-variable 'load-prefer-newer t))

Granted I don not use the macro. However I keep it handy for such a time that I might need it.

timu-base-disable-yes-or-no-p

On occasions the “yes or no” prompt that one gets to confirm an action can get pretty annoying. This functions facilitates getting rid of the prompt.

(defun timu-base-disable-yes-or-no-p (orig-fun &rest args)
  "Advice how to answer yes or no automatically in Emacs.
Use around any ORIG-FUN to \"skip\" `yes-or-no-p' and `y-or-n-p' with t.
Credit: https://stackoverflow.com/a/35263420."
  (advice-add 'yes-or-no-p :around (lambda (&rest _) t))
  (advice-add 'y-or-n-p :around (lambda (&rest _) t))
  (unwind-protect
      (apply orig-fun args)
    (advice-remove 'yes-or-no-p (lambda (&rest _) t))
    (advice-remove 'y-or-n-p (lambda (&rest _) t))))

I use it mostly as an advice around other functions and commands

(advice-add 'dired-do-flagged-delete :around #'timu-base-disable-yes-or-no-p)
(advice-add 'dired-do-delete :around #'timu-base-disable-yes-or-no-p)

With this I can delete files without being prompted to confirm.

timu-base-async-shell-command-no-window

Running async-shell-command results in a new window with the output of the shell command. This is not always desired. This function omits the windows.

(defun timu-base-async-shell-command-no-window (command)
  "Do not display the `async-shell-command' COMMAND output buffer.
Credit: https://stackoverflow.com/a/60333836
Credit: https://stackoverflow.com/a/47910509."
  (interactive)
  (let ((display-buffer-alist
         (list (cons
                "\\*Async Shell Command\\*.*"
                (cons #'display-buffer-no-window nil)))))
    (async-shell-command command)))

Say you want to open a Finder.app window at the current location.

(defun shell-open-dir ()
  "Open current directory at point with shell command \"open\".
This will open \"Finder.app\" at current location."
  (interactive)
  (timu-base-async-shell-command-no-window "open ./" ))

timu-base-visit-emacs-init & timu-base-visit-emacs-early-init

These two functions are quite straight forward. There are here just for completeness sake. I bind these to keys to get to init.el & early-init.el as fast a possible.

(defun timu-base-visit-emacs-init ()
  "Load `init.el' file into a buffer."
  (interactive)
  (find-file user-init-file))

(defun timu-base-visit-emacs-early-init ()
  "Load `early-init.el' file into a buffer."
  (interactive)
  (find-file early-init-file))

Quite honestly, I think these might be fine as lambdas in the Lisp for the keybindings.

timu-base-make-key-string

This one needs a bit of explaining.

You see I mostly use macOS and my muscle memory is quite used to the keybindings on a Mac. I have stated this before in this post.

However I do use Linux and windows as well. But I still want to keep the keybindings that I know. Or at least close enough to those.

I swap the control key and the alt key system wide on Linux or Windows machines. This way the control key is next to the space bar, which is the macOS command key’s position on the keyboard.

However I want the alt (now control key) key in Emacs to be still be alt (Meta). I comes this function.

(defun timu-base-make-key-string (modsymbol basic-event)
  "Convert the combination of MODSYMBOL and BASIC-EVENT.
BASIC-EVENT can be a character or a function-key symbol.  The
return value can be used with `define-key'.
Credit: https://gist.github.com/mmarshall540/8db9b5bac8dc5670cb9323e387de1317"
  (vector (event-convert-list `(,modsymbol ,basic-event))))

Which gets used on Linux and Windows like so

(pcase system-type
  ((or'windows-nt 'gnu/linux)
   ;; Escaped chars are:
   ;; tab return space del backspace (typically translated to del)
   (dolist
       (char
        (append
         '(up down left right menu print scroll pause
              insert delete home end prior next
              tab return space backspace escape
              f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12)
         ;; Escape gets translated to `C-\[' in `local-function-key-map'
         ;; We want that to keep working, so we don't swap `C-\[' with `M-\['.
         (remq ?\[ (number-sequence 33 126))))
     ;; Changing this to use `input-decode-map', as it works for more keys.
     (define-key input-decode-map
       (timu-base-make-key-string 'control char)
       (timu-base-make-key-string 'meta char))
     (define-key input-decode-map
       (timu-base-make-key-string 'meta char)
       (timu-base-make-key-string 'control char)))))

Quite a unique problem to solve I think. But this is one of the functions I always need to stay sane.

Conclusion

As a rule of thumb we Emacs users spend a considerable amount of time tweaking it. Is it worth it the time? You tell me!

UPDATE - 27. May 2023

Renamed a few modules (now ‘bits’). Hence the few changes in this post.