When working with markdown files in Emacs (e.g., README.md
), users may need to manually update the table of contents. Automating this process saves time and ensures that the table of contents remains consistent with the document structure. This article presents an Emacs Lisp code snippet that uses:
- The
markdown-toc
package to automatically generate or refresh a table of contents, - A custom function (
my-markdown-toc-gen-if-present
) that runs before markdown files are saved. It performs the following actions:- Updates the table of contents if one is already present.
- Ensures that both the window start and cursor position remain unchanged, addressing a common issue with the
markdown-toc
package, which can disrupt the editing flow by moving the cursor and/or changing the window start. This behavior can be frustrating, as it interrupts the user’s focus and requires them to navigate back to their original position.
The code snippet that updates the table of contents and ensures that the cursor and window start remain unchanged
The following code snippet updates the table of contents while ensuring that the cursor and window start remain unchanged:
(The table of contents will be updated only if it is already present, so it is necessary to generate it at least once using the (markdown-toc-generate-toc)
function. Additionally, ensure that the markdown-mode Emacs package is installed.)
;; Author: James Cherti
;; URL: https://www.jamescherti.com/emacs-markdown-table-of-contents-update-before-save/
;; License: MIT
;; Configure the markdown-toc package
(use-package markdown-toc
:ensure t
:defer t
:commands (markdown-toc-generate-toc
markdown-toc-generate-or-refresh-toc
markdown-toc-delete-toc
markdown-toc--toc-already-present-p))
;; The following functions and hooks guarantee that any existing table of
;; contents remains current whenever changes are made to the markdown file,
;; while also ensuring that both the window start and cursor position remain
;; unchanged.
(defun my-markdown-toc-gen-if-present ()
"Generate a table of contents if it is already present."
(let ((marker (point-marker))
(current-visual-line
(save-excursion
(beginning-of-visual-line)
(count-screen-lines (window-start) (point)))))
(unwind-protect
(when (markdown-toc--toc-already-present-p)
(markdown-toc-generate-toc))
(goto-char (marker-position marker))
(beginning-of-visual-line)
(when (> current-visual-line 0)
(let ((window-start
(save-excursion
(beginning-of-visual-line)
(let ((line-move-ignore-invisible nil))
(line-move-visual (* -1 current-visual-line)))
(beginning-of-visual-line)
(point))))
(set-window-start (selected-window) window-start)))
(goto-char (marker-position marker)))))
(defun my-setup-markdown-toc ()
"Setup the markdown-toc package."
(add-hook 'before-save-hook #'my-markdown-toc-gen-if-present nil t))
(add-hook 'markdown-mode-hook #'my-setup-markdown-toc)
(add-hook 'markdown-ts-mode-hook #'my-setup-markdown-toc)
(add-hook 'gfm-mode-hook #'my-setup-markdown-toc)
Code language: Lisp (lisp)
The above code snippet leverages the markdown-toc Emacs package to automate the generation of a table of contents within markdown documents. This package includes functions such as markdown-toc-generate-toc
and markdown-toc-generate-or-refresh-toc
, which facilitate the creation and updating of the table of contents as needed.
I implemented the my-setup-markdown-toc
function to establish a hook that triggers my-markdown-toc-gen-if-present
each time a markdown buffer is saved. This function guarantees that any existing table of contents remains current whenever changes are made to the markdown file, while also ensuring that both the window start and cursor position remain unchanged.
Requirement: Markdown Mode Setup
The table of contents update code snippet above will only function correctly only if the markdown-mode package is installed:
(use-package markdown-mode
:ensure t
:defer t
:commands (gfm-mode gfm-view-mode markdown-mode markdown-view-mode)
:mode (("\\.markdown\\'" . markdown-mode)
("\\.md\\'" . markdown-mode)
("README\\.md\\'" . gfm-mode)))
Code language: Lisp (lisp)
Conclusion
The my-markdown-toc-gen-if-present
function automates the generation of a table of contents, ensuring that markdown documents consistently remain up-to-date with minimal effort.