diff --git a/Readme.org b/Readme.org index 2840872..d2d3e9a 100644 --- a/Readme.org +++ b/Readme.org @@ -66,6 +66,9 @@ Refill previews in the backlinks buffer so they fit the window. Compute previews lazily for much better performance in buffers with many backlinks or reflinks. +** SPIKE [[file:lisp/org-format.el][org-format]] (/spike/) +Formatter for org-mode files to ensure consistency. + * Installation Most packages should be manually installable via =package.el=, assuming you have [[https://melpa.org/#/getting-started][MELPA]] set up. But honestly, you're better off just cloning this repo and putting diff --git a/lisp/org-format.el b/lisp/org-format.el new file mode 100644 index 0000000..15cbd9c --- /dev/null +++ b/lisp/org-format.el @@ -0,0 +1,114 @@ +;;; org-format.el --- Auto-format org buffers. -*- lexical-binding: t; -*- + +;;; Commentary: + +;; Adapted from: https://emacs.stackexchange.com/a/28268 + +;;; Code: + +(require 'org) + +(defgroup org-format nil + "Automatically format org buffers on save." + :group 'productivity + :prefix "org-format-") + +(defcustom org-format-blank-lines-before-subheadings 1 + "Number of blank lines between a heading and preceding content. + +Only applies to subheadings." + :group 'org-format + :type 'integer) + +(defcustom org-format-blank-lines-before-first-heading 1 + "Number of blank lines between a heading and preceding content. + +Only applies to first level-1 heading in the document, and +supercedes the setting for +`org-format-blank-lines-before-level-1-headings'." + :group 'org-format + :type 'integer) + +(defcustom org-format-blank-lines-before-level-1-headings 1 + "Number of blank lines between a heading and preceding content. + +Only applies to level-1 headings in the document." + :group 'org-format + :type 'integer) + +(defcustom org-format-blank-lines-before-content 0 + "Number of blank lines after the heading line and any property drawers." + :group 'org-format + :type 'integer) + +(defcustom org-format-blank-lines-before-meta 0 + "Number of blank lines between headers and subsequent planning & drawers." + :group 'org-format + :type 'integer) + +(defun org-format--ensure-empty-lines (n) + (save-excursion + (goto-char (line-beginning-position)) + (unless (bobp) + (forward-char -1) + (let ((start (point))) + (when (search-backward-regexp (rx (not (any space "\n")))) + (forward-char 1) + (delete-region (point) start))) + (insert (make-string n ?\n))))) + +(defun org-format-all-headings () + "Ensure that blank lines exist between headings and their contents." + (interactive) + (let ((scope (if (equal (buffer-name) "archive.org") 'tree 'file)) + (seen-first-heading-p)) + (org-map-entries (lambda () + ;; Widen so we can see space preceding the current + ;; headline. + (org-with-wide-buffer + (let* ((level (car (org-heading-components))) + (headline-spacing (cond + ((and (equal 1 level) (not seen-first-heading-p)) + (setq seen-first-heading-p t) + org-format-blank-lines-before-first-heading) + ((equal 1 level) + org-format-blank-lines-before-level-1-headings) + (t + org-format-blank-lines-before-subheadings)))) + (org-format--ensure-empty-lines headline-spacing))) + + (unless (or (org-at-heading-p) + (when (fboundp 'org-transclusion-within-transclusion-p) + (org-transclusion-within-transclusion-p))) + (org-format--ensure-empty-lines org-format-blank-lines-before-meta) + + (org-end-of-meta-data t) + (org-format--ensure-empty-lines org-format-blank-lines-before-content))) + t + scope) + + ;; Format transcluded headings as if they were really there. + (org-with-wide-buffer + (goto-char (point-min)) + (while (search-forward-regexp (rx bol "#+transclude:") nil t) + (save-excursion + (unless (search-forward ":only-content" (line-end-position) t) + (goto-char (line-beginning-position)) + (org-format--ensure-empty-lines org-format-blank-lines-before-subheadings))))))) + +;; NB: Set this higher than the default to avoid interfering with things like +;; org-transclusion, etc. +(defvar org-format-on-save-mode-hook-depth 95) + +(define-minor-mode org-format-on-save-mode + "Minor mode to enable formatting on buffer save in `org-mode'." + :lighter nil + (cond + (org-format-on-save-mode + (add-hook 'before-save-hook 'org-format-all-headings org-format-on-save-mode-hook-depth t)) + (t + (remove-hook 'before-save-hook 'org-format-all-headings t)))) + +(provide 'org-format) + +;;; org-format.el ends here