Fix subtle differences between overlays and invisible text properties

* lisp/org-clock.el (org-clock-in):
(org-clock-find-position):
(org-clock-out):
* lisp/org.el (org-add-planning-info):
(org-scan-tags):
(org-global-tags-completion-table):
(org-make-tags-matcher):
(org-tags-expand):
(org--property-local-values):
(org-read-date-analyze):
(org-revert-all-org-buffers):
(org-beginning-of-line): Make sure that we inherit invisible state
when inserting text.
(org-sort-entries): Preserve invisible state after replace-match.

(org-log-beginning): Do not try to move by visible lines.

* lisp/org-macs.el (org-preserve-local-variables): Do not try to
preserve overlays.
* lisp/ox.el (org-export--generate-copy-script): Preserve folding
properties in export buffer.
* testing/lisp/test-ob.el (test-ob/preserve-results-indentation): Fix
test failure.
* testing/lisp/test-org.el (test-org/meta-return):
(test-org/custom-properties): Use new folding.
This commit is contained in:
Ihor Radchenko 2022-01-16 15:18:53 +08:00
parent cd83606cfd
commit f63ff07441
No known key found for this signature in database
GPG Key ID: 6470762A7DA11D8B
6 changed files with 367 additions and 340 deletions

View File

@ -1373,14 +1373,14 @@ the default behavior."
(sit-for 2)
(throw 'abort nil))
(t
(insert-before-markers "\n")
(insert-before-markers-and-inherit "\n")
(backward-char 1)
(when (and (save-excursion
(end-of-line 0)
(org-in-item-p)))
(beginning-of-line 1)
(indent-line-to (max 0 (- (current-indentation) 2))))
(insert org-clock-string " ")
(insert-and-inherit org-clock-string " ")
(setq org-clock-effort (org-entry-get (point) org-effort-property))
(setq org-clock-total-time (org-clock-sum-current-item
(org-clock-get-sum-start)))
@ -1581,19 +1581,23 @@ line and position cursor in that line."
count (1+ count))))))
(cond
((null positions)
(org-fold-core-ignore-modifications
;; Skip planning line and property drawer, if any.
(org-end-of-meta-data)
(unless (bolp) (insert "\n"))
(unless (bolp) (insert-and-inherit "\n"))
;; Create a new drawer if necessary.
(when (and org-clock-into-drawer
(or (not (wholenump org-clock-into-drawer))
(< org-clock-into-drawer 2)))
(let ((beg (point)))
(insert ":" drawer ":\n:END:\n")
(insert-and-inherit ":" drawer ":\n:END:\n")
(org-indent-region beg (point))
(org-flag-region
(line-end-position -1) (1- (point)) t 'outline)
(forward-line -1))))
(if (eq org-fold-core-style 'text-properties)
(org-fold-region
(line-end-position -1) (1- (point)) t 'drawer)
(org-fold-region
(line-end-position -1) (1- (point)) t 'outline))
(forward-line -1)))))
;; When a clock drawer needs to be created because of the
;; number of clock items or simply if it is missing, collect
;; all clocks in the section and wrap them within the drawer.
@ -1602,8 +1606,9 @@ line and position cursor in that line."
drawer)
;; Skip planning line and property drawer, if any.
(org-end-of-meta-data)
(org-fold-core-ignore-modifications
(let ((beg (point)))
(insert
(insert-and-inherit
(mapconcat
(lambda (p)
(save-excursion
@ -1616,14 +1621,14 @@ line and position cursor in that line."
"\n:END:\n")
(let ((end (point-marker)))
(goto-char beg)
(save-excursion (insert ":" drawer ":\n"))
(org-flag-region (line-end-position) (1- end) t 'outline)
(save-excursion (insert-and-inherit ":" drawer ":\n"))
(org-fold-region (line-end-position) (1- end) t 'outline)
(org-indent-region (point) end)
(forward-line)
(unless org-log-states-order-reversed
(goto-char end)
(beginning-of-line -1))
(set-marker end nil))))
(set-marker end nil)))))
(org-log-states-order-reversed (goto-char (car (last positions))))
(t (goto-char (car positions))))))))
@ -1672,7 +1677,8 @@ to, overriding the existing value of `org-clock-out-switch-to-state'."
(if fail-quietly (throw 'exit nil) (error "Clock start time is gone")))
(goto-char (match-end 0))
(delete-region (point) (point-at-eol))
(insert "--")
(org-fold-core-ignore-modifications
(insert-and-inherit "--")
(setq te (org-insert-time-stamp (or at-time now) 'with-hm 'inactive))
(setq s (org-time-convert-to-integer
(time-subtract
@ -1680,7 +1686,7 @@ to, overriding the existing value of `org-clock-out-switch-to-state'."
(org-time-string-to-time ts)))
h (floor s 3600)
m (floor (mod s 3600) 60))
(insert " => " (format "%2d:%02d" h m))
(insert-and-inherit " => " (format "%2d:%02d" h m))
(move-marker org-clock-marker nil)
(move-marker org-clock-hd-marker nil)
;; Possibly remove zero time clocks.
@ -1689,7 +1695,7 @@ to, overriding the existing value of `org-clock-out-switch-to-state'."
(setq remove t)
(delete-region (line-beginning-position)
(line-beginning-position 2)))
(org-clock-remove-empty-clock-drawer)
(org-clock-remove-empty-clock-drawer))
(when org-clock-mode-line-timer
(cancel-timer org-clock-mode-line-timer)
(setq org-clock-mode-line-timer nil))

View File

@ -170,16 +170,8 @@
(when local-variables
(org-with-wide-buffer
(goto-char (point-max))
;; If last section is folded, make sure to also hide file
;; local variables after inserting them back.
(let ((overlay
(cl-find-if (lambda (o)
(eq 'outline (overlay-get o 'invisible)))
(overlays-at (1- (point))))))
(unless (bolp) (insert "\n"))
(insert local-variables)
(when overlay
(move-overlay overlay (overlay-start overlay) (point-max)))))))))
(insert local-variables))))))
(defmacro org-no-popups (&rest body)
"Suppress popup windows and evaluate BODY."

View File

@ -6411,7 +6411,7 @@ odd number. Returns values greater than 0."
(replace-match "# " nil t))
((= level 1)
(user-error "Cannot promote to level 0. UNDO to recover if necessary"))
(t (replace-match up-head nil t)))
(t (replace-match (apply #'propertize up-head (text-properties-at (match-beginning 0))) t)))
(unless (= level 1)
(when org-auto-align-tags (org-align-tags))
(when org-adapt-indentation (org-fixup-indentation (- diff))))
@ -6426,9 +6426,10 @@ odd number. Returns values greater than 0."
(level (save-match-data (funcall outline-level)))
(down-head (concat (make-string (org-get-valid-level level 1) ?*) " "))
(diff (abs (- level (length down-head) -1))))
(replace-match down-head nil t)
(org-fold-core-ignore-fragility-checks
(replace-match (apply #'propertize down-head (text-properties-at (match-beginning 0))) t)
(when org-auto-align-tags (org-align-tags))
(when org-adapt-indentation (org-fixup-indentation diff))
(when org-adapt-indentation (org-fixup-indentation diff)))
(run-hooks 'org-after-demote-entry-hook))))
(defun org-cycle-level ()
@ -8956,7 +8957,15 @@ When called through ELisp, arg is also interpreted in the following way:
this org-state block-reason)
(throw 'exit nil)))))
(store-match-data match-data)
(replace-match next t t)
(org-fold-core-ignore-modifications
(save-excursion
(goto-char (match-beginning 0))
(setf (buffer-substring (match-beginning 0) (match-end 0)) "")
(insert-and-inherit next)
(unless (org-invisible-p (line-beginning-position))
(org-fold-region (line-beginning-position)
(line-end-position)
nil))))
(cond ((and org-state (equal this org-state))
(message "TODO state was already %s" (org-trim next)))
((not (pos-visible-in-window-p hl-pos))
@ -9697,6 +9706,7 @@ of `org-todo-keywords-1'."
"Insert DEADLINE or SCHEDULE information in current entry.
TYPE is either `deadline' or `scheduled'. See `org-deadline' or
`org-schedule' for information about ARG and TIME arguments."
(org-fold-core-ignore-modifications
(let* ((deadline? (eq type 'deadline))
(keyword (if deadline? org-deadline-string org-scheduled-string))
(log (if deadline? org-log-redeadline org-log-reschedule))
@ -9765,13 +9775,13 @@ TYPE is either `deadline' or `scheduled'. See `org-deadline' or
(line-end-position 2)
t)
(goto-char (1- (match-end 0)))
(insert " " repeater)
(insert-and-inherit " " repeater)
(setq org-last-inserted-timestamp
(concat (substring org-last-inserted-timestamp 0 -1)
" " repeater
(substring org-last-inserted-timestamp -1))))))
(message (if deadline? "Deadline on %s" "Scheduled to %s")
org-last-inserted-timestamp)))))
org-last-inserted-timestamp))))))
(defun org-deadline (arg &optional time)
"Insert a \"DEADLINE:\" string with a timestamp to make a deadline.
@ -9876,6 +9886,7 @@ among `closed', `deadline', `scheduled' and nil. TIME indicates
the time to use. If none is given, the user is prompted for
a date. REMOVE indicates what kind of entries to remove. An old
WHAT entry will also be removed."
(org-fold-core-ignore-modifications
(let (org-time-was-given org-end-time-was-given default-time default-input)
(when (and (memq what '(scheduled deadline))
(or (not time)
@ -9950,13 +9961,13 @@ WHAT entry will also be removed."
(delete-region (point) (line-end-position)))))))
(what
(end-of-line)
(insert "\n")
(insert-and-inherit "\n")
(when org-adapt-indentation
(indent-to-column (1+ (org-outline-level)))))
(t nil)))
(when what
;; Insert planning keyword.
(insert (cl-case what
(insert-and-inherit (cl-case what
(closed org-closed-string)
(deadline org-deadline-string)
(scheduled org-scheduled-string)
@ -9970,7 +9981,7 @@ WHAT entry will also be removed."
(eq what 'closed)
nil nil (list org-end-time-was-given))))
(unless (eolp) (insert " "))
ts)))))
ts))))))
(defvar org-log-note-marker (make-marker)
"Marker pointing at the entry where the note is to be inserted.")
@ -10020,13 +10031,19 @@ narrowing."
(throw 'exit nil))))
;; No drawer found. Create one, if permitted.
(when create
(unless (bolp) (insert "\n"))
;; Avoid situation when we insert drawer right before
;; first "*". Otherwise, if the previous heading is
;; folded, we are inserting after visible newline at
;; the end of the fold, thus breaking the fold
;; continuity.
(when (org-at-heading-p) (backward-char))
(org-fold-core-ignore-modifications
(unless (bolp) (insert-and-inherit "\n"))
(let ((beg (point)))
(insert ":" drawer ":\n:END:\n")
(insert-and-inherit ":" drawer ":\n:END:\n")
(org-indent-region beg (point))
(org-flag-region (line-end-position -1)
(1- (point)) t 'outline))
(end-of-line -1)))))
(org-fold-region (line-end-position -1) (1- (point)) t (if (eq org-fold-core-style 'text-properties) 'drawer 'outline)))))
(end-of-line -1))))
(t
(org-end-of-meta-data org-log-state-notes-insert-after-drawers)
(skip-chars-forward " \t\n")
@ -10034,7 +10051,7 @@ narrowing."
(unless org-log-states-order-reversed
(org-skip-over-state-notes)
(skip-chars-backward " \t\n")
(forward-line)))))
(beginning-of-line 2)))))
(if (bolp) (point) (line-beginning-position 2))))
(defun org-add-log-setup (&optional purpose state prev-state how extra)
@ -10160,6 +10177,7 @@ EXTRA is additional text that will be inserted into the notes buffer."
(push note lines))
(when (and lines (not org-note-abort))
(with-current-buffer (marker-buffer org-log-note-marker)
(org-fold-core-ignore-modifications
(org-with-wide-buffer
;; Find location for the new note.
(goto-char org-log-note-marker)
@ -10169,8 +10187,8 @@ EXTRA is additional text that will be inserted into the notes buffer."
(unless (eq org-log-note-purpose 'clock-out)
(goto-char (org-log-beginning t)))
;; Make sure point is at the beginning of an empty line.
(cond ((not (bolp)) (let ((inhibit-read-only t)) (insert "\n")))
((looking-at "[ \t]*\\S-") (save-excursion (insert "\n"))))
(cond ((not (bolp)) (let ((inhibit-read-only t)) (insert-and-inherit "\n")))
((looking-at "[ \t]*\\S-") (save-excursion (insert-and-inherit "\n"))))
;; In an existing list, add a new item at the top level.
;; Otherwise, indent line like a regular one.
(let ((itemp (org-in-item-p)))
@ -10180,14 +10198,14 @@ EXTRA is additional text that will be inserted into the notes buffer."
(goto-char itemp) (org-list-struct))))
(org-list-get-ind (org-list-get-top-point struct) struct)))
(org-indent-line)))
(insert (org-list-bullet-string "-") (pop lines))
(insert-and-inherit (org-list-bullet-string "-") (pop lines))
(let ((ind (org-list-item-body-column (line-beginning-position))))
(dolist (line lines)
(insert "\n")
(insert-and-inherit "\n")
(indent-line-to ind)
(insert line)))
(insert-and-inherit line)))
(message "Note stored")
(org-back-to-heading t)))))
(org-back-to-heading t))))))
;; Don't add undo information when called from `org-agenda-todo'.
(set-window-configuration org-log-note-window-configuration)
(with-current-buffer (marker-buffer org-log-note-return-to)
@ -11318,6 +11336,7 @@ If TAGS is nil or the empty string, all tags are removed.
This function assumes point is on a headline."
(org-with-wide-buffer
(org-fold-core-ignore-modifications
(let ((tags (pcase tags
((pred listp) tags)
((pred stringp) (split-string (org-trim tags) ":" t))
@ -11337,15 +11356,15 @@ This function assumes point is on a headline."
;; Re-introduce one space in this case.
(unless (org-at-heading-p) (insert " "))
(when tags
(save-excursion (insert " " (org-make-tag-string tags)))
(save-excursion (insert-and-inherit " " (org-make-tag-string tags)))
;; When text is being inserted on an invisible region
;; boundary, it can be inadvertently sucked into
;; invisibility.
(unless (org-invisible-p (line-beginning-position))
(org-flag-region (point) (line-end-position) nil 'outline))))
(org-fold-region (point) (line-end-position) nil 'outline))))
;; Align tags, if any.
(when tags (org-align-tags))
(when tags-change? (run-hooks 'org-after-tags-change-hook)))))
(when tags-change? (run-hooks 'org-after-tags-change-hook))))))
(defun org-change-tag-in-region (beg end tag off)
"Add or remove TAG for each entry in the region.
@ -12539,6 +12558,7 @@ decreases scheduled or deadline date by one day."
((member property org-special-properties)
(error "The %s property cannot be set with `org-entry-put'" property))
(t
(org-fold-core-ignore-modifications
(let* ((range (org-get-property-block beg 'force))
(end (cdr range))
(case-fold-search t))
@ -12547,11 +12567,11 @@ decreases scheduled or deadline date by one day."
(progn (delete-region (match-beginning 0) (match-end 0))
(goto-char (match-beginning 0)))
(goto-char end)
(insert "\n")
(insert-and-inherit "\n")
(backward-char))
(insert ":" property ":")
(when value (insert " " value))
(org-indent-line)))))
(insert-and-inherit ":" property ":")
(when value (insert-and-inherit " " value))
(org-indent-line))))))
(run-hook-with-args 'org-property-changed-functions property value))))
(defun org-buffer-property-keys (&optional specials defaults columns)
@ -13705,10 +13725,11 @@ stamp will not contribute to the agenda.
PRE and POST are optional strings to be inserted before and after the
stamp.
The command returns the inserted time stamp."
(org-fold-core-ignore-modifications
(let ((fmt (funcall (if with-hm 'cdr 'car) org-time-stamp-formats))
stamp)
(when inactive (setq fmt (concat "[" (substring fmt 1 -1) "]")))
(insert-before-markers (or pre ""))
(insert-before-markers-and-inherit (or pre ""))
(when (listp extra)
(setq extra (car extra))
(if (and (stringp extra)
@ -13719,9 +13740,9 @@ The command returns the inserted time stamp."
(setq extra nil)))
(when extra
(setq fmt (concat (substring fmt 0 -1) extra (substring fmt -1))))
(insert-before-markers (setq stamp (format-time-string fmt time)))
(insert-before-markers (or post ""))
(setq org-last-inserted-timestamp stamp)))
(insert-before-markers-and-inherit (setq stamp (format-time-string fmt time)))
(insert-before-markers-and-inherit (or post ""))
(setq org-last-inserted-timestamp stamp))))
(defun org-toggle-time-stamp-overlays ()
"Toggle the use of custom time stamp formats."
@ -18346,7 +18367,10 @@ Alignment is done according to `org-property-format', which see."
(let ((newtext (concat (match-string 4)
(org-trim
(format org-property-format (match-string 1) (match-string 3))))))
(setf (buffer-substring (match-beginning 0) (match-end 0)) newtext)))))
;; Do not use `replace-match' here as we want to inherit folding
;; properties if inside fold.
(setf (buffer-substring (match-beginning 0) (match-end 0)) "")
(insert-and-inherit newtext)))))
(defun org-indent-line ()
"Indent line depending on context.

View File

@ -2588,7 +2588,9 @@ The function assumes BUFFER's major mode is `org-mode'."
(or (memq var
'(default-directory
buffer-file-name
buffer-file-coding-system))
buffer-file-coding-system
;; Needed to preserve folding state
char-property-alias-alist))
(assq var bound-variables)
(string-match "^\\(org-\\|orgtbl-\\)"
(symbol-name var)))

View File

@ -1557,8 +1557,8 @@ echo \"$data\"
(org-test-with-temp-text " #+begin_src emacs-lisp\n(+ 1 1)\n #+end_src"
(org-babel-execute-src-block)
(let ((case-fold-search t)) (search-forward "RESULTS"))
(list (org-get-indentation)
(progn (forward-line) (org-get-indentation))))))
(list (current-indentation)
(progn (forward-line) (current-indentation))))))
(should
(equal
'(2 2)
@ -1566,8 +1566,8 @@ echo \"$data\"
" #+name: block\n #+begin_src emacs-lisp\n(+ 1 1)\n #+end_src"
(org-babel-execute-src-block)
(let ((case-fold-search t)) (search-forward "RESULTS"))
(list (org-get-indentation)
(progn (forward-line) (org-get-indentation))))))
(list (current-indentation)
(progn (forward-line) (current-indentation))))))
;; Don't get fooled by TAB-based indentation.
(should
(equal
@ -1577,8 +1577,8 @@ echo \"$data\"
(setq tab-width 4)
(org-babel-execute-src-block)
(let ((case-fold-search t)) (search-forward "RESULTS"))
(list (org-get-indentation)
(progn (forward-line) (org-get-indentation))))))
(list (current-indentation)
(progn (forward-line) (current-indentation))))))
;; Properly indent examplified blocks.
(should
(equal

View File

@ -1522,6 +1522,7 @@
(should
(org-test-with-temp-text ":MYDRAWER:\n- a\n:END:"
(forward-line)
(org-fold-reveal)
(org-meta-return)
(beginning-of-line)
(looking-at "- $"))))
@ -2943,6 +2944,7 @@ Foo Bar
(let ((org-custom-properties '("FOO" "BAR")))
(org-test-with-temp-text
"* H\n:PROPERTIES:\n<point>:FOO: val\n:P: 1\n:BAR: baz\n:END:\n"
(org-fold-reveal)
(org-toggle-custom-properties-visibility)
(and (org-invisible-p2)
(not (progn (forward-line) (org-invisible-p2)))
@ -2963,6 +2965,7 @@ Foo Bar
(let ((org-custom-properties '("A")))
(org-test-with-temp-text
"* H\n:PROPERTIES:\n:A: 1\n:END:\n\n:PROPERTIES:\n<point>:A: 2\n:END:"
(org-fold-reveal)
(org-toggle-custom-properties-visibility)
(org-invisible-p2)))))