Files
nursery/lisp/org-format.el

152 lines
5.5 KiB
EmacsLisp
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

;;; org-format.el --- Auto-format org buffers. -*- lexical-binding: t; -*-
;;; Commentary:
;; Adapted from: https://emacs.stackexchange.com/a/28268
;; Example configuration:
;;
;; (use-package org-format
;; :hook (org-mode . org-format-on-save-mode))
;;; Code:
(require 'org)
(require 'thingatpt)
(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"))))
(ignore-errors
(forward-char 1)
(delete-region (point) start))))
(insert (make-string n ?\n)))))
(defun org-format--in-archived-heading-p ()
(save-excursion
(when (org-before-first-heading-p)
(org-forward-heading-same-level 1))
(let ((tags (org-get-tags)))
(seq-contains-p tags org-archive-tag))))
(defun org-format--delete-blank-lines ()
"Modified version of `delete-blank-lines'."
(beginning-of-line)
(when (looking-at "[ \t]*$")
(delete-region (point)
(if (re-search-backward "[^ \t\n]" nil t)
(progn (forward-line 1) (point))
(point-min))))
;; Handle the special case where point is followed by newline and eob.
;; Delete the line, leaving point at eob.
(when (looking-at "^[ \t]*\n\\'")
(delete-region (point) (point-max))))
(defun org-format-all-headings ()
"Ensure that blank lines exist between headings and their contents."
(interactive)
(let ((scope (if (org-format--in-archived-heading-p)
;; archive files can be enormous--just format the heading at
;; point after archiving.
'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 (and (fboundp 'org-transclusion-within-transclusion-p)
(org-transclusion-within-transclusion-p))
(forward-line 1)
(org-format--delete-blank-lines)
(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)
(org-with-wide-buffer
;; Clean up trailing whitespace.
(goto-char (point-max))
(org-format--delete-blank-lines)
;; Format transcluded headings as if they were really there.
(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