Configuring Emacs

Last updated on
31 October 2023

TL;DR. Just show me a snippet to put in my ~/.emacs or point me to the most complete solution.

Introduction

Emacs is a text editor with its own interpreter making it extensible through a few lines of Emacs Lisp (elisp) in ~/.emacs or using whole extensions/packages.

This guide is for setting up Emacs 24 or later* in a Drupal environment with bias on module development and It is roughly divided in three parts:

(*) Where Emacs 23 differs it will add a note about it.  The guide was written for Emacs 24, but should still work for later versions.

Managing packages in Emacs

All further references to Emacs packages assumes you use Emacs Lisp Package Archive (ELPA) for package management. Some GNU/Linux distributions will provide a subset of these as OS packages, but ELPA is usually more up to date. In addition to the default GNU archive (and default Non-GNU archive from Emacs 28), MELPA contains many popular packages used in some of the Emacs Drupal solutions below, so something in the line of this is should be in your ~/.emacs:

(require 'package)
(add-to-list 'package-archives '("nongnu" . "https://elpa.nongnu.org/nongnu/") t)
(add-to-list 'package-archives '("melpa" . "http://melpa.milkbox.net/packages/") t)
(package-initialize)

Quick usage:

  • Refresh package list: M-x package-refresh-contents.
  • Install a package: M-x package-install RET <package-name>.
  • List available packages: M-x package-list-packages.

For Emacs 23, add package.el from https://github.com/technomancy/package.el to your config (but you should update to a newer version of Emacs if possible; many things will no longer support such an old version).

Major modes

All suggested drupal-modes will in some way extend installed major modes for editing source code. Depending on your development environment, select and install packages for your needs. You should at the very least install a variant of php-mode.

Modes for working with PHP

De facto mode for working with pure PHP code in Emacs is as of today (August, 2014) php-mode maintained by Eric James Michael Ritz. (Though he is currently looking for help and/or a new maintainer).

M-x package-install RET php-mode

Other alternatives include php+-mode, nxhtml-mode, php-mode-improved, though they all are at some point forks of php-mode and is more or less unmaintained.

Modes for working with JavaScript

  • js-mode (Built in). This provides only basic syntax highlighting and indentation.
  • js2-mode maintained by Masafumi Oyamada. Among other improvements over js-mode it has on-the-fly reporting of syntax errors and strict-mode warnings. It also behaves better with chained methods (Think jQuery).
    M-x package-install RET js2-mode
    
  • js3-mode maintained by Thom Blake. It is a merge of js-mode provided by Emacs 24 and js2-mode with some enhancements. (Untested. Anyone used this in a Drupal environment?).
    M-x package-install RET js3-mode
    

Modes for working with templates

If you're a themer and working with templates (mixed content), you might want to have a look at one of the following solutions that lets you combine two or more major modes in the same buffer. Further setup and documentation is available on the package's home page.

  • web-mode. This has support for a whole slew of template engines, including PHP (Drupal 7) and Twig (Drupal 8).
    M-x package-install RET web-mode
    
  • Polymode.
    M-x package-install RET polymode
    

Source code tagging systems

If you haven't worked with source code tags in Emacs, you have been missing out. It is an essential development tool for looking up functions and other symbols in a source tree. In addition it is a source for many sub-systems used in various PHP and Drupal IDE variants:

  • Auto complete candidates.
  • Eldoc function argument list in echo area.
  • Drupal hook templates.

It is therefore highly recommended that you set up one of the following systems in your Drupal projects:

Universal Ctags

https://ctags.io/ is the currently-maintained version of the ctags family (outside of the one included with Emacs).  Use the -e option to make it generate Emacs-compatible output.  Make sure you are running this ctags, rather than the ctags shipped with Emacs.

Emacs tags (etags)

Etags is a part of Emacs and will generate a TAGS file based on all source files provided on the command line argument:

  $ etags <source-file1.php> [source-file2.php …]

To create a TAGS file for a drupal project you can run the following command:

 $ find . -regex ".*\.\(php\|inc\|module\|theme\|install\|engine\)" -print | etags --language=php -

Pros:

  • Been around for many years, is well known and is widely adapted.
  • It's part of Emacs. If Emacs is installed you have Etags.

Cons:

  • It doesn't add function arguments to the TAGS file, so ElDoc implementations have to load the destination file and parse the code at point to retrieve the argument list.
  • It is rather slow during the first lookup with large TAGS files (say, when you have tagged the entire Drupal core and 50 additional contrib modules).
  • You have to specify all files that should be tagged on the command line, and for large projects this means using find and xargs.

GNU Global (gtags)

GNU Global is a modern, generic, editor independent solution for tagging various source code across a whole project. Emacs 23 and forwards supports GNU global with the provided gtags-mode, but there is a more user-friendly ggtags-mode from the package ggtags available in ELPA.

M-x package-install RET ggtags

Read GNU Global setup for Drupal for installation, usage and some quirks specific for Drupal that needs to be addressed.

LSP and language servers

In the Object-Oriented hellscape landscape of modern Drupal/PHP, a TAGS file is no longer enough to navigate the tangle of identically-named methods and "inherited" (missing) documentation. Instead, we must turn to the Language Server Protocol (LSP) to provide a solution. Language Servers use intelligent static analysis of the code to determine what any given piece of code is actually referring to, and can take you to the correct definition and/or show you the appropriate documentation automatically.

Note that it is advantageous to have at least Emacs 27.1 when using LSP due the latter throwing around a lot of JSON data, and the former incorporating the Jansson C library for parsing it efficiently. Ubuntu users running an older OS release than 22.04 (or wanting a newer version of Emacs in general) can install Emacs 27+ from this PPA.

There are two options for adding LSP support to Emacs:

Eglot will be a standard part of Emacs starting from Emacs 29.1, and it integrates more closely with other standard Emacs features, so we will use Eglot for this guide. (GNU ELPA will still have the latest version and updates to Eglot inbetween releases of Emacs itself.)

Whichever option you choose, you will additionally need to install a Language Server for PHP. There are again multiple options, but for this guide we'll use Intelephense.

Intelephense

To install Intelephense in your development environment you should refer to the documentation; but at the time of writing it has a dependency on Node.js version 12 or higher (with the latest Node.js release being 16.x). Some operating systems still supply much older versions in their OS package archives however, so you may need to install Node.js manually. On GNU/Linux systems you can simply download and unpack the binary release archive, and then either symlink the executables in its top-level bin directory to a bin directory in your PATH, or else add that directory to your PATH. You can then run the npm i intelephense -g command, which (assuming success) will leave a new intelephense executable in that same bin directory, so make sure that this too is available in your PATH.

Now that the language server is installed, you can configure Emacs to use it.

Eglot

Eglot uses LSP to activate modern IDE features in Emacs. These features are provided via a number of auxiliary packages:

  • at-point documentation, via the ElDoc package;
  • on-the-fly diagnostic annotations with server-suggested fixes, via the Flymake package;
  • definition chasing/cross-referencing, via the Xref package;
  • in-file symbolic navigation, via the Imenu package;
  • completion (via Emacs built-in front-ends, Company, or other front-ends)

To install Eglot:

  • M-x package-refresh-contents RET
  • M-x list-packages RET
  • / n eglot RET
  • i to mark the latest version for installation
  • x to install

To configure Eglot to use Intelephense, add the following to your init file:

(with-eval-after-load "eglot"
  (add-to-list 'eglot-server-programs '(php-mode "intelephense" "--stdio")))

You can now run M-x eglot in a PHP buffer. To make this happen automatically, use the following:

(add-hook 'php-mode-hook 'eglot-ensure)

User interface

To jump to the definition of the thing at point, call xref-find-definitions. In recent Emacs releases this command is bound to M-. by default.

You will quickly notice that Eglot uses eldoc to display the help for classes and methods after a short delay, and that the default behaviour is very chatty and potentially distracting.

To switch to an on-demand floating view of the help, you can use the eldoc-box package from MELPA and bind a key to eldoc-box-eglot-help-at-point (using the F5 function key in this example), and then tell Eglot not to proactively use eldoc, updating your eval-after-load form like so:

(with-eval-after-load "eglot"
  (add-to-list 'eglot-server-programs '(php-mode "intelephense" "--stdio"))
  (add-to-list 'eglot-stay-out-of 'eldoc)
  (with-eval-after-load "php-mode"
    (define-key php-mode-map (kbd "<f5>") #'eldoc-box-eglot-help-at-point)))

Refer to the Eglot manual for other information about its additional features.

Docker containers and/or Tramp

Eglot (v1.8 or later) works over Tramp, and Tramp can be used to access files in docker containers, so in conjunction you can still use LSP in Emacs even if your development environment runs in a container or otherwise remotely. (You will still need to install Node.js and Intelephense inside that container.)

Starting from Emacs 29.1 (or potentially already in the GNU ELPA version when you read this), Tramp will support a docker method by default. If not, then you can install the docker-tramp package from MELPA. See https://www.emacswiki.org/emacs/TrampAndDocker and https://www.gnu.org/software/tramp/#Customizing-Methods for more information.

With a docker Tramp method available, and the language server installed inside the container, you can access your PHP files with /docker:username@containername:/path/to/file.php and from there M-x eglot should Just Work.

Useful extensions

Though not necessary, these extensions have proven themselves useful while working with code for Drupal in Emacs. Setup and documentation is available on the packages home pages, or it is documented on the individual Drupal mode solutions.

  • Auto complete. Collects symbols and functions from various sources and provides possible completions in a popup menu.
    M-x package-install RET auto-complete
    
  • Flymake1 or Flycheck2. These tools in combination with PHP Codesniffer and the sniffers available in Coder module and DrupalPractice will give you on-the-fly reporting about syntax errors and Drupal coding style errors. This is an incredible nice thing to work with. Drupal compliant code right out of the box.

1 There is a Flymake package as part of Emacs 23, but this is considered outdated.
2 Only available for Emacs 24 and later.

Drupal modes

These solutions are ordered by "IDE-ness" with the first being the most basic, only containing a few lines of elisp that indents code according to Drupal's coding style, and the last being the most complete with auto-completion and function argument listing, code navigation, real-time syntax and coding style checking, hook templates and more.

Miscellaneous elisp

M-x sort-lines-case-insensitive

The Drupal testbot currently requires use statements to be sorted case-insensitively, which almost certainly isn't your default for the sort-fold-case user option.  Instead of toggling that, you can use M-x sort-lines-case-insensitive:

(defun sort-lines-case-insensitive (reverse beg end)
  "Call `sort-lines' with `sort-fold-case' non-nil."
  (interactive "P\nr")
  (let ((sort-fold-case t))
    (sort-lines reverse beg end)))

(defun sort-lines-case-sensitive (reverse beg end)
  "Call `sort-lines' with `sort-fold-case' nil."
  (interactive "P\nr")
  (let ((sort-fold-case nil))
    (sort-lines reverse beg end)))

Help improve this page

Page status: No known problems

You can: