123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469 |
- (require 'cl-lib)
- (require 'org-macs)
- (defvar org-comment-string)
- (defvar org-complex-heading-regexp)
- (defvar org-cycle-level-faces)
- (defvar org-footnote-section)
- (defvar org-level-faces)
- (defvar org-n-level-faces)
- (defvar org-odd-levels-only)
- (declare-function org-back-to-heading "org" (&optional invisible-ok))
- (declare-function org-entry-get "org" (pom property &optional inherit literal-nil))
- (declare-function org-reduced-level "org" (l))
- (defcustom org-num-face nil
- "Face to use for numbering.
- When nil, use the same face as the headline. This value is
- ignored if `org-num-format-function' specifies a face for its
- output."
- :group 'org-appearance
- :package-version '(Org . "9.3")
- :type '(choice (const :tag "Like the headline" nil)
- (face :tag "Use face"))
- :safe (lambda (val) (or (null val) (facep val))))
- (defcustom org-num-format-function 'org-num-default-format
- "Function used to display numbering.
- It is called with one argument, a list of numbers, and should
- return a string, or nil. When nil, no numbering is displayed.
- Any `face' text property on the returned string overrides
- `org-num-face'."
- :group 'org-appearance
- :package-version '(Org . "9.3")
- :type 'function
- :safe nil)
- (defcustom org-num-max-level nil
- "Level below which headlines are not numbered.
- When set to nil, all headlines are numbered."
- :group 'org-appearance
- :package-version '(Org . "9.3")
- :type '(choice (const :tag "Number everything" nil)
- (integer :tag "Stop numbering at level"))
- :safe (lambda (val) (or (null val) (wholenump val))))
- (defcustom org-num-skip-commented nil
- "Non-nil means commented sub-trees are not numbered."
- :group 'org-appearance
- :package-version '(Org . "9.3")
- :type 'boolean
- :safe #'booleanp)
- (defcustom org-num-skip-footnotes nil
- "Non-nil means footnotes sections are not numbered."
- :group 'org-appearance
- :package-version '(Org . "9.3")
- :type 'boolean
- :safe #'booleanp)
- (defcustom org-num-skip-tags nil
- "List of tags preventing the numbering of sub-trees.
- For example, add \"ARCHIVE\" to this list to avoid numbering
- archived sub-trees.
- Tag in this list prevent numbering the whole sub-tree,
- irrespective to `org-use-tags-inheritance', or other means to
- control tag inheritance."
- :group 'org-appearance
- :package-version '(Org . "9.3")
- :type '(repeat (string :tag "Tag"))
- :safe (lambda (val) (and (listp val) (cl-every #'stringp val))))
- (defcustom org-num-skip-unnumbered nil
- "Non-nil means numbering obeys to UNNUMBERED property."
- :group 'org-appearance
- :package-version '(Org . "9.3")
- :type 'boolean
- :safe #'booleanp)
- (defconst org-num--comment-re (format "\\`%s\\(?: \\|$\\)" org-comment-string)
- "Regexp matching a COMMENT keyword at headline beginning.")
- (defvar-local org-num--overlays nil
- "Ordered list of overlays used for numbering outlines.")
- (defvar-local org-num--skip-level nil
- "Level below which headlines from current tree are not numbered.
- When nil, all headlines are numbered. It is used to handle
- inheritance of no-numbering attributes.")
- (defvar-local org-num--numbering nil
- "Current headline numbering.
- A numbering is a list of integers, in reverse order. So numbering
- for headline \"1.2.3\" is (3 2 1).")
- (defvar-local org-num--missing-overlay nil
- "Buffer position signaling a headline without an overlay.")
- (defvar-local org-num--invalid-flag nil
- "Non-nil means an overlay became invalid since last update.")
- (defsubst org-num--headline-regexp ()
- "Return regexp matching a numbered headline."
- (if (null org-num-max-level) (org-with-limited-levels org-outline-regexp-bol)
- (format "^\\*\\{1,%d\\} "
- (if org-odd-levels-only (1- (* 2 org-num-max-level))
- org-num-max-level))))
- (defsubst org-num--overlay-p (o)
- "Non-nil if overlay O is a numbering overlay."
- (overlay-get o 'org-num))
- (defsubst org-num--valid-overlay-p (o)
- "Non-nil if overlay O is still active in the buffer."
- (not (eq 'invalid (overlay-get o 'org-num))))
- (defsubst org-num--invalidate-overlay (o)
- "Mark overlay O as invalid.
- Update `org-num--invalid-flag' accordingly."
- (overlay-put o 'org-num 'invalid)
- (setq org-num--invalid-flag t))
- (defun org-num--clear ()
- "Remove all numbering overlays in current buffer."
- (mapc #'delete-overlay org-num--overlays)
- (setq org-num--overlays nil))
- (defun org-num--make-overlay (numbering level skip)
- "Return overlay for numbering headline at point.
- NUMBERING is the numbering to use, as a list of integers, or nil
- if nothing should be displayed. LEVEL is the level of the
- headline. SKIP is its skip value.
- Assume point is at a headline."
- (let ((after-edit-functions
- (list (lambda (o &rest _) (org-num--invalidate-overlay o))))
- (o (save-excursion
- (beginning-of-line)
- (skip-chars-forward "*")
- (make-overlay (line-beginning-position) (1+ (point))))))
- (overlay-put o 'org-num t)
- (overlay-put o 'skip skip)
- (overlay-put o 'level level)
- (overlay-put o 'numbering-face
- (or org-num-face
-
-
-
-
- (nth (if org-cycle-level-faces
- (% (1- level) org-n-level-faces)
- (1- (min level org-n-level-faces)))
- org-level-faces)))
- (overlay-put o 'modification-hooks after-edit-functions)
- (overlay-put o 'insert-in-front-hooks after-edit-functions)
- (org-num--refresh-display o numbering)
- o))
- (defun org-num--refresh-display (overlay numbering)
- "Refresh OVERLAY's display.
- NUMBERING specifies the new numbering, as a list of integers, or
- nil if nothing should be displayed. Assume OVERLAY is valid."
- (let ((display (and numbering
- (funcall org-num-format-function (reverse numbering)))))
- (when (and display (not (get-text-property 0 'face display)))
- (org-add-props display `(face ,(overlay-get overlay 'numbering-face))))
- (overlay-put overlay 'after-string display)))
- (defun org-num--skip-value ()
- "Return skip value for headline at point.
- Value is t when headline should not be numbered, and nil
- otherwise."
- (org-match-line org-complex-heading-regexp)
- (let ((title (match-string 4))
- (tags (and org-num-skip-tags
- (match-end 5)
- (org-split-string (match-string 5) ":"))))
- (or (and org-num-skip-footnotes
- org-footnote-section
- (equal title org-footnote-section))
- (and org-num-skip-commented
- (let ((case-fold-search nil))
- (string-match org-num--comment-re title))
- t)
- (and org-num-skip-tags
- (cl-some (lambda (tag) (member tag org-num-skip-tags))
- tags)
- t)
- (and org-num-skip-unnumbered
- (org-entry-get (point) "UNNUMBERED")
- t))))
- (defun org-num--current-numbering (level skip)
- "Return numbering for current headline.
- LEVEL is headline's level, and SKIP its skip value. Return nil
- if headline should be skipped."
- (cond
-
- ((and org-num--skip-level (> level org-num--skip-level)) nil)
-
-
- (skip (setq org-num--skip-level level) nil)
- (t
- (setq org-num--skip-level nil)
-
- (let ((last-level (length org-num--numbering)))
- (setq org-num--numbering
- (cond
-
- ((null org-num--numbering) (cons 1 (make-list (1- level) 0)))
-
- ((= level last-level)
- (cons (1+ (car org-num--numbering)) (cdr org-num--numbering)))
-
- ((< level last-level)
- (let ((suffix (nthcdr (- last-level level) org-num--numbering)))
- (cons (1+ (car suffix)) (cdr suffix))))
-
- (t
- (append (cons 1 (make-list (- level last-level 1) 0))
- org-num--numbering))))))))
- (defun org-num--number-region (start end)
- "Add numbering overlays between START and END positions.
- When START or END are nil, use buffer boundaries. Narrowing, if
- any, is ignored. Return the list of created overlays, newest
- first."
- (org-with-point-at (or start 1)
-
- (when start (end-of-line))
- (let ((regexp (org-num--headline-regexp))
- (new nil))
- (while (re-search-forward regexp end t)
- (let* ((level (org-reduced-level
- (- (match-end 0) (match-beginning 0) 1)))
- (skip (org-num--skip-value))
- (numbering (org-num--current-numbering level skip)))
-
-
- (push (org-num--make-overlay numbering level skip)
- new)))
- new)))
- (defun org-num--update ()
- "Update buffer's numbering.
- This function removes invalid overlays and refreshes numbering
- for the valid ones in the numbering overlays list. It also adds
- missing overlays to that list."
- (setq org-num--skip-level nil)
- (setq org-num--numbering nil)
- (let ((new-overlays nil)
- (overlay nil))
- (while (setq overlay (pop org-num--overlays))
- (cond
-
-
-
-
-
-
- ((org-num--valid-overlay-p overlay)
- (let ((next (overlay-start overlay))
- (last (and new-overlays (overlay-start (car new-overlays)))))
- (cond
- ((null org-num--missing-overlay))
- ((> org-num--missing-overlay next))
- ((or (null last) (> org-num--missing-overlay last))
- (setq org-num--missing-overlay nil)
- (setq new-overlays (nconc (org-num--number-region last next)
- new-overlays)))
-
-
-
- (t
- (setq org-num--missing-overlay nil))))
-
- (let* ((level (overlay-get overlay 'level))
- (skip (overlay-get overlay 'skip))
- (numbering (org-num--current-numbering level skip)))
- (org-num--refresh-display overlay numbering)
- (push overlay new-overlays)))
-
-
-
- (t
-
-
- (delete-overlay overlay)
- (while (and org-num--overlays
- (not (org-num--valid-overlay-p (car org-num--overlays))))
- (delete-overlay (pop org-num--overlays)))
-
- (let ((last (and new-overlays (overlay-start (car new-overlays))))
- (next (and org-num--overlays
- (overlay-start (car org-num--overlays)))))
- (setq new-overlays (nconc (org-num--number-region last next)
- new-overlays))))))
-
-
-
- (when org-num--missing-overlay
- (let ((last (and new-overlays (overlay-start (car new-overlays)))))
- (setq new-overlays (nconc (org-num--number-region last nil)
- new-overlays))))
-
-
- (setq org-num--invalid-flag nil)
- (setq org-num--overlays (nreverse new-overlays))))
- (defun org-num--verify (beg end _)
- "Check numbering integrity; update it if necessary.
- This function is meant to be used in `after-change-functions'.
- See this variable for the meaning of BEG and END."
- (setq org-num--missing-overlay nil)
- (save-match-data
- (org-with-point-at beg
- (let ((regexp (org-num--headline-regexp)))
-
-
-
-
-
-
-
-
-
- (save-excursion
-
-
- (when (and (org-with-limited-levels
- (ignore-errors (org-back-to-heading t)))
- (looking-at regexp))
- (pcase (get-char-property-and-overlay (point) 'org-num)
- (`(nil)
-
-
- (setq org-num--missing-overlay (point)))
- (`(t . ,o)
-
-
- (unless (eq (org-num--skip-value) (overlay-get o 'skip))
- (org-num--invalidate-overlay o)))
- (_ nil))))
-
-
-
- (when (and (= beg end) (not (bolp)))
- (pcase (get-char-property-and-overlay (point) 'org-num)
- (`(t . ,o) (org-num--invalidate-overlay o))
- (_ nil)))
-
-
-
-
-
- (unless (or org-num--invalid-flag
- org-num--missing-overlay
- (<= end (line-end-position)))
- (forward-line)
- (when (or (re-search-forward regexp end 'move)
-
- (progn (skip-chars-backward "*") (looking-at regexp)))
- (setq org-num--missing-overlay (line-beginning-position))))))
-
- (when (or org-num--missing-overlay org-num--invalid-flag)
- (org-num--update))))
- (defun org-num-default-format (numbering)
- "Default numbering display function.
- NUMBERING is a list of numbers."
- (concat (mapconcat #'number-to-string numbering ".") " "))
- (define-minor-mode org-num-mode
- "Dynamic numbering of headlines in an Org buffer."
- :lighter " o#"
- (cond
- (org-num-mode
- (unless (derived-mode-p 'org-mode)
- (user-error "Cannot activate headline numbering outside Org mode"))
- (setq org-num--numbering nil)
- (setq org-num--overlays (nreverse (org-num--number-region nil nil)))
- (add-hook 'after-change-functions #'org-num--verify nil t)
- (add-hook 'change-major-mode-hook #'org-num--clear nil t))
- (t
- (org-num--clear)
- (remove-hook 'after-change-functions #'org-num--verify t)
- (remove-hook 'change-major-mode-hook #'org-num--clear t))))
- (provide 'org-num)
|