Developing an Emacs major mode is perhaps unnecessarily complex, but it offers unmatched flexibility and control.
Emacs does many things without the need for the major mode author to be explicit. This can be infuriating when you just want to hack together a basic mode for a language or config file format you find yourself using that either doesn’t have a major mode or has a poorly implemented unmaintained major mode.
Major mode authors (or Emacs maintainers) tend to be too clever and over complicate simple things like listing a modes keywords. In turn this makes it tricky to look at a builtin major mode to learn and most importantly, imitate.
I think the best approach is to start developing a major mode with
define-generic-mode
and only when that proves insufficient should
one transition to developing a major mode with
define-derived-mode
.
This is the simple, more or less, declarative way to quickly define a major mode. I have never seen it used in the wild. But still it is where to start and may be sufficient. At the very least it will help organize the kinds of things that will be needed for a successful derived major mode.
Major modes defined with define-generic-mode
are short and can go
in your init file. The basic form for a generic mode definition
follows
(define-generic-mode null-mode
() ;; comment list
() ;; keyword list
() ;; font lock list
() ;; auto mode list
() ;; function list
"Documentation string.")
Assume you’re frequently editing a config file for a program that
stores its config file in ~/.simplerc
. The config file has a
syntax of command variable = value
and each entry is on its own
line. A command can be set
, modify
, or unset
. The variable
can be any sequence of characters. The value can be a number, a
string, or true
or false
. Comments are lines beginning with
#
.
An example ~/.simplerc
looks like the following
# Example comment
set OptionOne = 1
set OptionTwo = 2
set AllowExample = true
set Identifier = "com.example.xyz"
# Another comment
modify OptionOne = 33
unset OptionTwo
The generic mode definition for this mode is as simple as
(define-generic-mode simplerc-mode
(list ?#)
(list "set" "modify" "unset")
'(("true" . 'font-lock-constant-face)
("false" . 'font-lock-constant-face))
(list "\\.simplerc$")
nil
"Major mode for simplerc config files.")
Imagine a simple language that contains named blocks and properties/variables.
App {
Prop1 1
Prop2 2
PropStr "a string"
Main {
puts "hello world"
}
}
Then a simple indent function for the language might look like the following
(defun simple-indent-line ()
"Indent current line of Simple code."
(interactive)
(let ((savep (> (current-column) (current-indentation)))
(indent (condition-case nil (max (simple-calculate-indentation) 0)
(error 0))))
(if savep
(save-excursion (indent-line-to indent))
(indent-line-to indent))))
(defun simple-calculate-indentation ()
"Return the column to which the current line should be indented."
(* tab-width (min (car (syntax-ppss (line-beginning-position)))
(car (syntax-ppss (line-end-position))))))
(define-generic-mode simple-mode
() () () ;; comment, keyword, & font lock
(list "\\.simp$") ;; auto load
(list ;; function list
(lambda ()
(setq-local tab-width 4)
(setq-local indent-tabs-mode nil)
(setq-local indent-line-function 'simple-indent-line)))
"Simple mode for editing Simple language code.")