Showing posts with label emacs. Show all posts
Showing posts with label emacs. Show all posts

Sunday, 22 July 2012

Emacs: Speeding Up Loading Your .emacs File

One of the best things about Emacs is its extensibility. The bulk of its editing functions are written in the same language as the editor itself, Emacs Lisp, which you, the user, can program inside the editor itself.

Experienced Emacs users invariably find packages on the Web, maybe from EmacsWiki or github, and stuff them into their .emacs file, loading them with (require). For a small number of small packages, this can often work well, but eventually you're going to either stumble across a large package like Org Mode, or for the polyglot programmer, load the major modes for half a dozen or more languages.

All those (load) and (require) calls will slow down the time taken to load your .emacs file considerably. If you want to see how long it takes, M-x emacs-init-time will give you the answer. Personally, I consider load times in excess of one or two seconds to be unacceptable for development-class hardware.

Loading What You Need, When You Need It

So how do you speed up loading of your init file, but at the same time keep everything that makes Emacs work for you? Simple: autoload and eval-after-load.

autoload tells Emacs that a function is defined in a specific file, but doesn't actually load that file until it's explicitly asked for, e.g:

(autoload 'ruby-mode "ruby-mode")

This tells Emacs that the function ruby-mode is in the file ruby-mode.el (or ruby-mode.elc, if appropriate). When the function is invoked, the path elements in load-path are checked in order to find ruby-mode.el, which is then loaded on-demand.

Automatically Using the Right Mode for a File

So, how does ruby-mode get called? Well, it's either explicitly (M-x ruby-mode), or by specifying that it should be invoked when you load an appropriate file, like this:

(add-to-list 'auto-mode-alist '("\\.rb$" . ruby-mode))

This says that Emacs should enter ruby-mode whenever a file with the ".rb" extension is loaded. When it is, ruby-mode is started, and the autoload ensures that it is loaded from the appropriate file if required.

Customising Just-Loaded Code

But what happens if you want to do more than just load the major mode when .rb files are loaded? Maybe you also want to load yasnippet, ace-jump or other modules?

You've got two choices: a mode hook, or eval-after-load. The former is cleaner, but the latter is more flexible, and available in the event that there's no mode hook variable for your language (e.g. scala-mode-hook isn't available until after scala-mode is loaded).

eval-after-load takes an Emacs Lisp form as an unevaluated list and only evaluates it once the specified file has been loaded, e.g.:

(eval-after-load "ruby-mode" '(setup-autocompletion))

The time-saver here comes from the fact that the quoted form is evaluated after load, so it can potentially result in quite a lot of work getting done when it's loaded, and you'll only feel the effects when you enter that specific mode.

In this case, the same effect can be achieved by adding a function to ruby-mode-hook:

(add-to-list 'ruby-mode-hook #'setup-autocompletion)

Here, I'm assuming that (setup-autocompletion) has already been defined and, as in my case, is referred to by multiple programming modes (nb: that function is one I've written myself; it's not part of Emacs, so you won't have it unless you've written one with the same name!). The form can be atribrarily complex, but it must be a single form. Multiple forms can be wrapped in a (progn) to compose them. Here's an example for cperl-mode that's more involved, setting some defaults, defining some Perl-specific functions, and doing that (setup-autocompletion) again.

(eval-after-load "cperl-mode"
  '(progn
    (setq
     cperl-merge-trailing-else nil
     cperl-continued-statement-offset 0
     cperl-extra-newline-before-brace t)
    (defun installed-perl-version ()
      (interactive)
      (let ((perl (executable-find "perl")))
        (if perl
            (shell-command-to-string (concatenate 'string perl " -e '($v = $]) =~ s/(?<!\\.)(?=(\\d{3})+$)/./g; print $v;'")))))
    (defun use-installed-perl-version ()
      (interactive)
      (let ((perl-version (installed-perl-version)))
        (if perl-version
            (save-excursion
              (beginning-of-buffer)
              (let ((case-fold-search nil))
                (re-search-forward "^use [a-z]" (point-max) t)
                (beginning-of-line)
                (open-line 1)
                (insert (concatenate 'string "use v" perl-version ";"))))
            (message "Couldn't determine perl version"))))
    (setup-autocompletion)))

eval-after-load isn't limited to programming modes. Here's an example of how you can auto-load your credentials when loading GNUS:

(eval-after-load "gnus"
  '(setq
    nntp-authinfo-file "~/.authinfo"
    gnus-nntp-server *my-nntp-server*))

Summary

So, there you have it:

  • auto-mode-alist allows you to enter specific modes based on file extension
  • autoload allows you to not not load that mode until it's actually required by opening a file of that type
  • eval-after-load and/or (add-to-list {mode}-mode-hook) can be used for mode customisation or setup, again only when the mode actually loads

As much as possible, ensure that your .emacs file makes minimal use of non-deferred (require) or (load) calls, although (require 'cl) is an exception. It's also fair to load code/minor modes that you use all the time, since there's no point in deferring them just to load them the first time you do anything!

Thursday, 8 April 2010

Simple File Renaming in Emacs

I stumbled across this tip while browsing reddit/emacs. In summary, it allows for the possibility of having a 'write file' (C-x C-w) variant that deletes the original file, essentially performing a simple rename without having to invoke dired or fire up a shell.

Putting on my Emacs cap, I came up with something similar, shorter and (debatably) more 'emacs-y':


(defadvice write-file (around write-file-possibly-rename activate)
(let ((old-filename (buffer-file-name)))
ad-do-it
(and current-prefix-arg
old-filename
(not (string-equal old-filename (buffer-file-name)))
(delete-file old-filename))))


defadvice allows you to hook a new function up to an existing one, to be invoked either before, after or around it (around advice invokes the original function with its original arguments with ad-do-it, and can do that zero or more times). This form of advice is what hits the spot for this task. Subsequently, when write-file is called, this advice intercepts it, captures the current filename and then invokes the old behaviour. After the write has happened, it checks to see whether or not it should delete the old file, and does so if the preconditions are met.

This preserves the existing C-x C-w behaviour, creating a completely new copy of your file if you choose a different name. Invoked as C-u C-x C-w, however, it deletes the old filename, performing the same rename with less code and less duplication of the existing file-writing plumbing.