10

I'm writing a major mode for a programming language, but I want to support older Emacs versions. prog-mode is relatively new. I want to inherit from prog-mode if it's defined, but still do something sensible otherwise.

What's the best approach? Should I defalias prog-mode on older Emacsen, or will that interfere with other modes if they do the same thing?

6
  • I'd advice to simply drop support for Emacs < 24. In my opinion it is not worth the effort anymore, and you'll have to waive more important features than prog-mode. Notably, you'll suffer from the lack of lexical binding.
    – user227
    Oct 9, 2014 at 16:24
  • I'm contributing to julia-mode, and some of the core team use older Emacsen and would prefer we supported it. Oct 9, 2014 at 17:24
  • 1
    @lunaryorn Emacs 24 is still pretty new. Emacs 23 is the current version on many OSes. Emacs 22 is still current on a few. Not everyone upgrades their software like crazy. Dropping support for Emacs 23 would limit you to the few users who crave the bleeding edge. Oct 9, 2014 at 23:00
  • 1
    There are many reasons to use older Emacs versions. For example, on Windows, Emacs 23 became really sluggish, so I have opted to stick to Emacs 22 there. Oct 13, 2014 at 15:18
  • @Gilles I doubt that it's just a "few users". Flycheck never supported Emacs 23 in the first place, and became one of the most popular packages on MELPA nonetheless…
    – user227
    Oct 13, 2014 at 19:53

4 Answers 4

11

At the cost of an extra top-level symbol binding, there's a very neat solution which avoids repeating the define-derived-mode form:

(defalias 'my-fancy-parent-mode
  (if (fboundp 'prog-mode) 'prog-mode 'fundamental-mode))

(define-derived-mode my-fancy-mode my-fancy-parent-mode
   ...)

Works fine in any Emacs >= 23. I came up with this for haml-mode a couple of years ago IIRC, and it seems to have spread from there to several other major modes. The main thing the define-derived-mode macro does with the parent mode symbol is generate code which calls its function: in this sense, defalias makes the new variable exactly equivalent to the aliased function.

One caveat is that this can confuse derived-mode-p, so code which checks to see if your mode is derived from prog-mode might not work correctly. In practice I haven't encountered any problems: it's more usual for such code to hook into prog-mode-hook, which still gets run.

(As Jorgen points out in the comments, define-derived-mode also uses the mode-class property from the parent mode symbol, and defalias will not copy it. At the time of writing, this property only seems to be used for special-mode.)

Update: these days I'd simply suggest requiring at least Emacs 24, since older versions are long obsolete.

2
  • 2
    Nice solution! Just a caveat: This works for prog-mode, but will not work for every mode. define-derived-mode copies the mode-class symbol property to the child mode. The defalias will not transfer this property. If mode-class is relevant to your use case, you need to copy/set it manually. Oct 9, 2014 at 20:46
  • Thanks for catching that, Jorgen -- I'll have to dig around and learn more about what the mode-class property denotes.
    – sanityinc
    Oct 10, 2014 at 7:20
3

tl;dr: Use if and your own init function:

(if (fboundp 'prog-mode)
    (define-derived-mode your-cool-mode prog-mode "Cool"
      "Docstring"
      (your-cool--init))
  (define-derived-mode your-cool-mode nil "Cool"
    "Docstring"
    (your-cool--init)))

Then do all the mode's initialization in your-cool-init.

Longer explanation:

The problem is that the official way of writing a derived major mode is to use the define-derived-mode macro:

(define-derived-mode your-cool-mode prog-mode ...)

On older Emacsen (pre-24), this breaks when prog-mode. And you can't use (if (fboundp 'prog-mode) ...) there because the macro expects a literal symbol, and will quote it for you in the expansion.

define-derived-mode uses the parent in a multitude of ways. You'd need to copy all of those in your own mode definition to make use of them, and that's both tedious and error-prone.

So the only way is to use two different define-derived-mode statements, depending on whether prog-mode exists or not. That leaves you with the problem of writing your initialization code twice. Which is of course bad, so you extract that into its own function, as described above.

(The best solution is of course to drop support for 23.x and use lexical scoping. But I guess you already considered and dropped that option. :-))

6
  • What's the closest equivalent to prog-mode in older Emacsen? Would it make sense to derive from text-mode or fundamental-mode if prog-mode isn't available? Oct 9, 2014 at 9:55
  • @Jorgen Or can we derive an intermediate mode using fboundp first, with just the define-derived-mode statement? Then the actual mode with full definition can be derived from that intermediate mode? That way the whole mode doesn't have to be defined twice. Oct 9, 2014 at 10:21
  • 1
    @WilfredHughes, there is none. Deriving from fundamental-mode is equivalent to deriving from nil (and indeed, define-derived-mode replaces fundamental-mode with nil), while text-mode is not appropriate, as program code is not text. Most of the default settings in text-mode do not make sense in programming modes outside of comments. This is why prog-mode was introduced in Emacs 24. Oct 9, 2014 at 11:00
  • @kaushalmodi, you could derive an intermediate mode, but that would still require two define-derived-mode definitions in an if form, just for the intermediate mode instead of the final mode. You would be replacing the defun for the init function with a define-derived-mode for the final mode. I do not think this is particularly preferable. You could also define a prog-mode yourself, as the original question suggests, but that can easily confuse other modes who rely on fboundp to check for the presence of that mode. Oct 9, 2014 at 11:05
  • I don't believe that two different define-derived-mode statements are necessary. A couple of years ago I came up with the solution I've posted as a separate answer, and it seems to work fine in both Emacs 23 & 24. Code like it is used in a number of popular major modes.
    – sanityinc
    Oct 9, 2014 at 14:58
0

I think testing using fboundp makes more sense.

(if (fboundp 'prog-mode)
    ...
   ...)
0

You can define a wrapper macro for define-derived-mode that evaluates its arguments.

(defmacro define-derived-mode* (child parent name &optional docstring &rest body)
  (macroexpand `(define-derived-mode ,(eval child) ,(eval parent) ,(eval name)
                                     ,(eval docstring) . ,body)))
(define-derived-mode* 'toy-mode
  (if (fboundp 'prog-mode) 'prog-mode 'fundamental-mode)
  "Toy"
  "Major mode for my favorite toy language"
  (toy-mode-setup))

(Warning: only minimally tested.)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.