56

I have this by default in my auto-mode-alist:

("\\.js\\'" . javascript-mode)

(even with emacs -Q). I'd like to substitute js2-mode for javascript-mode. Of course, I could use assq-delete-all and then add-to-list again, but I'm wondering whether there isn't a better way.

Edit: I explicitly do not want to use Customize, I prefer crafting my init.el myself.

Dan
  • 32,584
  • 6
  • 98
  • 168
mbork
  • 1,647
  • 1
  • 13
  • 23

6 Answers6

56

While @Dan's answer is a perfectly fine solution, it is unnecessary. One of the reasons Emacs uses an alist here is that with an alist you can simply add a new element to the front of the list and it will shadow matches further down the list.

(add-to-list 'auto-mode-alist '("\\.js\\'" . js2-mode))
Drew
  • 75,699
  • 9
  • 109
  • 225
  • 10
    Admittedly, it does not answer the question of how to **replace**. Its point is that you **do not need** to replace (unless you have some other need than what you described). – Drew Nov 11 '14 at 22:19
  • 1
    Exactly. Still, in my book this counts as a solution (even if I do not like it). – mbork Nov 11 '14 at 22:27
  • 1
    +1. It's the less-convoluted solution for working with this alist than the `setf` option, but the latter gets to the question of how to *replace* elements rather than shadow them. – Dan Nov 11 '14 at 23:12
  • 2
    the question wasn't *really* about replacing in the technical sense but rather changing in the higher-level sense. – Erik Kaplun Nov 12 '14 at 00:43
  • 2
    This will always work no matter if the cons is in pure space or will be removed in some future version. – politza Nov 30 '16 at 20:52
53

Use setf to change the value in place:

(setf (cdr (rassoc 'javascript-mode auto-mode-alist)) 'js2-mode)

If you want to replace a value in the list, then setf is the generalized machinery you need to do so. For the more idiomatic way to deal with the auto-mode-alist, see @Drew's answer (and his explanation of shadowing).

Dan
  • 32,584
  • 6
  • 98
  • 168
  • 10
    @mbork You might enjoy this classic explanation to the Perl guys. http://lists.warhead.org.uk/pipermail/iwe/2005-July/000130.html – purple_arrows Nov 11 '14 at 22:43
26

The fastest way to actually change the cons cell is probably setcdr

setcdr is a built-in function in `C source code'.

(setcdr CELL NEWCDR)

Set the cdr of CELL to be NEWCDR.  Returns NEWCDR.

It's worth noting that setf isn't available in older Emacsen, but setcdr is.


*** Welcome to IELM ***  Type (describe-mode) for help.
ELISP> (setq tmp '((one . 1) (two . 2) (three . 4)))
((one . 1)
 (two . 2)
 (three . 4))

ELISP> (setcdr (assq 'three tmp) 3)
3 (#o3, #x3, ?\C-c)
ELISP> tmp
((one . 1)
 (two . 2)
 (three . 3))
Sean Allred
  • 6,861
  • 16
  • 85
  • Do you happen to know which version of Emacs added `setf`? – dshepherd Nov 06 '18 at 17:47
  • 1
    @dsheperd not off-hand, no. Why do you need to know? I would say any emacs that should be targeted for new development will have setf, but there might not be handling for it for the kind of data you want to set. They're called [generalized variables](https://www.gnu.org/software/emacs/manual/html_node/elisp/Setting-Generalized-Variables.html). – Sean Allred Nov 06 '18 at 20:10
  • I wanted to know if it was ok to use setf in some new code, but as you said it turns out there was no generalised variable for what I wanted until too recent a version anyway. – dshepherd Nov 07 '18 at 08:40
12

The OP asks for a solution which handles alists that have string keys. To handle that, see this question. If by chance you only need to handle alists with symbol keys, then as of Emacs 25 you can use:

(setf (alist-get <key> <alist>) <value>)

to replace a cdr. If you have access to Emacs 26, this technique does work with string keys, as follows:

(setf (alist-get "\\.js\\'" auto-mode-alist nil nil #'equal) 'js2-mode)

Note that there are also other ways in Emacs 26 to handle string keys; see this question as mentioned above.

Resigned June 2023
  • 1,502
  • 15
  • 20
  • `(setf (alist-get "\\.js\\'" auto-mode-alist nil nil #'equal) 'js2-mode)` should work (requires Emacs 26 though). – npostavs Feb 03 '18 at 13:45
  • @RadonRosborough: there is an edit feature. Consider to fix you answer. – antonio Feb 27 '18 at 21:41
  • You are using `alist-get` with the string `"\\.js\\'"`, but `alist-get` is based on `assq`, so it won't work with a string as you claim in your answer. – antonio Mar 02 '18 at 05:22
  • @antonio Oh yes, you are entirely correct. I had not realized that the question as posted actually does require a solution which handles string keys. I'll make the edit, thanks! – Resigned June 2023 Mar 02 '18 at 19:34
4

If you know you won't use javascript-mode ever again let auto-mode-alist untouched and add to your init.el

  (defalias 'javascript-mode 'js2-mode "Some handy explanation goes here.")
Matthias
  • 745
  • 3
  • 14
  • 2
    Actually, there is no `javascript-mode`, really: `javascript-mode` is only an alias for `js-mode` (by default) and it was done this way *specifically* so that users could do like you suggest if they prefer `js2-mode` (without losing the ability to use `js-mode` if they want to). – Stefan May 17 '17 at 13:14
  • I derived my answer from habit of aliasing for cperl-mode and nxml-mode. So what would do the trick here? (defalias 'js-mode 'js2-mode)? – Matthias May 17 '17 at 13:22
  • 1
    You misunderstood me. I'm saying that your answer is exactly right and doesn't prevent you from using "javascript-mode" since what you call by that name is really `js-mode` (contrary to what happens for `perl-mode`, for instance). – Stefan May 17 '17 at 13:25
  • Got it... (casual javascript-mode user here) – Matthias May 17 '17 at 13:29
1

With the new seq.el library you can filter the alist.

E.g. we can seq-filter the auto-mode-alist. We return nil for the entries we wish to remove from the alist and t for the entries we want to stay.

(seq-filter
 (lambda (x)
   (if (equal (car x) "\\.js\\'")
       nil
     t))
auto-mode-alist)

Then you set the auto-mode-alist value to this new filtered alist.

(setq auto-mode-alist (seq-filter
 (lambda (x)
   (if (equal (car x) "\\.js\\'")
       nil
     t)) auto-mode-alist))

Edit: Drew simplified the predicate in the comments:

(setq auto-mode-alist 
  (seq-filter
    (lambda (x)
      (not (equal (car x) "\\.js\\'"))) 
    auto-mode-alist))
wos
  • 11
  • 2