org-toggle-item: Move footnote-definitions out of the lists

* lisp/org-list.el (org-toggle-item): When headings contain
footnote-definitions, move them out of the list.  Footnote-definitions
must not be indented and hence cannot belong to the list.  Ensure that
definitions do not slurp the following element after the list.
Consider when the list created by `org-toggle-item' is continued by an
existing list.
* testing/lisp/test-org-list.el (test-org-list/toggle-item): Add
tests.

Reported-by: Ypo <ypuntot@gmail.com>
Link: https://orgmode.org/list/877d3k70lu.fsf@localhost
This commit is contained in:
Ihor Radchenko 2022-11-11 13:19:01 +08:00
parent c41a2198f2
commit f83e45526b
No known key found for this signature in database
GPG Key ID: 6470762A7DA11D8B
2 changed files with 128 additions and 8 deletions

View File

@ -3021,7 +3021,38 @@ If it is an item, convert all items to normal lines.
If it is normal text, change region into a list of items.
With a prefix argument ARG, change the region in a single item."
(interactive "P")
(let ((shift-text
(let ((extract-footnote-definitions
(lambda (end)
;; Remove footnote definitions from point to END.
;; Return the list of the extracted definitions.
(let (definitions element)
(save-excursion
(while (re-search-forward org-footnote-definition-re end t)
(setq element (org-element-at-point))
(when (eq 'footnote-definition
(org-element-type element))
(push (buffer-substring-no-properties
(org-element-property :begin element)
(org-element-property :end element))
definitions)
;; Ensure at least 2 blank lines after the last
;; footnote definition, thus not slurping the
;; following element.
(unless (<= 2 (org-element-property
:post-blank
(org-element-at-point)))
(setf (car definitions)
(concat (car definitions)
(make-string
(- 2 (org-element-property
:post-blank
(org-element-at-point)))
?\n))))
(delete-region
(org-element-property :begin element)
(org-element-property :end element))))
definitions))))
(shift-text
(lambda (ind end)
;; Shift text in current section to IND, from point to END.
;; The function leaves point to END line.
@ -3079,7 +3110,7 @@ With a prefix argument ARG, change the region in a single item."
(skip-chars-forward " \t")
(delete-region (point) (match-end 0)))
(forward-line)))
;; Case 2. Start at an heading: convert to items.
;; Case 2. Start at a heading: convert to items.
((org-at-heading-p)
;; Remove metadata
(let (org-loop-over-headlines-in-active-region)
@ -3095,7 +3126,9 @@ With a prefix argument ARG, change the region in a single item."
(t (length (match-string 0))))))
;; Level of first heading. Further headings will be
;; compared to it to determine hierarchy in the list.
(ref-level (org-reduced-level (org-outline-level))))
(ref-level (org-reduced-level (org-outline-level)))
(footnote-definitions
(funcall extract-footnote-definitions end)))
(while (< (point) end)
(let* ((level (org-reduced-level (org-outline-level)))
(delta (max 0 (- level ref-level)))
@ -3124,8 +3157,8 @@ With a prefix argument ARG, change the region in a single item."
"[X]"
"[ ]"))
(org-list-write-struct struct
(org-list-parents-alist struct)
old)))
(org-list-parents-alist struct)
old)))
;; Ensure all text down to END (or SECTION-END) belongs
;; to the newly created item.
(let ((section-end (save-excursion
@ -3133,13 +3166,23 @@ With a prefix argument ARG, change the region in a single item."
(forward-line)
(funcall shift-text
(+ start-ind (* (1+ delta) bul-len))
(min end section-end)))))))
(min end section-end)))))
(when footnote-definitions
(goto-char end)
;; Insert footnote definitions after the list.
(unless (bolp) (beginning-of-line 2))
;; At (point-max).
(unless (bolp) (insert "\n"))
(dolist (def footnote-definitions)
(insert def)))))
;; Case 3. Normal line with ARG: make the first line of region
;; an item, and shift indentation of others lines to
;; set them as item's body.
(arg (let* ((bul (org-list-bullet-string "-"))
(bul-len (length bul))
(ref-ind (org-current-text-indentation)))
(ref-ind (org-current-text-indentation))
(footnote-definitions
(funcall extract-footnote-definitions end)))
(skip-chars-forward " \t")
(insert bul)
(forward-line)
@ -3150,7 +3193,21 @@ With a prefix argument ARG, change the region in a single item."
(+ ref-ind bul-len)
(min end (save-excursion (or (outline-next-heading)
(point)))))
(forward-line))))
(forward-line))
(when footnote-definitions
;; If the new list is followed by same-level items,
;; move past them as well.
(goto-char (org-element-property
:end
(org-element-lineage
(org-element-at-point (1- end))
'(plain-list) t)))
;; Insert footnote definitions after the list.
(unless (bolp) (beginning-of-line 2))
;; At (point-max).
(unless (bolp) (insert "\n"))
(dolist (def footnote-definitions)
(insert def)))))
;; Case 4. Normal line without ARG: turn each non-item line
;; into an item.
(t

View File

@ -1356,6 +1356,69 @@ b. Item 2<point>"
(goto-char (point-max))
(org-toggle-item nil)
(buffer-string))))
;; When headings contain footnote definitions, move the definition
;; out of the list. Footnote definitions cannot be indented.
(should
(equal "- Main headline
- Headline 1
bbbbbbbb [fn:1]
- Headline 2
[fn:1] cccccccccccccccc
"
(org-test-with-temp-text "* Main headline
** Headline 1
bbbbbbbb [fn:1]
[fn:1] cccccccccccccccc
* Headline 2"
(transient-mark-mode 1)
(push-mark (point) t t)
(goto-char (point-max))
(org-toggle-item t)
(buffer-string))))
;; Footnote definitions that did not have trailing double blank line
;; must not slurp the following element.
(should
(equal "- Head 1
- Head 2
[fn:1] cccccccccccccccc
Paragraph outside footnote definitions."
(org-test-with-temp-text "* Head 1
[fn:1] cccccccccccccccc
* Head 2
Paragraph outside footnote definitions."
(transient-mark-mode 1)
(push-mark (point) t t)
(search-forward "Head 2")
(org-toggle-item t)
(buffer-string))))
;; Move footnote definitions past pre-existing items after.
(should
(equal "- Line 1
Line 2
- next item
[fn:1] definition
"
(org-test-with-temp-text "Line 1
Line 2
[fn:1] definition
- next item"
(transient-mark-mode 1)
(push-mark (point) t t)
(search-forward "definition")
(org-toggle-item t)
(buffer-string))))
;; When argument ARG is non-nil, change the whole region into
;; a single item.
(should