Elisp is an interpreted language. You can put version-specific code in your .emacs
, but protect it by testing at load time that it's operating on the correct version.
(if (is-new-feature-available)
(shiny-new-feature)
(old-less-nifty-feature))
This code will work in all versions because (shiny-new-feature)
is only evaluated when (is-new-feature-available)
returns true. Much of this answer is devoted to how to implement (is-new-feature-available)
.
Coping with different sets of features
It's better to test whether a feature is available than to test the Emacs version. Sometimes the feature may be available as an optional package. If you want to run code in XEmacs or other Emacs variant, it may have acquired the same features in different versions. Use the function boundp
to test if a variable is available, and fboundp
to test if a function is available.
For example, the following snippet binds a key to toggle visual-line-mode
if available, and longlines-mode
otherwise.
(global-set-key "\eml" (if (fboundp 'visual-line-mode)
'visual-line-mode
'longlines-mode))
Sometimes, rather than test for the feature, it's easier to run a small piece of code and ignore any errors due to undefined functions, invalid arguments, etc. Don't do this for large amounts of code, as this will make your code very hard to debug.
For example, I don't want to see a toolbar. Older versions of Emacs didn't have them at all. GNU Emacs and XEmacs added that feature in different ways and made it the default. Here's how I turn them off. The set-specifier
function is specific to XEmacs, and default-toolbar-visible-p
is specific to recent enough versions of Emacs; using condition-case
takes care of both requirements. GNU Emacs provides a dedicated function so I merely test if that function is available.
;; For XEmacs
(condition-case nil
(set-specifier default-toolbar-visible-p nil)
(error nil))
;; For GNU Emacs
(if (fboundp 'tool-bar-mode)
(tool-bar-mode 0))
Some face names change over versions. Use facep
to test the availability of a face name.
(let ((face (if (facep 'mode-line) 'mode-line 'modeline)))
(set-face-background face …))
Sometimes you may want to load a nice package if present, and do nothing if the package isn't available. require
has an optional argument for that.
(require 'tex-site nil t) ;; Load AUCTeX if available
This argument was introduced in GNU Emacs 20.4 and isn't available in XEmacs, so if you want to go that far back, you'll have to either wrap it in condition-case
or use load
instead (which doesn't check for already-loaded libraries).
Limit the version dependencies to user-level features. Don't use newer programming features that aren't available in all the versions you want to support: you'll have to provide a compatibility version for older versions, and it's easier to maintain a single version.
Sometimes you do need a feature in many places, and it's available on all the implementations you care about, but in a different way. This is mostly the case if you want to support both XEmacs and GNU Emacs: they had a frustrating tendency of copying each other's features but not their interface. In this case, defining a compatibility function is more convenient than testing at the point of use.
For example, the following code defines a function that returns the window system of the current frame, the modern GNU way, the modern XEmacs way, and the old-style way when you couldn't combine terminal and GUI frames in the same instance.
(defalias 'compat-window-system
(cond
((fboundp 'window-system) #'window-system)
((fboundp 'device-type)
(lambda (&optional frame)
(device-type (frame-device frame))))
(t
(lambda (&optional frame) window-system))))
Environment dependencies
There isn't much code that needs to be platform-dependent. The variable system-type
indicates the operating system. I use it exclusively to activate a few hacks for ms-dos
(yes, my files are that old) and windows-nt
.
You may want to add directories to your executable search path (PATH
), but that's usually best done outside Emacs, in your .profile
for Unix-like system and through the control panel in Windows. To test whether an external program is available, call executable-find
.
For code that needs to act differently depending on the type of GUI if any, check window-type
or its successors (see above).
Initialization files
For maximum compatibility, put your code in ~/.emacs
. GNU Emacs started looking in ~/emacs.d
in version 22. XEmacs started looking in ~/.xemacs
in version 21.4. An alternative approach is to put compatibility code in ~/.emacs
and finish by loading your main file. Put (setq load-home-init-file t)
somewhere to avoid having recent¹ versions of XEmacs ask you whether you want to move your .emacs
to the XEmacs-only location.
Different versions of Emacs may have different, and incompatible, expansion for some macros. So don't share your byte-compiled files between versions, compile the files on each machine.
Sometimes a feature is deprecated, but you still want to use it because that's all there is in some other version that you want to support. The byte compiler warnings come from the byte-obsolete-variable
property.
(cond
((not (boundp 'desktop-enable))
(defvaralias 'desktop-enable 'desktop-save-mode))
((get 'desktop-enable 'byte-obsolete-variable)
(put 'desktop-enable 'byte-obsolete-variable nil)))
¹ Relatively speaking, compared with older XEmacs.
window-system
, etc. can be reasonably answered here.forward-char
and you want the question changed to ask aboutscroll-up
instead? If the OP wants Emacs 22+ compatibility, leave it alone. And no, the question posed is not only about OS X. And yes, there are still lots of people using older Emacs versions (some even older than 22, FWIW).