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?

Wilfred Hughes
  • 6,890
  • 2
  • 29
  • 59
  • 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. –  Oct 09 '14 at 16:24
  • I'm contributing to julia-mode, and some of the core team use older Emacsen and would prefer we supported it. – Wilfred Hughes Oct 09 '14 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. – Gilles 'SO- stop being evil' Oct 09 '14 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. – Lindydancer Oct 13 '14 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… –  Oct 13 '14 at 19:53
  • lunaryorn: Sure, but ELPA was third-party functionality for Emacs 23, and a great many people would never have encountered that library. I suspect your observation only indicates that more people are using packages with the major version of Emacs which supports them out-of-the-box. – phils Oct 13 '14 at 23:59

4 Answers4

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.

sanityinc
  • 2,871
  • 13
  • 17
  • 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. – Jorgen Schäfer Oct 09 '14 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 '14 at 07: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. :-))

Jorgen Schäfer
  • 3,899
  • 2
  • 17
  • 19
  • 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? – Wilfred Hughes Oct 09 '14 at 09: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. – Kaushal Modi Oct 09 '14 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. – Jorgen Schäfer Oct 09 '14 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. – Jorgen Schäfer Oct 09 '14 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 09 '14 at 14:58
  • Nice solution, @sanityinc! Thanks, I learned something today :-) – Jorgen Schäfer Oct 09 '14 at 20:47
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.)

0

I think testing using fboundp makes more sense.

(if (fboundp 'prog-mode)
    ...
   ...)
Alex Schröder
  • 356
  • 1
  • 4