Preserve file local variables during some operations

* lisp/org-macs.el (org-preserve-local-variables): New macro.
* lisp/org-footnote.el (org-footnote--clear-footnote-section):
(org-footnote--goto-local-insertion-point):
(org-footnote-create-definition):
(org-footnote-delete):
(org-footnote-renumber-fn:N):
(org-footnote-sort):
(org-footnote-normalize):
* lisp/org.el (org-move-subtree-down):
(org-copy-subtree):
(org-sort-entries):
(org-refile): Use new macro.
* testing/lisp/test-org-footnote.el (test-org-footnote/normalize):
(test-org-footnote/delete):
(test-org-footnote/sort):
(test-org-footnote/normalize):
* testing/lisp/test-org.el (test-org/sort-entries): Add tests.

Operations affected include copying, killing, refiling, archiving and
moving subtrees.  It also affects sorting, creating and deleting
footnotes.
This commit is contained in:
Nicolas Goaziou 2017-12-18 15:50:51 +01:00
parent dce82f7e5c
commit fdb2eb6701
5 changed files with 491 additions and 391 deletions

View File

@ -313,23 +313,23 @@ otherwise."
(defun org-footnote--clear-footnote-section ()
"Remove all footnote sections in buffer and create a new one.
New section is created at the end of the buffer, before any file
local variable definition. Leave point within the new section."
New section is created at the end of the buffer. Leave point
within the new section."
(when org-footnote-section
(goto-char (point-min))
(let ((regexp
(format "^\\*+ +%s[ \t]*$"
(regexp-quote org-footnote-section))))
(let ((regexp (format "^\\*+ +%s[ \t]*$"
(regexp-quote org-footnote-section))))
(while (re-search-forward regexp nil t)
(delete-region
(match-beginning 0)
(progn (org-end-of-subtree t t)
(if (not (eobp)) (point)
(org-footnote--goto-local-insertion-point)
(skip-chars-forward " \t\n")
(if (eobp) (point) (line-beginning-position)))))))
(org-end-of-subtree t t))))
(goto-char (point-max))
(org-footnote--goto-local-insertion-point)
;; Clean-up blank lines at the end of the buffer.
(skip-chars-backward " \r\t\n")
(unless (bobp)
(forward-line)
(when (eolp) (insert "\n")))
(delete-region (point) (point-max))
(when (and (cdr (assq 'heading org-blank-before-new-entry))
(zerop (save-excursion (org-back-over-empty-lines))))
(insert "\n"))
@ -448,14 +448,8 @@ while collecting them."
"Find insertion point for footnote, just before next outline heading.
Assume insertion point is within currently accessible part of the buffer."
(org-with-limited-levels (outline-next-heading))
;; Skip file local variables. See `modify-file-local-variable'.
(when (eobp)
(let ((case-fold-search t))
(re-search-backward "^[ \t]*# +Local Variables:"
(max (- (point-max) 3000) (point-min))
t)))
(skip-chars-backward " \t\n")
(forward-line)
(unless (bobp) (forward-line))
(unless (bolp) (insert "\n")))
@ -676,21 +670,22 @@ Return buffer position at the beginning of the definition. This
function doesn't move point."
(let ((label (org-footnote-normalize-label label))
electric-indent-mode) ; Prevent wrong indentation.
(org-with-wide-buffer
(cond
((not org-footnote-section) (org-footnote--goto-local-insertion-point))
((save-excursion
(goto-char (point-min))
(re-search-forward
(concat "^\\*+[ \t]+" (regexp-quote org-footnote-section) "[ \t]*$")
nil t))
(goto-char (match-end 0))
(forward-line)
(unless (bolp) (insert "\n")))
(t (org-footnote--clear-footnote-section)))
(when (zerop (org-back-over-empty-lines)) (insert "\n"))
(insert "[fn:" label "] \n")
(line-beginning-position 0))))
(org-preserve-local-variables
(org-with-wide-buffer
(cond
((not org-footnote-section) (org-footnote--goto-local-insertion-point))
((save-excursion
(goto-char (point-min))
(re-search-forward
(concat "^\\*+[ \t]+" (regexp-quote org-footnote-section) "[ \t]*$")
nil t))
(goto-char (match-end 0))
(forward-line)
(unless (bolp) (insert "\n")))
(t (org-footnote--clear-footnote-section)))
(when (zerop (org-back-over-empty-lines)) (insert "\n"))
(insert "[fn:" label "] \n")
(line-beginning-position 0)))))
(defun org-footnote-delete-references (label)
"Delete every reference to footnote LABEL.
@ -733,31 +728,32 @@ and all references of a footnote label.
If LABEL is non-nil, delete that footnote instead."
(catch 'done
(let* ((nref 0) (ndef 0) x
;; 1. Determine LABEL of footnote at point.
(label (cond
;; LABEL is provided as argument.
(label)
;; Footnote reference at point. If the footnote is
;; anonymous, delete it and exit instead.
((setq x (org-footnote-at-reference-p))
(or (car x)
(progn
(delete-region (nth 1 x) (nth 2 x))
(message "Anonymous footnote removed")
(throw 'done t))))
;; Footnote definition at point.
((setq x (org-footnote-at-definition-p))
(car x))
(t (error "Don't know which footnote to remove")))))
;; 2. Now that LABEL is non-nil, find every reference and every
;; definition, and delete them.
(setq nref (org-footnote-delete-references label)
ndef (org-footnote-delete-definitions label))
;; 3. Verify consistency of footnotes and notify user.
(org-footnote-auto-adjust-maybe)
(message "%d definition(s) of and %d reference(s) of footnote %s removed"
ndef nref label))))
(org-preserve-local-variables
(let* ((nref 0) (ndef 0) x
;; 1. Determine LABEL of footnote at point.
(label (cond
;; LABEL is provided as argument.
(label)
;; Footnote reference at point. If the footnote is
;; anonymous, delete it and exit instead.
((setq x (org-footnote-at-reference-p))
(or (car x)
(progn
(delete-region (nth 1 x) (nth 2 x))
(message "Anonymous footnote removed")
(throw 'done t))))
;; Footnote definition at point.
((setq x (org-footnote-at-definition-p))
(car x))
(t (error "Don't know which footnote to remove")))))
;; 2. Now that LABEL is non-nil, find every reference and every
;; definition, and delete them.
(setq nref (org-footnote-delete-references label)
ndef (org-footnote-delete-definitions label))
;; 3. Verify consistency of footnotes and notify user.
(org-footnote-auto-adjust-maybe)
(message "%d definition(s) of and %d reference(s) of footnote %s removed"
ndef nref label)))))
;;;; Sorting, Renumbering, Normalizing
@ -765,28 +761,25 @@ If LABEL is non-nil, delete that footnote instead."
(defun org-footnote-renumber-fn:N ()
"Order numbered footnotes into a sequence in the document."
(interactive)
(let ((references (org-footnote--collect-references)))
(unwind-protect
(let* ((c 0)
(references (cl-remove-if-not
(lambda (r) (string-match-p "\\`[0-9]+\\'" (car r)))
references))
(alist (mapcar (lambda (l) (cons l (number-to-string (cl-incf c))))
(delete-dups (mapcar #'car references)))))
(org-with-wide-buffer
;; Re-number references.
(dolist (ref references)
(goto-char (nth 1 ref))
(org-footnote--set-label (cdr (assoc (nth 0 ref) alist))))
;; Re-number definitions.
(goto-char (point-min))
(while (re-search-forward "^\\[fn:\\([0-9]+\\)\\]" nil t)
(replace-match (or (cdr (assoc (match-string 1) alist))
;; Un-referenced definitions get
;; higher numbers.
(number-to-string (cl-incf c)))
nil nil nil 1))))
(dolist (r references) (set-marker (nth 1 r) nil)))))
(let* ((c 0)
(references (cl-remove-if-not
(lambda (r) (string-match-p "\\`[0-9]+\\'" (car r)))
(org-footnote--collect-references)))
(alist (mapcar (lambda (l) (cons l (number-to-string (cl-incf c))))
(delete-dups (mapcar #'car references)))))
(org-with-wide-buffer
;; Re-number references.
(dolist (ref references)
(goto-char (nth 1 ref))
(org-footnote--set-label (cdr (assoc (nth 0 ref) alist))))
;; Re-number definitions.
(goto-char (point-min))
(while (re-search-forward "^\\[fn:\\([0-9]+\\)\\]" nil t)
(replace-match (or (cdr (assoc (match-string 1) alist))
;; Un-referenced definitions get higher
;; numbers.
(number-to-string (cl-incf c)))
nil nil nil 1)))))
(defun org-footnote-sort ()
"Rearrange footnote definitions in the current buffer.
@ -795,129 +788,121 @@ references. Also relocate definitions at the end of their
relative section or within a single footnote section, according
to `org-footnote-section'. Inline definitions are ignored."
(let ((references (org-footnote--collect-references)))
(unwind-protect
(let ((definitions (org-footnote--collect-definitions 'delete)))
(org-with-wide-buffer
(org-footnote--clear-footnote-section)
;; Insert footnote definitions at the appropriate location,
;; separated by a blank line. Each definition is inserted
;; only once throughout the buffer.
(let (inserted)
(dolist (cell references)
(let ((label (car cell))
(nested (not (nth 2 cell)))
(inline (nth 3 cell)))
(unless (or (member label inserted) inline)
(push label inserted)
(unless (or org-footnote-section nested)
;; If `org-footnote-section' is non-nil, or
;; reference is nested, point is already at the
;; correct position. Otherwise, move at the
;; appropriate location within the section
;; containing the reference.
(goto-char (nth 1 cell))
(org-footnote--goto-local-insertion-point))
(insert "\n"
(or (cdr (assoc label definitions))
(format "[fn:%s] DEFINITION NOT FOUND." label))
"\n"))))
;; Insert un-referenced footnote definitions at the end.
(let ((unreferenced
(cl-remove-if (lambda (d) (member (car d) inserted))
definitions)))
(dolist (d unreferenced) (insert "\n" (cdr d) "\n"))))))
;; Clear dangling markers in the buffer.
(dolist (r references) (set-marker (nth 1 r) nil)))))
(org-preserve-local-variables
(let ((definitions (org-footnote--collect-definitions 'delete)))
(org-with-wide-buffer
(org-footnote--clear-footnote-section)
;; Insert footnote definitions at the appropriate location,
;; separated by a blank line. Each definition is inserted
;; only once throughout the buffer.
(let (inserted)
(dolist (cell references)
(let ((label (car cell))
(nested (not (nth 2 cell)))
(inline (nth 3 cell)))
(unless (or (member label inserted) inline)
(push label inserted)
(unless (or org-footnote-section nested)
;; If `org-footnote-section' is non-nil, or
;; reference is nested, point is already at the
;; correct position. Otherwise, move at the
;; appropriate location within the section
;; containing the reference.
(goto-char (nth 1 cell))
(org-footnote--goto-local-insertion-point))
(insert "\n"
(or (cdr (assoc label definitions))
(format "[fn:%s] DEFINITION NOT FOUND." label))
"\n"))))
;; Insert un-referenced footnote definitions at the end.
(pcase-dolist (`(,label . ,definition) definitions)
(unless (member label inserted)
(insert "\n" definition "\n")))))))))
(defun org-footnote-normalize ()
"Turn every footnote in buffer into a numbered one."
(interactive)
(let ((references (org-footnote--collect-references 'anonymous)))
(unwind-protect
(let ((n 0)
(translations nil)
(definitions nil))
(org-with-wide-buffer
;; Update label for reference. We need to do this before
;; clearing definitions in order to rename nested footnotes
;; before they are deleted.
(dolist (cell references)
(let* ((label (car cell))
(anonymous (not label))
(new
(cond
;; In order to differentiate anonymous
;; references from regular ones, set their
;; labels to integers, not strings.
(anonymous (setcar cell (cl-incf n)))
((cdr (assoc label translations)))
(t (let ((l (number-to-string (cl-incf n))))
(push (cons label l) translations)
l)))))
(goto-char (nth 1 cell)) ; Move to reference's start.
(org-footnote--set-label
(if anonymous (number-to-string new) new))
(let ((size (nth 3 cell)))
;; Transform inline footnotes into regular references
;; and retain their definition for later insertion as
;; a regular footnote definition.
(when size
(let ((def (concat
(format "[fn:%s] " new)
(org-trim
(substring
(delete-and-extract-region
(point) (+ (point) size 1))
1)))))
(push (cons (if anonymous new label) def) definitions)
(when org-footnote-fill-after-inline-note-extraction
(org-fill-paragraph)))))))
;; Collect definitions. Update labels according to ALIST.
(let ((definitions
(nconc definitions
(org-footnote--collect-definitions 'delete)))
(inserted))
(org-footnote--clear-footnote-section)
(dolist (cell references)
(let* ((label (car cell))
(anonymous (integerp label))
(pos (nth 1 cell)))
;; Move to appropriate location, if required. When
;; there is a footnote section or reference is
;; nested, point is already at the expected location.
(unless (or org-footnote-section (not (nth 2 cell)))
(goto-char pos)
(org-footnote--goto-local-insertion-point))
;; Insert new definition once label is updated.
(unless (member label inserted)
(push label inserted)
(let ((stored (cdr (assoc label definitions)))
;; Anonymous footnotes' label is already
;; up-to-date.
(new (if anonymous label
(cdr (assoc label translations)))))
(insert "\n"
(cond
((not stored)
(format "[fn:%s] DEFINITION NOT FOUND." new))
(anonymous stored)
(t
(replace-regexp-in-string
"\\`\\[fn:\\(.*?\\)\\]" new stored nil nil 1)))
"\n")))))
;; Insert un-referenced footnote definitions at the end.
(let ((unreferenced
(cl-remove-if (lambda (d) (member (car d) inserted))
definitions)))
(dolist (d unreferenced)
(insert "\n"
(replace-regexp-in-string
org-footnote-definition-re
(format "[fn:%d]" (cl-incf n))
(cdr d))
"\n"))))))
;; Clear dangling markers.
(dolist (r references) (set-marker (nth 1 r) nil)))))
(org-preserve-local-variables
(let ((n 0)
(translations nil)
(definitions nil)
(references (org-footnote--collect-references 'anonymous)))
(org-with-wide-buffer
;; Update label for reference. We need to do this before
;; clearing definitions in order to rename nested footnotes
;; before they are deleted.
(dolist (cell references)
(let* ((label (car cell))
(anonymous (not label))
(new
(cond
;; In order to differentiate anonymous references
;; from regular ones, set their labels to integers,
;; not strings.
(anonymous (setcar cell (cl-incf n)))
((cdr (assoc label translations)))
(t (let ((l (number-to-string (cl-incf n))))
(push (cons label l) translations)
l)))))
(goto-char (nth 1 cell)) ; Move to reference's start.
(org-footnote--set-label
(if anonymous (number-to-string new) new))
(let ((size (nth 3 cell)))
;; Transform inline footnotes into regular references and
;; retain their definition for later insertion as
;; a regular footnote definition.
(when size
(let ((def (concat
(format "[fn:%s] " new)
(org-trim
(substring
(delete-and-extract-region
(point) (+ (point) size 1))
1)))))
(push (cons (if anonymous new label) def) definitions)
(when org-footnote-fill-after-inline-note-extraction
(org-fill-paragraph)))))))
;; Collect definitions. Update labels according to ALIST.
(let ((definitions
(nconc definitions
(org-footnote--collect-definitions 'delete)))
(inserted))
(org-footnote--clear-footnote-section)
(dolist (cell references)
(let* ((label (car cell))
(anonymous (integerp label))
(pos (nth 1 cell)))
;; Move to appropriate location, if required. When there
;; is a footnote section or reference is nested, point is
;; already at the expected location.
(unless (or org-footnote-section (not (nth 2 cell)))
(goto-char pos)
(org-footnote--goto-local-insertion-point))
;; Insert new definition once label is updated.
(unless (member label inserted)
(push label inserted)
(let ((stored (cdr (assoc label definitions)))
;; Anonymous footnotes' label is already
;; up-to-date.
(new (if anonymous label
(cdr (assoc label translations)))))
(insert "\n"
(cond
((not stored)
(format "[fn:%s] DEFINITION NOT FOUND." new))
(anonymous stored)
(t
(replace-regexp-in-string
"\\`\\[fn:\\(.*?\\)\\]" new stored nil nil 1)))
"\n")))))
;; Insert un-referenced footnote definitions at the end.
(pcase-dolist (`(,label . ,definition) definitions)
(unless (member label inserted)
(insert "\n"
(replace-regexp-in-string org-footnote-definition-re
(format "[fn:%d]" (cl-incf n))
definition)
"\n"))))))))
(defun org-footnote-auto-adjust-maybe ()
"Renumber and/or sort footnotes according to user settings."

View File

@ -169,6 +169,24 @@ point nowhere."
"Load FILE with optional arguments NOERROR and MUSTSUFFIX."
`(load ,file 'noerror nil nil 'mustsuffix))
(defmacro org-preserve-local-variables (&rest body)
"Execute BODY while preserving local variables."
(declare (debug (body)))
`(let ((local-variables
(org-with-wide-buffer
(goto-char (point-max))
(let ((case-fold-search t))
(and (re-search-backward "^[ \t]*# +Local Variables:"
(max (- (point) 3000) 1)
t)
(delete-and-extract-region (point) (point-max)))))))
(unwind-protect (progn ,@body)
(when local-variables
(org-with-wide-buffer
(goto-char (point-max))
(unless (bolp) (insert "\n"))
(insert local-variables))))))
;;; Buffer

View File

@ -8085,80 +8085,81 @@ case."
"Move the current subtree down past ARG headlines of the same level."
(interactive "p")
(setq arg (prefix-numeric-value arg))
(let ((movfunc (if (> arg 0) 'org-get-next-sibling
'org-get-last-sibling))
(ins-point (make-marker))
(cnt (abs arg))
(col (current-column))
beg beg0 end txt folded ne-beg ne-end ne-ins ins-end)
;; Select the tree
(org-back-to-heading)
(setq beg0 (point))
(save-excursion
(setq ne-beg (org-back-over-empty-lines))
(setq beg (point)))
(save-match-data
(save-excursion (outline-end-of-heading)
(setq folded (org-invisible-p)))
(progn (org-end-of-subtree nil t)
(unless (eobp) (backward-char))))
(outline-next-heading)
(setq ne-end (org-back-over-empty-lines))
(setq end (point))
(goto-char beg0)
(when (and (> arg 0) (org-first-sibling-p) (< ne-end ne-beg))
;; include less whitespace
(save-excursion
(goto-char beg)
(forward-line (- ne-beg ne-end))
(setq beg (point))))
;; Find insertion point, with error handling
(while (> cnt 0)
(or (and (funcall movfunc) (looking-at org-outline-regexp))
(progn (goto-char beg0)
(user-error "Cannot move past superior level or buffer limit")))
(setq cnt (1- cnt)))
(when (> arg 0)
;; Moving forward - still need to move over subtree
(org-end-of-subtree t t)
(save-excursion
(org-back-over-empty-lines)
(or (bolp) (newline))))
(setq ne-ins (org-back-over-empty-lines))
(move-marker ins-point (point))
(setq txt (buffer-substring beg end))
(org-save-markers-in-region beg end)
(delete-region beg end)
(org-remove-empty-overlays-at beg)
(or (= beg (point-min)) (outline-flag-region (1- beg) beg nil))
(or (bobp) (outline-flag-region (1- (point)) (point) nil))
(and (not (bolp)) (looking-at "\n") (forward-char 1))
(let ((bbb (point)))
(insert-before-markers txt)
(org-reinstall-markers-in-region bbb)
(move-marker ins-point bbb))
(or (bolp) (insert "\n"))
(setq ins-end (point))
(goto-char ins-point)
(org-skip-whitespace)
(when (and (< arg 0)
(org-first-sibling-p)
(> ne-ins ne-beg))
;; Move whitespace back to beginning
(save-excursion
(goto-char ins-end)
(let ((kill-whole-line t))
(kill-line (- ne-ins ne-beg)) (point)))
(insert (make-string (- ne-ins ne-beg) ?\n)))
(move-marker ins-point nil)
(if folded
(outline-hide-subtree)
(org-show-entry)
(org-show-children)
(org-cycle-hide-drawers 'children))
(org-clean-visibility-after-subtree-move)
;; move back to the initial column we were at
(move-to-column col)))
(org-preserve-local-variables
(let ((movfunc (if (> arg 0) 'org-get-next-sibling
'org-get-last-sibling))
(ins-point (make-marker))
(cnt (abs arg))
(col (current-column))
beg beg0 end txt folded ne-beg ne-end ne-ins ins-end)
;; Select the tree
(org-back-to-heading)
(setq beg0 (point))
(save-excursion
(setq ne-beg (org-back-over-empty-lines))
(setq beg (point)))
(save-match-data
(save-excursion (outline-end-of-heading)
(setq folded (org-invisible-p)))
(progn (org-end-of-subtree nil t)
(unless (eobp) (backward-char))))
(outline-next-heading)
(setq ne-end (org-back-over-empty-lines))
(setq end (point))
(goto-char beg0)
(when (and (> arg 0) (org-first-sibling-p) (< ne-end ne-beg))
;; include less whitespace
(save-excursion
(goto-char beg)
(forward-line (- ne-beg ne-end))
(setq beg (point))))
;; Find insertion point, with error handling
(while (> cnt 0)
(or (and (funcall movfunc) (looking-at org-outline-regexp))
(progn (goto-char beg0)
(user-error "Cannot move past superior level or buffer limit")))
(setq cnt (1- cnt)))
(when (> arg 0)
;; Moving forward - still need to move over subtree
(org-end-of-subtree t t)
(save-excursion
(org-back-over-empty-lines)
(unless (bolp) (insert "\n"))))
(setq ne-ins (org-back-over-empty-lines))
(move-marker ins-point (point))
(setq txt (buffer-substring beg end))
(org-save-markers-in-region beg end)
(delete-region beg end)
(org-remove-empty-overlays-at beg)
(or (= beg (point-min)) (outline-flag-region (1- beg) beg nil))
(or (bobp) (outline-flag-region (1- (point)) (point) nil))
(and (not (bolp)) (looking-at "\n") (forward-char 1))
(let ((bbb (point)))
(insert-before-markers txt)
(org-reinstall-markers-in-region bbb)
(move-marker ins-point bbb))
(or (bolp) (insert "\n"))
(setq ins-end (point))
(goto-char ins-point)
(org-skip-whitespace)
(when (and (< arg 0)
(org-first-sibling-p)
(> ne-ins ne-beg))
;; Move whitespace back to beginning.
(save-excursion
(goto-char ins-end)
(let ((kill-whole-line t))
(kill-line (- ne-ins ne-beg)) (point)))
(insert (make-string (- ne-ins ne-beg) ?\n)))
(move-marker ins-point nil)
(if folded
(outline-hide-subtree)
(org-show-entry)
(org-show-children)
(org-cycle-hide-drawers 'children))
(org-clean-visibility-after-subtree-move)
;; Move back to the initial column we were at.
(move-to-column col))))
(defvar org-subtree-clip ""
"Clipboard for cut and paste of subtrees.
@ -8185,35 +8186,36 @@ If FORCE-STORE-MARKERS is non-nil, store the relative locations
of some markers in the region, even if CUT is non-nil. This is
useful if the caller implements cut-and-paste as copy-then-paste-then-cut."
(interactive "p")
(let (beg end folded (beg0 (point)))
(if (called-interactively-p 'any)
(org-back-to-heading nil) ; take what looks like a subtree
(org-back-to-heading t)) ; take what is really there
(setq beg (point))
(skip-chars-forward " \t\r\n")
(save-match-data
(if nosubtrees
(outline-next-heading)
(save-excursion (outline-end-of-heading)
(setq folded (org-invisible-p)))
(ignore-errors (org-forward-heading-same-level (1- n) t))
(org-end-of-subtree t t)))
;; Include the end of an inlinetask
(when (and (featurep 'org-inlinetask)
(looking-at-p (concat (org-inlinetask-outline-regexp)
"END[ \t]*$")))
(end-of-line))
(setq end (point))
(goto-char beg0)
(when (> end beg)
(setq org-subtree-clip-folded folded)
(when (or cut force-store-markers)
(org-save-markers-in-region beg end))
(if cut (kill-region beg end) (copy-region-as-kill beg end))
(setq org-subtree-clip (current-kill 0))
(message "%s: Subtree(s) with %d characters"
(if cut "Cut" "Copied")
(length org-subtree-clip)))))
(org-preserve-local-variables
(let (beg end folded (beg0 (point)))
(if (called-interactively-p 'any)
(org-back-to-heading nil) ; take what looks like a subtree
(org-back-to-heading t)) ; take what is really there
(setq beg (point))
(skip-chars-forward " \t\r\n")
(save-match-data
(if nosubtrees
(outline-next-heading)
(save-excursion (outline-end-of-heading)
(setq folded (org-invisible-p)))
(ignore-errors (org-forward-heading-same-level (1- n) t))
(org-end-of-subtree t t)))
;; Include the end of an inlinetask
(when (and (featurep 'org-inlinetask)
(looking-at-p (concat (org-inlinetask-outline-regexp)
"END[ \t]*$")))
(end-of-line))
(setq end (point))
(goto-char beg0)
(when (> end beg)
(setq org-subtree-clip-folded folded)
(when (or cut force-store-markers)
(org-save-markers-in-region beg end))
(if cut (kill-region beg end) (copy-region-as-kill beg end))
(setq org-subtree-clip (current-kill 0))
(message "%s: Subtree(s) with %d characters"
(if cut "Cut" "Copied")
(length org-subtree-clip))))))
(defun org-paste-subtree (&optional level tree for-yank remove)
"Paste the clipboard as a subtree, with modification of headline level.
@ -8691,88 +8693,91 @@ function is being called interactively."
(dcst (downcase sorting-type))
(case-fold-search nil)
(now (current-time)))
(sort-subr
(/= dcst sorting-type)
;; This function moves to the beginning character of the "record" to
;; be sorted.
(lambda nil
(if (re-search-forward re nil t)
(goto-char (match-beginning 0))
(goto-char (point-max))))
;; This function moves to the last character of the "record" being
;; sorted.
(lambda nil
(save-match-data
(condition-case nil
(outline-forward-same-level 1)
(error
(goto-char (point-max))))))
;; This function returns the value that gets sorted against.
(lambda nil
(cond
((= dcst ?n)
(if (looking-at org-complex-heading-regexp)
(string-to-number (org-sort-remove-invisible (match-string 4)))
nil))
((= dcst ?a)
(if (looking-at org-complex-heading-regexp)
(funcall case-func (org-sort-remove-invisible (match-string 4)))
nil))
((= dcst ?k)
(or (get-text-property (point) :org-clock-minutes) 0))
((= dcst ?t)
(let ((end (save-excursion (outline-next-heading) (point))))
(if (or (re-search-forward org-ts-regexp end t)
(re-search-forward org-ts-regexp-both end t))
(org-time-string-to-seconds (match-string 0))
(float-time now))))
((= dcst ?c)
(let ((end (save-excursion (outline-next-heading) (point))))
(if (re-search-forward
(concat "^[ \t]*\\[" org-ts-regexp1 "\\]")
end t)
(org-time-string-to-seconds (match-string 0))
(float-time now))))
((= dcst ?s)
(let ((end (save-excursion (outline-next-heading) (point))))
(if (re-search-forward org-scheduled-time-regexp end t)
(org-time-string-to-seconds (match-string 1))
(float-time now))))
((= dcst ?d)
(let ((end (save-excursion (outline-next-heading) (point))))
(if (re-search-forward org-deadline-time-regexp end t)
(org-time-string-to-seconds (match-string 1))
(float-time now))))
((= dcst ?p)
(if (re-search-forward org-priority-regexp (point-at-eol) t)
(string-to-char (match-string 2))
org-default-priority))
((= dcst ?r)
(or (org-entry-get nil property) ""))
((= dcst ?o)
(when (looking-at org-complex-heading-regexp)
(let* ((m (match-string 2))
(s (if (member m org-done-keywords) '- '+)))
(- 99 (funcall s (length (member m org-todo-keywords-1)))))))
((= dcst ?f)
(if getkey-func
(progn
(setq tmp (funcall getkey-func))
(when (stringp tmp) (setq tmp (funcall case-func tmp)))
tmp)
(error "Invalid key function `%s'" getkey-func)))
(t (error "Invalid sorting type `%c'" sorting-type))))
nil
(cond
((= dcst ?a) 'string<)
((= dcst ?f)
(or compare-func
(and interactive?
(org-read-function
(concat "Function for comparing keys "
"(empty for default `sort-subr' predicate): ")
'allow-empty))))
((member dcst '(?p ?t ?s ?d ?c ?k)) '<)))
(org-preserve-local-variables
(sort-subr
(/= dcst sorting-type)
;; This function moves to the beginning character of the
;; "record" to be sorted.
(lambda nil
(if (re-search-forward re nil t)
(goto-char (match-beginning 0))
(goto-char (point-max))))
;; This function moves to the last character of the "record" being
;; sorted.
(lambda nil
(save-match-data
(condition-case nil
(outline-forward-same-level 1)
(error
(goto-char (point-max))))))
;; This function returns the value that gets sorted against.
(lambda ()
(cond
((= dcst ?n)
(if (looking-at org-complex-heading-regexp)
(string-to-number
(org-sort-remove-invisible (match-string 4)))
nil))
((= dcst ?a)
(if (looking-at org-complex-heading-regexp)
(funcall case-func
(org-sort-remove-invisible (match-string 4)))
nil))
((= dcst ?k)
(or (get-text-property (point) :org-clock-minutes) 0))
((= dcst ?t)
(let ((end (save-excursion (outline-next-heading) (point))))
(if (or (re-search-forward org-ts-regexp end t)
(re-search-forward org-ts-regexp-both end t))
(org-time-string-to-seconds (match-string 0))
(float-time now))))
((= dcst ?c)
(let ((end (save-excursion (outline-next-heading) (point))))
(if (re-search-forward
(concat "^[ \t]*\\[" org-ts-regexp1 "\\]")
end t)
(org-time-string-to-seconds (match-string 0))
(float-time now))))
((= dcst ?s)
(let ((end (save-excursion (outline-next-heading) (point))))
(if (re-search-forward org-scheduled-time-regexp end t)
(org-time-string-to-seconds (match-string 1))
(float-time now))))
((= dcst ?d)
(let ((end (save-excursion (outline-next-heading) (point))))
(if (re-search-forward org-deadline-time-regexp end t)
(org-time-string-to-seconds (match-string 1))
(float-time now))))
((= dcst ?p)
(if (re-search-forward org-priority-regexp (point-at-eol) t)
(string-to-char (match-string 2))
org-default-priority))
((= dcst ?r)
(or (org-entry-get nil property) ""))
((= dcst ?o)
(when (looking-at org-complex-heading-regexp)
(let* ((m (match-string 2))
(s (if (member m org-done-keywords) '- '+)))
(- 99 (funcall s (length (member m org-todo-keywords-1)))))))
((= dcst ?f)
(if getkey-func
(progn
(setq tmp (funcall getkey-func))
(when (stringp tmp) (setq tmp (funcall case-func tmp)))
tmp)
(error "Invalid key function `%s'" getkey-func)))
(t (error "Invalid sorting type `%c'" sorting-type))))
nil
(cond
((= dcst ?a) 'string<)
((= dcst ?f)
(or compare-func
(and interactive?
(org-read-function
(concat "Function for comparing keys "
"(empty for default `sort-subr' predicate): ")
'allow-empty))))
((member dcst '(?p ?t ?s ?d ?c ?k)) '<))))
(when restore-clock?
(move-marker org-clock-marker
(1+ (next-single-property-change
@ -11552,9 +11557,10 @@ prefix argument (`C-u C-u C-u C-c C-w')."
(unless org-refile-keep
(if regionp
(delete-region (point) (+ (point) (- region-end region-start)))
(delete-region
(and (org-back-to-heading t) (point))
(min (1+ (buffer-size)) (org-end-of-subtree t t) (point)))))
(org-preserve-local-variables
(delete-region
(and (org-back-to-heading t) (point))
(min (1+ (buffer-size)) (org-end-of-subtree t t) (point))))))
(when (featurep 'org-inlinetask)
(org-inlinetask-remove-END-maybe))
(setq org-markers-to-move nil)

View File

@ -112,7 +112,33 @@
(org-footnote-new))
(buffer-substring-no-properties
(line-beginning-position -1)
(line-beginning-position 4))))))
(line-beginning-position 4)))))
;; Do not alter file local variables when inserting new definition
;; label.
(should
(equal "Paragraph[fn:1]
\[fn:1]
# Local Variables:
# foo: t
# End:"
(org-test-with-temp-text
"Paragraph<point>\n# Local Variables:\n# foo: t\n# End:"
(let ((org-footnote-section nil)) (org-footnote-new))
(buffer-string))))
(should
(equal "Paragraph[fn:1]
* Footnotes
\[fn:1]
# Local Variables:
# foo: t
# End:"
(org-test-with-temp-text
"Paragraph<point>\n# Local Variables:\n# foo: t\n# End:"
(let ((org-footnote-section "Footnotes")) (org-footnote-new))
(buffer-string)))))
(ert-deftest test-org-footnote/delete ()
"Test `org-footnote-delete' specifications."
@ -175,7 +201,15 @@
(org-test-with-temp-text
"Text[fn:1]\n\n[fn:1] Definition.\n\n\nOther text."
(org-footnote-delete "1")
(buffer-string))))))
(buffer-string)))))
;; Preserve file local variables when deleting a footnote.
(should
(equal
"Paragraph\n# Local Variables:\n# foo: t\n# End:"
(org-test-with-temp-text
"Paragraph[fn:1]\n[fn:1] Def 1\n# Local Variables:\n# foo: t\n# End:"
(let ((org-footnote-section nil)) (org-footnote-delete "1"))
(buffer-string)))))
(ert-deftest test-org-footnote/goto-definition ()
"Test `org-footnote-goto-definition' specifications."
@ -454,7 +488,32 @@ Text[fn:1][fn:4]
"
(org-test-with-temp-text "Text[fn:9]\n\n[fn:1] A\n[fn:9] B"
(let ((org-footnote-section nil)) (org-footnote-sort))
(buffer-string)))))
(buffer-string))))
;; When sorting, preserve file local variables.
(should
(equal "
Paragraph[fn:1][fn:2]
\[fn:1] Def 1
\[fn:2] Def 2
# Local Variables:
# foo: t
# End:"
(org-test-with-temp-text
"
Paragraph[fn:1][fn:2]
\[fn:2] Def 2
\[fn:1] Def 1
# Local Variables:
# foo: t
# End:"
(let ((org-footnote-section nil)) (org-footnote-sort))
(buffer-string)))))
(ert-deftest test-org-footnote/renumber-fn:N ()
"Test `org-footnote-renumber-fn:N' specifications."
@ -569,7 +628,32 @@ Text[fn:1][fn:4]
"Test[fn:1]\nNext\n\n[fn:1] def\n\n[fn:2] A\n"
(org-test-with-temp-text "Test[fn::def]\nNext\n[fn:unref] A"
(let ((org-footnote-section nil)) (org-footnote-normalize))
(buffer-string)))))
(buffer-string))))
;; Preserve file local variables when normalizing.
(should
(equal "
Paragraph[fn:1][fn:2]
\[fn:1] Def 1
\[fn:2] Def 2
# Local Variables:
# foo: t
# End:"
(org-test-with-temp-text
"
Paragraph[fn:foo][fn:bar]
\[fn:bar] Def 2
\[fn:foo] Def 1
# Local Variables:
# foo: t
# End:"
(let ((org-footnote-section nil)) (org-footnote-normalize))
(buffer-string)))))
(provide 'test-org-footnote)

View File

@ -2918,6 +2918,13 @@ SCHEDULED: <2017-05-06 Sat>
:END:
"
(org-sort-entries nil ?k)
(buffer-string))))
;; Preserve file local variables when sorting.
(should
(equal "\n* A\n* B\n# Local Variables:\n# foo: t\n# End:\n"
(org-test-with-temp-text
"\n* B\n* A\n# Local Variables:\n# foo: t\n# End:"
(org-sort-entries nil ?a)
(buffer-string)))))
(ert-deftest test-org/file-contents ()