Location-Based Themes in Emacs

There are lots of Emacs themes out there. People mostly use them to change colors. That’s great, but you can do more. As the documentation says:

Custom themes are collections of settings that can be enabled or disabled as a unit.

That can be any setting, not just colors.

I use Emacs in multiple situations. I use it at home, I use it in the office, I use it when I’m working from home, and so on. And there are settings that change depending on how and where I’m using Emacs, the most obvious of which being user-mail-address, which I want to set to user@home.org when I’m sending personal mail from home, and user@work.com when I’m sending professional mail from work. I have other settings that change between locations, like the directory where org files go, because reasons. Your location-dependent settings will be different.

So it seems the natural way to deal with this is to put all of the location-dependent settings in a theme, and load whichever theme is appropriate for what you’re doing. Since themes are groups of settings that can be turned on or off as a group, you can even switch themes in the middle of an Emacs session. For instance, if you’re a consultant working with two clients, you can enable theme client1 in the morning; and then, in the afternoon, disable theme client1 and enable theme client2 to update your settings.

Setting Up Location-Based Themes

To start with, let’s set up a couple of themes called personal and work, for personal and professional stuff, respectively. Check the value of custom-theme-directory. By default, Emacs looks for themes in the same directory as init.el, but I like to put them in a separate subdirectory, ~/.emacs.d/themes. Customize this to your taste. For the rest of this post, I’ll assume that you’re using ~/.emacs.d/themes.

Theme files should go in a file named <theme-name>-theme.el. So create the file ~/.emacs.d/themes/personal-theme.el to hold the personal theme:

(deftheme personal
  "Settings when working on personal projects."
  :family "location")

(custom-theme-set-variables 'personal
 '(user-mail-address "bob@home.org")
 ;; Add additional variables here.
 )

(provide-theme 'loc-personal)

You can now run M-x load-theme personal to load the theme. Theme files contain Emacs Lisp code, which can be a security risk, so the first time you load it, or any time you make any changes, Emacs will ask you to confirm whether to load the theme, and whether to mark it as safe in the future. If you say yes, the next time it’ll just load the theme asking for confirmation.

Follow the same steps to create a work theme.

Loading A Theme at Startup

Once you have something that works reasonably well, you’ll probably want something that loads the right theme when Emacs starts. For this example, we’ll assume that if the hostname is my-laptop, then you’re working on personal things and want to load the personal theme, while if the hostname is office-workstation, then you’re at work and want to load the work theme.

Add something like the following to your ~/.emacs.d/init.el:

(add-hook 'emacs-startup-hook
  (lambda nil
    ;; Figure out which theme to load, depending on which machine this
    ;; is running on, and which options were specified.
    (let ((loc-theme 'unknon)
          )
      (cond
       ;; Running on personal laptop.
       ((string= system-name "my-laptop")
        (setq loc-theme 'personal)
        )

       ;; Running on work machine.
       ((string= system-name "office-workstation")
        (setq loc-theme 'work))

       ;; Add any other locations/work modes here.
       ))

      ;; Check whether the theme exists. If it does, load it.
      (if (member loc-theme (custom-available-themes))
          (progn
            (load-theme loc-theme)
            )
        (warn "Can't find location theme \"%s\"" loc-theme)
        ))))

Here, we’re just looking at the hostname, but obviously the condition can be arbitrarily complex. You might pick different themes depending on the day of the week, or whether you’re ssh-ed in from another host, the phase of the moon, or whatever makes sense in your situation. Add or remove themes as necessary.

Further Thoughts

One advantage of doing things this way is separation of information. Maybe you want to make your Emacs setup publicly visible on github, but your work setup includes hostnames or other information that shouldn’t leave company premises. In this case, you can store your work-theme.el file on your company machine, perhaps in a separate directory. You can add an entry to custom-theme-load-path, a directory outside of ~/.emacs.d, so that your work theme doesn’t accidentally get added to the git repo that has your init.el and your publicly-visible themes.

There’s one problem that I haven’t found a good solution to: child themes. Let’s say you have your work theme, with a hundred work-related settings. But of those 100 settings, there are three that change depending on whether you’re logged in to host1 or host2.It would be nice to have a host1 theme that automatically includes everything from the work theme, plus the three settings that are specific to host1. I don’t see a good way of doing this. It might make sense to use literate programming to generate multiple *-theme.el files from one source .org file.

Literate Lists

I’ve written before about literate programming, and how one of its most attractive features is that you can write code with the primary goal of conveying information to a person, and only secondarily of telling a computer what to do. So there’s a bit in my .bashrc that adds directories to $PATH that isn’t as reader-friendly as I’d like:

for dir in \
    /usr/sbin \
    /opt/sbin \
    /usr/local/sbin \
    /some/very/specific/directory \
    ; do
    PATH="$dir:$PATH"
done

I’d like to be add a comment to each directory entry, explaining why I want it in $PATH, but sh syntax won’t let me: there’s just no way to interleave strings and comments this way. So far, I’ve documented these directories in a comment above the for loop, but that’s not exactly what I’d like to do. In fact, I’d like to do something like:

$PATH components

  • /usr/sbin
  • /usr/local/bin
for dir in \
    {{path-components}} \
    ; do
    PATH="$dir:$PATH"
done

Or even:

$PATH components

DirectoryComments
/usr/sbinsbin directories contain sysadminny stuff, and should go before bin directories.
/usr/local/binLocally-installed utilities take precedence over vendor-installed ones.
for dir in \
    {{path-components}} \
    ; do
    PATH="$dir:$PATH"
done

Spoiler alert: both are possible with org-mode.

Lists

The key is to use Library of Babel code blocks: these allow you to execute org-mode code blocks and use the results elsewhere. Let’s start by writing the code that we want to be able to write:

#+name: path-list
- /usr/bin
- /opt/bin
- /usr/local/bin
- /sbin
- /opt/sbin
- /usr/local/sbin

#+begin_src bash :noweb no-export :tangle list.sh
  for l in \
      <<org-list-to-sh(l=path-list)>> \
      ; do
      PATH="$l:$PATH"
  done
#+end_src

Note the :noweb argument to the bash code block, and the <<org-list-to-sh()>> call in noweb brackets. This is a function we need to write. It’ll (somehow) take an org list as input and convert it into a string that can be inserted in this fragment of bash code.

This function is a Babel code block that we will evaluate, and which will return a string. We can write it in any supported language we like, such as R or Python, but for the sake of simplicity and portability, let’s stick with Emacs lisp.

Next, we’ll want a test rig to actually write the org-list-to-sh function. Let’s start with:

#+name: org-list-to-sh
#+begin_src emacs-lisp :var l='nil
  l
#+end_src

#+name: test-list
- First
- Second
- Third

#+CALL: org-list-to-sh(l=test-list) :results value raw

The begin_src block at the top defines our function. For now, it simply takes one parameter, l, which defaults to nil, and returns l. Then there’s a list, to provide test data, and finally a #+CALL: line, which contains a call to org-list-to-sh and some header arguments, which we’ll get to in a moment.

If you press C-c C-c on the #+CALL line, Emacs will evaluate the call and write the result to a #+RESULTS block underneath. Go ahead and experiment with the Lisp code and any parameters you might be curious about.

The possible values for the :results header are listed under “Results of Evaluation” in the Org-Mode manual. There are a lot of them, but the one we care the most about is value: we’re going to execute code and take its return value, not its printed output. But this is the default, so it can be omitted.

If you tangle this file with C-c C-v C-t, you’ll see the following in list.sh:

for l in \
    ((/usr/bin) (/opt/bin) (/usr/local/bin) (/sbin) (/opt/sbin) (/usr/local/sbin)) \
    ; do
    PATH="$l:$PATH"
done

    It looks as though our org-mode list got turned into a Lisp list. As it turns out, yes, but not really. Let’s change the source of the org-list-to-sh() function to illustrate what’s going on:

    #+name: org-list-to-sh
    #+begin_src emacs-lisp :var l='nil :results raw
      (format "aaa %s zzz" l)
    #+end_src

    Now, when we tangle list.sh, it contains

        aaa ((/usr/bin) (/opt/bin) (/usr/local/bin) (/sbin) (/opt/sbin) (/usr/local/sbin)) zzz \

    So the return value from org-list-to-sh was turned into a string, and that string was inserted into the tangled file. This is because we chose :results raw in the definition of org-list-to-sh. If you play around with other values, you’ll see why they don’t work: vector wraps the result in extraneous parentheses, scalar adds extraneous quotation marks, and so on.

    Really, what we want is a plain string, generated from Lisp code and inserted in our sh code as-is. So we’ll need to change the org-list-to-sh code to return a string, and use :results raw to insert that string unchanged in the tangled file.

    We saw above that org-list-to-sh sees its parameter as a list of lists of strings, so let’s concatenate those strings, with space between them:

    #+name: org-list-to-sh
    #+begin_src emacs-lisp :var l='nil :results raw
      (mapconcat 'identity
    	     (mapcar
    	      (lambda (elt)
    		(car elt)
    		)
    	      l)
    	     " ")
    #+end_src

    This yields, in list.sh:

    for l in \
        /usr/bin /opt/bin /usr/local/bin /sbin /opt/sbin /usr/local/sbin \
        ; do
        PATH="$l:$PATH"
    done

    which looks pretty nice. It would be nice to break that list of strings across multiple lines, and also quote them (in case there are directories with spaces in them), but I’ll leave that as an exercise for the reader.

    Tables

    That takes care of converting an org-mode list to a sh string. But earlier I said it would be even better to define the $PATH components in an org-mode table, with directories in the first column and comments in the second. This is easy, with what we’ve already done with strings. Let’s add a test table to our org-mode code, and some code to just return its input:

    #+name: echo-input
    #+begin_src emacs-lisp :var l='nil :results raw
      l
    #+end_src
    
    #+name: test-table
    | *Name*   | *Comment*        |
    |----------+------------------|
    | /bin     | First directory  |
    | /sbin    | Second directory |
    | /opt/bin | Third directory  |
    
    #+CALL: echo-input(l=test-table) :results value code
    
    #+RESULTS:

    Press C-c C-c on the #+CALL line to evaluate it, and you’ll see the results:

    #+RESULTS:
    #+begin_src emacs-lisp
    (("/bin" "First directory")
     ("/sbin" "Second directory")
     ("/opt/bin" "Third directory"))
    #+end_src

    First of all, note that, just as with lists, the table is converted to a list of lists of strings, where the first string in each list is the name of the directory. So we can just reuse our existing org-list-to-sh code. Secondly, org has helpfully stripped the header line and the horizontal rule underneath it, giving us a clean set of data to work with (this seems a bit fragile, however, so in your own code, be sure to sanitize your inputs). Just convert the list of directories to a table of directories, and you’re done.

    Conclusion

    We’ve seen how to convert org-mode lists and tables to code that can be inserted into a sh (or other language) source file when it’s tangled. This means that when our code includes data best represented by a list or table, we can, in the spirit of literate programming, use org-mode formatting to present that data to the user as a good-looking list or table, rather than just list it as code.

    One final homework assignment: in the list or table that describes the path elements, it would be nice to use org-mode formatting for the directory name itself: =/bin= rather than /bin. Update org-list-to-sh to strip the formatting before converting to sh code.

    Monthly Reports with Org-Mode

    Like a lot of people, I have to submit a monthly “bullet” report, listing the things I’ve done in the previous month.

    Since I use Org-Mode for planning, scheduling, and organizing tool (or rather: I tend to throw a bunch of notes into a file and tell this love child of a day planner and a wiki to tell me what I should do next), I figured I should use that.

    I could use the timeline feature (C-c a L), but that only works for the current buffer, and I want a report that covers all buffers, just like the agenda.

    What I’ve done in the past is to use C-c a a to get the agenda view, go back a month, toggle displaying completed/archived/whatever items, and go through that to make my bullet list.

    But I finally got around to encapsulating that into a single M-x bullet command:

    ; Make it easier to generate bullets for $BOSS
    (defvar bullet-entry-types
      '(:closed)
      "Org-mode agenda types that we want to see in the monthly bullet report
    See `org-agenda-entry-types'."
      )
    
    (defun bullets ()
      "Show a list of achievements for the past month, for monthly reports.
    Uses `org-agenda'.
    "
      (interactive)
      (require 'org-agenda)
      ; All we're doing here, really, is calling `org-agenda' with
      ; arguments giving a start date and a number of days. But to do
      ; that, we need to figure out
      ; - the date of the first of last month
      ; - the number of days in last month
      (let* ((now (current-time))
    	 ; Figure out when last month was. Assuming that I run this
    	 ; close to the beginning of a month, then `now' minus two
    	 ; weeks was some time in the previous month. We can use that
    	 ; to extract the year and month that we're interested in.
    	 (2weeks-ago
    	  (time-subtract now
    			 (days-to-time 14)))
    	 ; We'll also need to know when the first of this month was,
    	 ; to find out how long last month was. If today is the 12th
    	 ; of the month, then the first of the month was `now' minus
    	 ; 11 days.
    	 (1st-of-this-month
    	  (time-subtract now
    			 (days-to-time
    			  (- (nth 3 (decode-time now))
    			     1))))
    	 ; Ditto to find the first of last month.
    	 (1st-of-last-month
    	  (time-subtract 2weeks-ago
    			 (days-to-time
    			  (- (nth 3 (decode-time 2weeks-ago))
    			     1))))
    	 ; The length of last month is the difference (in days)
    	 ; between the first of last month, and the first of this
    	 ; month.
    	 (len-last-month
    	  (time-to-number-of-days
    	   (time-subtract 1st-of-this-month
    			  1st-of-last-month)))
    	 (start-date (decode-time 1st-of-last-month))
    	 (start-year (nth 5 start-date))	; Year number
    	 (start-mon (nth 4 start-date))		; Month number
    	 ; Restrict the agenda to only those types of entries we're
    	 ; interested in. I think this takes advantage of dynamic
    	 ; scoping, which is normally an abomination unto the lord,
    	 ; but is useful here.
    	 (org-agenda-entry-types bullet-entry-types)
    	 )
        ; Create an agenda with the stuff we've prepared above
        (org-agenda-list nil
    		     (format "%04d-%02d-01"
    			     start-year
    			     start-mon)
    		     len-last-month)
        ))

    I hope this proves useful to someone.

    /kernel.el

    From an Ubuntu security advisory:

    After a standard system upgrade you need to restart emacs to effect the
    necessary changes.
    
    Details follow:
    
    Hendrik Tews discovered that emacs21 did not correctly handle certain
    GIF images. By tricking a user into opening a specially crafted GIF,
    a remote attacker could cause emacs21 to crash, resulting in a denial
    of service.

    Gosh, they make it sound as if Emacs is a daemon, run from an init file, running all the time and… oh, wait. Right.

    Making Emacs Do Stuff at Startup

    Like many users, I start an emacs session in .xinitrc and use it throughout the day. Since I’ve recently started using Emacs Planner, I wanted it to start up automatically in that first Emacs session (but not subsequent ones, if I just want to edit a file).

    Read More

    How to Prevent Lines from Wrapping in Emacs

    By default, Emacs’s buffer list truncates lines at the right edge of the screen: if you’re editing a file with a long name, it doesn’t wrap around; you have to use C-x < and C-x > to scroll the viewport left and right.

    I’d always wondered how to do that, since it can be useful when editing files like ~/.ssh/known_hosts, where the useful information is at the beginning of the line, and the wrapped keys get in the way.

    Now I know:

    (setq truncate-lines t)