Skip to content

Instantly share code, notes, and snippets.

@eddieh
Last active January 3, 2024 15:36
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eddieh/33aaa48625001992d8584bfdd5008501 to your computer and use it in GitHub Desktop.
Save eddieh/33aaa48625001992d8584bfdd5008501 to your computer and use it in GitHub Desktop.
Develop an Emacs Major Mode

Develop an Emacs Major Mode

Introduction

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.

Define Generic 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.")

Generic Mode for a Simple Configuration File

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.")

Generic Mode for a Simple Language

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.")

Define Derived Mode

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment