diff --git a/lisp/org-footnote.el b/lisp/org-footnote.el index 5df0efc4d..e5553646a 100644 --- a/lisp/org-footnote.el +++ b/lisp/org-footnote.el @@ -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." diff --git a/lisp/org-macs.el b/lisp/org-macs.el index 5fe322b52..cd2c56366 100644 --- a/lisp/org-macs.el +++ b/lisp/org-macs.el @@ -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 diff --git a/lisp/org.el b/lisp/org.el index 2cff98aa3..e66e6d543 100644 --- a/lisp/org.el +++ b/lisp/org.el @@ -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) diff --git a/testing/lisp/test-org-footnote.el b/testing/lisp/test-org-footnote.el index b2347cb97..78ff459d3 100644 --- a/testing/lisp/test-org-footnote.el +++ b/testing/lisp/test-org-footnote.el @@ -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\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\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) diff --git a/testing/lisp/test-org.el b/testing/lisp/test-org.el index d5d1b4aae..aa0c5fc42 100644 --- a/testing/lisp/test-org.el +++ b/testing/lisp/test-org.el @@ -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 ()