pre-commit-elisp – Pre-commit hooks for Emacs Lisp (Elisp) Git repositories

The pre-commit-elisp repository offers pre-commit hooks for Emacs Lisp (Elisp) projects. These hooks enforce code quality and consistency by performing automated checks on .el files prior to committing changes:

  • elisp-check-parens: Validates that all parentheses in .el files are correctly balanced.
  • elisp-check-byte-compile: Byte-compile Elisp files to detect compilation errors.
  • elisp-indent: Indent Elisp files according to Emacs Lisp style conventions.

These pre-commit hooks enforce syntactic correctness, successful byte-compilation, and consistent code formatting, ensuring a high standard of code quality and maintainability throughout the repository.

If this enhances your workflow, please show your support by ⭐ starring pre-commit-elisp on GitHub to help more Emacs users discover its benefits.

Installation

  1. Install pre-commit.

  2. Add this repository as a local hook in your .pre-commit-config.yaml:

---

repos:
  - repo: https://github.com/jamescherti/pre-commit-elisp
    rev: v1.0.5
    hooks:
      # Validate that all parentheses in .el files are correctly balanced
      - id: elisp-check-parens

      # Optional: Byte-compile .el files to identify compilation errors early
      # - id: elisp-check-byte-compile

      # Optional: Indent Elisp files according to Emacs Lisp style conventions
      # - id: elisp-indent
  1. Install the hooks in your project:
pre-commit install
  1. Run hooks manually on all files (optional):
pre-commit run --all-files

Customizations

Customizing load-path

Scripts such as elisp-check-byte-compile and elisp-byte-compile support customizing the load-path variable using a .dir-locals.el variable pre-commit-elisp-load-path. This variable allows specifying the directories that should be included in the load-path without modifying the scripts themselves, ensuring that dependencies and libraries located in the project or its subdirectories are correctly available for byte-compilation.

Customizing the load-path allows the byte-compilation scripts, such as elisp-check-byte-compile, to find and load project-specific Emacs Lisp files during compilation.

Here is an example of a .dir-locals.el file to place at the root of the Git repository:

((nil . ((pre-commit-elisp-load-path . ("." "lib/" "utils")))))

The pre-commit-elisp-load-path list is a list of directories relative to the Git repository root or project directory.

Each entry in the list determines how it is added to load-path:

  1. Directory ends with a slash (/): Recursively adds the directory and all its subdirectories to load-path. Example: "lib/" adds lib/ and all its subdirectories.

  2. Directory does not end with a slash: The directory is added non-recursively. Example: "utils" adds only the utils directory, not its subdirectories.

License

The pre-commit-elisp hooks have been written by James Cherti and is distributed under terms of the GNU General Public License version 3, or, at your choice, any later version.

Copyright (C) 2025 James Cherti

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program.

Links

Other Emacs packages by the same author:

  • minimal-emacs.d: This repository hosts a minimal Emacs configuration designed to serve as a foundation for your vanilla Emacs setup and provide a solid base for an enhanced Emacs experience.
  • compile-angel.el: Speed up Emacs! This package guarantees that all .el files are both byte-compiled and native-compiled, which significantly speeds up Emacs.
  • outline-indent.el: An Emacs package that provides a minor mode that enables code folding and outlining based on indentation levels for various indentation-based text files, such as YAML, Python, and other indented text files.
  • vim-tab-bar.el: Make the Emacs tab-bar Look Like Vim’s Tab Bar.
  • elispcomp: A command line tool that allows compiling Elisp code directly from the terminal or from a shell script. It facilitates the generation of optimized .elc (byte-compiled) and .eln (native-compiled) files.
  • tomorrow-night-deepblue-theme.el: The Tomorrow Night Deepblue Emacs theme is a beautiful deep blue variant of the Tomorrow Night theme, which is renowned for its elegant color palette that is pleasing to the eyes. It features a deep blue background color that creates a calming atmosphere. The theme is also a great choice for those who miss the blue themes that were trendy a few years ago.
  • Ultyas: A command-line tool designed to simplify the process of converting code snippets from UltiSnips to YASnippet format.
  • dir-config.el: Automatically find and evaluate .dir-config.el Elisp files to configure directory-specific settings.
  • flymake-bashate.el: A package that provides a Flymake backend for the bashate Bash script style checker.
  • flymake-ansible-lint.el: An Emacs package that offers a Flymake backend for ansible-lint.
  • inhibit-mouse.el: A package that disables mouse input in Emacs, offering a simpler and faster alternative to the disable-mouse package.
  • quick-sdcv.el: This package enables Emacs to function as an offline dictionary by using the sdcv command-line tool directly within Emacs.
  • enhanced-evil-paredit.el: An Emacs package that prevents parenthesis imbalance when using evil-mode with paredit. It intercepts evil-mode commands such as delete, change, and paste, blocking their execution if they would break the parenthetical structure.
  • stripspace.el: Ensure Emacs Automatically removes trailing whitespace before saving a buffer, with an option to preserve the cursor column.
  • persist-text-scale.el: Ensure that all adjustments made with text-scale-increase and text-scale-decrease are persisted and restored across sessions.
  • pathaction.el: Execute the pathaction command-line tool from Emacs. The pathaction command-line tool enables the execution of specific commands on targeted files or directories. Its key advantage lies in its flexibility, allowing users to handle various types of files simply by passing the file or directory as an argument to the pathaction tool. The tool uses a .pathaction.yaml rule-set file to determine which command to execute. Additionally, Jinja2 templating can be employed in the rule-set file to further customize the commands.

Emacs: Highlighting Codetags Like TODO, FIXME, BUG, NOTE…

Highlighting keywords such as TODO, FIXME, NOTE, BUG, and others (often referred to as tags, codetags, or tokens) enhances workflow by making key annotations more visible. This allows developers to quickly identify tasks, warnings, and notes within the code, reducing the time spent searching for unfinished work or potential issues.

This article outlines an Elisp code that highlights these codetags.

(There are packages like hl-todo and comment-tags that can highlight these codetags for those who need a more feature-rich solution. However, they contain hundreds of lines of code, which is excessive if your only goal is to just highlight codetags. While these packages likely offer additional features, such as navigating to the next codetag, the Elisp code in this article provides a much simpler solution for those who just want to highlight them.)

Elisp code to highlight codetags

To highlight these codetags, you can use the following Emacs Lisp code:

(defvar highlight-codetags-keywords
  '(("\\<\\(TODO\\|FIXME\\|BUG\\|XXX\\)\\>" 1 font-lock-warning-face prepend)
    ("\\<\\(NOTE\\|HACK\\)\\>" 1 font-lock-doc-face prepend)))

(define-minor-mode highlight-codetags-local-mode
  "Highlight codetags like TODO, FIXME..."
  :global nil
  (if highlight-codetags-local-mode
      (font-lock-add-keywords nil highlight-codetags-keywords)
    (font-lock-remove-keywords nil highlight-codetags-keywords))

  ;; Fontify the current buffer
  (when (bound-and-true-p font-lock-mode)
    (if (fboundp 'font-lock-flush)
        (font-lock-flush)
      (with-no-warnings (font-lock-fontify-buffer)))))Code language: Lisp (lisp)

To apply codetag highlighting across all programming modes, add highlight-codetags-local-mode to the prog-mode-hook:

(add-hook 'prog-mode-hook #'highlight-codetags-local-mode)Code language: Lisp (lisp)

If you call highlight-codetags-local-mode interactively, you can toggle the highlighting of codetags on and off.

Customizations

If desired (though not required), you can further customize the Elisp code:

  • You can customize the highlighting by substituting font-lock-warning-face or font-lock-doc-face with any other face of your choice. (You can view all available faces by executing the command: M-x list-faces-display)
  • Additionally, you can add more keywords to the regular expression.
    For instance, to add the MAYBE codetag to the \\<\\(NOTE\\|HACK\\)\\> pattern, simply append \\|MAYBE before the closing parenthesis \\):
    \\<\\(NOTE\\|HACK\\|MAYBE\\)>.

Conslusion

This simple configuration enhances keyword visibility in Emacs, making it easier to track important annotations while editing source code.

Emacs: Open a Vertico/Consult or Ivy/Counsel candidate in a new tab

In this article, you will find code snippets designed to simplify the task of opening Vertico/Consult/Embark or Ivy/Counsel candidates in a new Emacs tab using tab-bar.

The generic function that opens candidates in a new tab

This function below, tab-new-func-buffer-from-other-window, is designed to open the buffer generated by a specified function (func) in the other window and subsequently create a new tab. It also ensures that the state of the original window and tab is preserved.

;; License: MIT
;; Author: James Cherti
;; URL: https://www.jamescherti.com/emacs-open-vertico-consult-ivy-counsel-candidate-new-tab/

(defun tab-new-func-buffer-from-other-window (func)
  "Open the buffer created by the FUNC function in the other window in a new tab."
  (let* ((original-tab-index (1+ (tab-bar--current-tab-index)))
         (original-window (selected-window)))
    ;; Save the state of the other window
    (other-window 1)
    (let* ((other-window (selected-window))
           (other-window-buf (current-buffer))
           (other-window-point (point))
           (other-window-view (window-start)))
      ;; Move back to the original window
      (other-window -1)

      ;; Call the specified function (e.g., embark-dwim, ivy-call...)
      (funcall func)

      ;; Switch back to the other window
      (other-window 1)
      (unless (eq (selected-window) original-window)
        (let* ((preview-buf (current-buffer)))
          ;; Create a new tab and switch to the preview buffer
          (tab-bar-new-tab)
          (switch-to-buffer preview-buf)

          ;; Go back to the original tab
          (tab-bar-select-tab original-tab-index)

          ;; Restore the state of the other window
          (select-window other-window)
          (switch-to-buffer other-window-buf)
          (goto-char other-window-point)
          (set-window-start nil other-window-view)

          ;; Switch to the original window
          (select-window original-window))))))Code language: Lisp (lisp)

Option 1: Open Vertico, Consult, and Embark candidate in a new tab (embark-dwim)

For users of Vertico/Consult/Embark, the following function utilizes the generic function to open the default Embark action buffer in a new tab:

(defun tab-new-embark-dwim ()
  "Open embark-dwim in a new tab."
  (interactive)
  (tab-new-func-buffer-from-other-window #'embark-dwim))Code language: Lisp (lisp)

You can add the following key mapping to Vertico:

(keymap-set vertico-map "C-t" #'tab-new-embark-dwim)Code language: Lisp (lisp)

For Emacs Evil users, you can also add the following mappings:

(evil-define-key '(insert normal) vertico-map (kbd "C-t") 'tab-new-embark-dwim)
Code language: Lisp (lisp)

Option 2: Open Ivy, Counsel candidates in a new tab (ivy-call)

For users of Counsel/Ivy, the following function utilizes the generic function above to open the buffer created by ivy-call in a new tab:

(defun tab-new-ivy-call ()
  "Open ivy-call in a new tab."
  (interactive)
  (tab-new-func-buffer-from-other-window #'ivy-call))Code language: Lisp (lisp)

You can add the following key mapping to ivy-minibuffer-map:

(keymap-set ivy-minibuffer-map "C-t" #'tab-new-ivy-call)Code language: Lisp (lisp)

For Emacs Evil users, you can also add the following mappings:

(evil-define-key '(insert normal) ivy-minibuffer-map (kbd "C-t") 'tab-new-ivy-call)Code language: Lisp (lisp)

Emacs .dir-locals.el – Add project path to $PYTHONPATH (Python Development in Emacs)

In order to ensure that the processes executed by Emacs and its packages, such as Flycheck or Flymake, can access the Python modules of a project, it is essential to correctly configure the $PYTHONPATH environment variable.

This article provides a solution by introducing a .dir-locals.el file that adds the directory path of .dir-locals.el to the $PYTHONPATH environment variable.

The .dir-locals.el file should be placed in the root directory of a Python project.

File name: .dir-locals.el

;; -*- mode: emacs-lisp; -*-
;; File: .dir-locals.el
;; Description:
;; This file adds the path where `.dir-locals.el` is located to the
;; `$PYTHONPATH` environment variable to ensure that processes executed by
;; Emacs and its packages, such as Flycheck or Flymake, can access the Python
;; modules of a project.
;;
;; Author: James Cherti
;; License: MIT
;; URL: https://www.jamescherti.com/emacs-dir-locals-add-path-to-pythonpath/

((python-mode . ((eval . (progn
                           (let ((project_path
                                  (car (dir-locals-find-file
                                        (buffer-file-name))))
                                 (python_path_env (getenv "PYTHONPATH")))
                             (setq-local process-environment
                                         (cons
                                          (concat "PYTHONPATH="
                                                  project_path
                                                  (if python_path_env
                                                      (concat ":" python_path_env)
                                                    ""))
                                          process-environment))))))))
Code language: Lisp (lisp)