Reputation: 3949
I have the following code in my .emacs
file, which works as you'd expect:
;; Ruby
(global-font-lock-mode 1)
(autoload 'ruby-mode "ruby-mode" "Ruby editing mode." t)
(setq auto-mode-alist (cons '("\\.rb$" . ruby-mode) auto-mode-alist))
(setq auto-mode-alist (cons '("\\.rsel$" . ruby-mode) auto-mode-alist))
(setq auto-mode-alist (cons '("\\.rhtml$" . html-mode) auto-mode-alist))
(setq auto-mode-alist (cons '("\\.erb$" . html-mode) auto-mode-alist))
(setq auto-mode-alist (cons '("\\.prawn$" . html-mode) auto-mode-alist))
(setq auto-mode-alist (cons '("Rakefile$" . ruby-mode) auto-mode-alist))
However, my attempts to DRY it up a bit fail:
(defun set-mode-for-filename-patterns (mode filename-pattern-list)
(mapcar
(lambda (filename-pattern)
(setq
auto-mode-alist
(cons '(filename-pattern . mode) auto-mode-alist)))
filename-pattern-list))
;; Ruby
(global-font-lock-mode 1)
(autoload 'ruby-mode "ruby-mode" "Ruby editing mode." t)
(set-mode-for-filename-patterns
ruby-mode
'("\\.rb$"
"\\.rsel$"
"\\.rhtml$"
"\\.erb$"
"\\.prawn$"
"Rakefile$"
"Gemfile$"))
... with the following error:
Debugger entered--Lisp error: (void-variable ruby-mode)
(set-mode-for-filename-patterns ruby-mode (quote ("\\.rb$" "\\.rsel$" "\\.rhtml$" "\\.erb$" "\\.prawn$" "Rakefile$" "Gemfile$")))
eval-buffer(#<buffer *load*> nil "/home/duncan/.emacs" nil t) ; Reading at buffer position 1768
load-with-code-conversion("/home/duncan/.emacs" "/home/duncan/.emacs" t t)
load("~/.emacs" t t)
#[nil "\205\264
I'm a bit confused here ... in particular, I don't understand how ruby-mode
is void & so can't be passed to a function, but can be cons
ed into a pair?
Any pointers (heh) would be greatly appreciated.
Upvotes: 2
Views: 577
Reputation: 1586
I think set-mode-for-filename-patterns
is an interesting function. I'm going to add it to my config but use a more optimized implementation.
The implementations here all add one item to the auto-mode-alist
variable for each file suffix. Emacs searches this list every time it finds a file. So the shorter the auto-mode-alist
, the faster Emacs will find files.
This version is probably slower at startup but faster when finding files:
(defun set-mode-for-filename-patterns (mode filename-pattern-list)
(push (cons (regexp-opt filename-pattern-list) mode)
auto-mode-alist))`
This will work with the the same call:
(set-mode-for-filename-patterns
'ruby-mode
'("\\.rb$"
"\\.rsel$"
"\\.rhtml$"
"\\.erb$"
"\\.prawn$"
"Rakefile$"
"Gemfile$"))
If you look at the value of auto-mode-alist
you'll see that many of the built-in modes use regexps for the same performance reason.
BTW, I advise that you just trust regexp-opt
to do the right thing. The regexps it makes are pretty hard on the eye (and brain).
Upvotes: 1
Reputation: 10032
In the form:
(cons '("\\.rb$" . ruby-mode) ...
ruby-mode
is part of a quoted list. That means it is read as a symbol name, not evaluated as a variable. In other words, Emacs sees it as the symbol ruby-mode
and accepts it as is.
In the form:
(set-mode-for-filename-patterns
ruby-mode
'("\\.rb$"
"\\.rsel$"
...
ruby-mode
is not quoted, and so is read as the argument to a function. Function arguments are evaluated. Which means Emacs reads ruby-mode
, recognizes it as a symbol, and tries to evaluate it. Evaluating a symbol means looking for the value that it points to, which in this case doesn't exist.
EDIT:
Your function still doesn't work, there's another problem. You've used a quoted list inside set-mode-for-filename-patterns
. This works fine in your original code:
(setq auto-mode-alist (cons '("\\.rb$" . ruby-mode) auto-mode-alist))
as you are in effect manually supplying the value for filename-pattern
and mode
. Inside your function, you need these symbols to be evaluated, which doesn't happen when they're quoted! The result is that instead of adding each different string from your list to auto-mode-alist, you get the symbol filename-pattern
instead.
To fix this, you need to recognize that the '(filename-pattern . mode) is meant to produce a cons cell with the values of filename-pattern
and mode
. Which we can produce with (cons filename-pattern mode). So the corrected function would be:
(defun set-mode-for-filename-patterns (mode filename-pattern-list)
(mapcar
(lambda (filename-pattern)
(setq
auto-mode-alist
(cons (cons filename-pattern mode) auto-mode-alist)))
filename-pattern-list))
And the corrected function call is:
(set-mode-for-filename-patterns
'ruby-mode
'("\\.rb$"
"\\.rsel$"
"\\.rhtml$"
"\\.erb$"
"\\.prawn$"
"Rakefile$"
"Gemfile$"))
Upvotes: 4
Reputation: 5030
Look here:
(setq auto-mode-alist (cons '("\\.rb$" . ruby-mode) auto-mode-alist))
----------------------------^
This is a quote
which means you prevent the evaluation of the next
form, thus '("\\.rb$" . ruby-mode)
is equivalent to (cons '"hello"
'ruby-mode)
.
But when you call the function set-mode-for-filename-patterns
the
arguments are first evaluate then their result is passed to the
function. That's why evaluation (set-mode-for-filename-patterns
ruby-mode ..)
raise an error, because the emacs-lisp interpreter tries
to evaluate ruby-mode
as a variable, but ruby-mode
has no value in
this context hence the error (void-variable ruby-mode)
. What you
want here, is to pass the symbol ruby-mode
so you have to quote it
like this (set-mode-for-filename-patterns 'ruby-mode ...)
Note that you could have set a value to ruby-mode
mode with let
.
(let ((ruby-mode 'ruby-mode))
(set-mode-for-filename-patterns ruby-mode ...))
Here when the argument the form (set-...)
is evaluated it evaluates
ruby-mode
and can find a value for it (which is the symbol
ruby-mode
) and then pass it to the function.
Upvotes: 1