Browse Source

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.
Ihor Radchenko 3 years ago
parent
commit
f63ff07441
6 changed files with 367 additions and 340 deletions
  1. 61 55
      lisp/org-clock.el
  2. 2 10
      lisp/org-macs.el
  3. 292 268
      lisp/org.el
  4. 3 1
      lisp/ox.el
  5. 6 6
      testing/lisp/test-ob.el
  6. 3 0
      testing/lisp/test-org.el

+ 61 - 55
lisp/org-clock.el

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

+ 2 - 10
lisp/org-macs.el

@@ -170,16 +170,8 @@
        (when local-variables
        (when local-variables
 	 (org-with-wide-buffer
 	 (org-with-wide-buffer
 	  (goto-char (point-max))
 	  (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)))))))))
+	  (unless (bolp) (insert "\n"))
+	  (insert local-variables))))))
 
 
 (defmacro org-no-popups (&rest body)
 (defmacro org-no-popups (&rest body)
   "Suppress popup windows and evaluate BODY."
   "Suppress popup windows and evaluate BODY."

+ 292 - 268
lisp/org.el

@@ -6411,7 +6411,7 @@ odd number.  Returns values greater than 0."
        (replace-match "# " nil t))
        (replace-match "# " nil t))
       ((= level 1)
       ((= level 1)
        (user-error "Cannot promote to level 0.  UNDO to recover if necessary"))
        (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)
      (unless (= level 1)
        (when org-auto-align-tags (org-align-tags))
        (when org-auto-align-tags (org-align-tags))
        (when org-adapt-indentation (org-fixup-indentation (- diff))))
        (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)))
 	  (level (save-match-data (funcall outline-level)))
 	  (down-head (concat (make-string (org-get-valid-level level 1) ?*) " "))
 	  (down-head (concat (make-string (org-get-valid-level level 1) ?*) " "))
 	  (diff (abs (- level (length down-head) -1))))
 	  (diff (abs (- level (length down-head) -1))))
-     (replace-match down-head nil t)
-     (when org-auto-align-tags (org-align-tags))
-     (when org-adapt-indentation (org-fixup-indentation diff))
+     (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)))
      (run-hooks 'org-after-demote-entry-hook))))
      (run-hooks 'org-after-demote-entry-hook))))
 
 
 (defun org-cycle-level ()
 (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)
 			     this org-state block-reason)
 		    (throw 'exit nil)))))
 		    (throw 'exit nil)))))
 	    (store-match-data match-data)
 	    (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))
 	    (cond ((and org-state (equal this org-state))
 		   (message "TODO state was already %s" (org-trim next)))
 		   (message "TODO state was already %s" (org-trim next)))
 		  ((not (pos-visible-in-window-p hl-pos))
 		  ((not (pos-visible-in-window-p hl-pos))
@@ -9697,81 +9706,82 @@ of `org-todo-keywords-1'."
   "Insert DEADLINE or SCHEDULE information in current entry.
   "Insert DEADLINE or SCHEDULE information in current entry.
 TYPE is either `deadline' or `scheduled'.  See `org-deadline' or
 TYPE is either `deadline' or `scheduled'.  See `org-deadline' or
 `org-schedule' for information about ARG and TIME arguments."
 `org-schedule' for information about ARG and TIME arguments."
-  (let* ((deadline? (eq type 'deadline))
-	 (keyword (if deadline? org-deadline-string org-scheduled-string))
-	 (log (if deadline? org-log-redeadline org-log-reschedule))
-	 (old-date (org-entry-get nil (if deadline? "DEADLINE" "SCHEDULED")))
-	 (old-date-time (and old-date (org-time-string-to-time old-date)))
-	 ;; Save repeater cookie from either TIME or current scheduled
-	 ;; time stamp.  We are going to insert it back at the end of
-	 ;; the process.
-	 (repeater (or (and (org-string-nw-p time)
-			    ;; We use `org-repeat-re' because we need
-			    ;; to tell the difference between a real
-			    ;; repeater and a time delta, e.g. "+2d".
-			    (string-match org-repeat-re time)
-			    (match-string 1 time))
-		       (and (org-string-nw-p old-date)
-			    (string-match "\\([.+-]+[0-9]+[hdwmy]\
+  (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))
+	     (old-date (org-entry-get nil (if deadline? "DEADLINE" "SCHEDULED")))
+	     (old-date-time (and old-date (org-time-string-to-time old-date)))
+	     ;; Save repeater cookie from either TIME or current scheduled
+	     ;; time stamp.  We are going to insert it back at the end of
+	     ;; the process.
+	     (repeater (or (and (org-string-nw-p time)
+			        ;; We use `org-repeat-re' because we need
+			        ;; to tell the difference between a real
+			        ;; repeater and a time delta, e.g. "+2d".
+			        (string-match org-repeat-re time)
+			        (match-string 1 time))
+		           (and (org-string-nw-p old-date)
+			        (string-match "\\([.+-]+[0-9]+[hdwmy]\
 \\(?:[/ ][-+]?[0-9]+[hdwmy]\\)?\\)"
 \\(?:[/ ][-+]?[0-9]+[hdwmy]\\)?\\)"
-					  old-date)
-			    (match-string 1 old-date)))))
-    (pcase arg
-      (`(4)
-       (if (not old-date)
-	   (message (if deadline? "Entry had no deadline to remove"
-		      "Entry was not scheduled"))
-	 (when (and old-date log)
-	   (org-add-log-setup (if deadline? 'deldeadline 'delschedule)
-			      nil old-date log))
-	 (org-remove-timestamp-with-keyword keyword)
-	 (message (if deadline? "Entry no longer has a deadline."
-		    "Entry is no longer scheduled."))))
-      (`(16)
-       (save-excursion
-	 (org-back-to-heading t)
-	 (let ((regexp (if deadline? org-deadline-time-regexp
-			 org-scheduled-time-regexp)))
-	   (if (not (re-search-forward regexp (line-end-position 2) t))
-	       (user-error (if deadline? "No deadline information to update"
-			     "No scheduled information to update"))
-	     (let* ((rpl0 (match-string 1))
-		    (rpl (replace-regexp-in-string " -[0-9]+[hdwmy]" "" rpl0))
-		    (msg (if deadline? "Warn starting from" "Delay until")))
-	       (replace-match
-		(concat keyword
-			" <" rpl
-			(format " -%dd"
-				(abs (- (time-to-days
-					 (save-match-data
-					   (org-read-date
-					    nil t nil msg old-date-time)))
-					(time-to-days old-date-time))))
-			">") t t))))))
-      (_
-       (org-add-planning-info type time 'closed)
-       (when (and old-date
-		  log
-		  (not (equal old-date org-last-inserted-timestamp)))
-	 (org-add-log-setup (if deadline? 'redeadline 'reschedule)
-			    org-last-inserted-timestamp
-			    old-date
-			    log))
-       (when repeater
-	 (save-excursion
-	   (org-back-to-heading t)
-	   (when (re-search-forward
-		  (concat keyword " " org-last-inserted-timestamp)
-		  (line-end-position 2)
-		  t)
-	     (goto-char (1- (match-end 0)))
-	     (insert " " 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)))))
+					      old-date)
+			        (match-string 1 old-date)))))
+        (pcase arg
+          (`(4)
+           (if (not old-date)
+	       (message (if deadline? "Entry had no deadline to remove"
+		          "Entry was not scheduled"))
+	     (when (and old-date log)
+	       (org-add-log-setup (if deadline? 'deldeadline 'delschedule)
+			       nil old-date log))
+	     (org-remove-timestamp-with-keyword keyword)
+	     (message (if deadline? "Entry no longer has a deadline."
+		        "Entry is no longer scheduled."))))
+          (`(16)
+           (save-excursion
+	     (org-back-to-heading t)
+	     (let ((regexp (if deadline? org-deadline-time-regexp
+			     org-scheduled-time-regexp)))
+	       (if (not (re-search-forward regexp (line-end-position 2) t))
+	           (user-error (if deadline? "No deadline information to update"
+			         "No scheduled information to update"))
+	         (let* ((rpl0 (match-string 1))
+		        (rpl (replace-regexp-in-string " -[0-9]+[hdwmy]" "" rpl0))
+		        (msg (if deadline? "Warn starting from" "Delay until")))
+	           (replace-match
+		    (concat keyword
+			    " <" rpl
+			    (format " -%dd"
+				    (abs (- (time-to-days
+					     (save-match-data
+					       (org-read-date
+					        nil t nil msg old-date-time)))
+					    (time-to-days old-date-time))))
+			    ">") t t))))))
+          (_
+           (org-add-planning-info type time 'closed)
+           (when (and old-date
+		      log
+		      (not (equal old-date org-last-inserted-timestamp)))
+	     (org-add-log-setup (if deadline? 'redeadline 'reschedule)
+			     org-last-inserted-timestamp
+			     old-date
+			     log))
+           (when repeater
+	     (save-excursion
+	       (org-back-to-heading t)
+	       (when (re-search-forward
+		      (concat keyword " " org-last-inserted-timestamp)
+		      (line-end-position 2)
+		      t)
+	         (goto-char (1- (match-end 0)))
+	         (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))))))
 
 
 (defun org-deadline (arg &optional time)
 (defun org-deadline (arg &optional time)
   "Insert a \"DEADLINE:\" string with a timestamp to make a deadline.
   "Insert a \"DEADLINE:\" string with a timestamp to make a deadline.
@@ -9876,101 +9886,102 @@ among `closed', `deadline', `scheduled' and nil.  TIME indicates
 the time to use.  If none is given, the user is prompted for
 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
 a date.  REMOVE indicates what kind of entries to remove.  An old
 WHAT entry will also be removed."
 WHAT entry will also be removed."
-  (let (org-time-was-given org-end-time-was-given default-time default-input)
-    (when (and (memq what '(scheduled deadline))
-	       (or (not time)
-		   (and (stringp time)
-			(string-match "^[-+]+[0-9]" time))))
-      ;; Try to get a default date/time from existing timestamp
-      (save-excursion
-	(org-back-to-heading t)
-	(let ((end (save-excursion (outline-next-heading) (point))) ts)
-	  (when (re-search-forward (if (eq what 'scheduled)
-				       org-scheduled-time-regexp
-				     org-deadline-time-regexp)
-				   end t)
-	    (setq ts (match-string 1)
-		  default-time (org-time-string-to-time ts)
-		  default-input (and ts (org-get-compact-tod ts)))))))
-    (when what
-      (setq time
-	    (if (stringp time)
-		;; This is a string (relative or absolute), set
-		;; proper date.
-		(apply #'encode-time
-		       (org-read-date-analyze
-			time default-time (decode-time default-time)))
-	      ;; If necessary, get the time from the user
-	      (or time (org-read-date nil 'to-time nil
-				      (cl-case what
-					(deadline "DEADLINE")
-					(scheduled "SCHEDULED")
-					(otherwise nil))
-				      default-time default-input)))))
-    (org-with-wide-buffer
-     (org-back-to-heading t)
-     (let ((planning? (save-excursion
-			(forward-line)
-			(looking-at-p org-planning-line-re))))
-       (cond
-	(planning?
-	 (forward-line)
-	 ;; Move to current indentation.
-	 (skip-chars-forward " \t")
-	 ;; Check if we have to remove something.
-	 (dolist (type (if what (cons what remove) remove))
-	   (save-excursion
-	     (when (re-search-forward
-		    (cl-case type
-		      (closed org-closed-time-regexp)
-		      (deadline org-deadline-time-regexp)
-		      (scheduled org-scheduled-time-regexp)
-		      (otherwise (error "Invalid planning type: %s" type)))
-		    (line-end-position)
-		    t)
-	       ;; Delete until next keyword or end of line.
-	       (delete-region
-		(match-beginning 0)
-		(if (re-search-forward org-keyword-time-not-clock-regexp
-				       (line-end-position)
-				       t)
+  (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)
+		       (and (stringp time)
+			    (string-match "^[-+]+[0-9]" time))))
+          ;; Try to get a default date/time from existing timestamp
+          (save-excursion
+	    (org-back-to-heading t)
+	    (let ((end (save-excursion (outline-next-heading) (point))) ts)
+	      (when (re-search-forward (if (eq what 'scheduled)
+				           org-scheduled-time-regexp
+				         org-deadline-time-regexp)
+				       end t)
+	        (setq ts (match-string 1)
+		      default-time (org-time-string-to-time ts)
+		      default-input (and ts (org-get-compact-tod ts)))))))
+        (when what
+          (setq time
+	        (if (stringp time)
+		    ;; This is a string (relative or absolute), set
+		    ;; proper date.
+		    (apply #'encode-time
+		           (org-read-date-analyze
+			    time default-time (decode-time default-time)))
+	          ;; If necessary, get the time from the user
+	          (or time (org-read-date nil 'to-time nil
+				       (cl-case what
+				         (deadline "DEADLINE")
+				         (scheduled "SCHEDULED")
+				         (otherwise nil))
+				       default-time default-input)))))
+        (org-with-wide-buffer
+         (org-back-to-heading t)
+         (let ((planning? (save-excursion
+			    (forward-line)
+			    (looking-at-p org-planning-line-re))))
+           (cond
+	    (planning?
+	     (forward-line)
+	     ;; Move to current indentation.
+	     (skip-chars-forward " \t")
+	     ;; Check if we have to remove something.
+	     (dolist (type (if what (cons what remove) remove))
+	       (save-excursion
+	         (when (re-search-forward
+		        (cl-case type
+		          (closed org-closed-time-regexp)
+		          (deadline org-deadline-time-regexp)
+		          (scheduled org-scheduled-time-regexp)
+		          (otherwise (error "Invalid planning type: %s" type)))
+		        (line-end-position)
+		        t)
+	           ;; Delete until next keyword or end of line.
+	           (delete-region
 		    (match-beginning 0)
 		    (match-beginning 0)
-		  (line-end-position))))))
-	 ;; If there is nothing more to add and no more keyword is
-	 ;; left, remove the line completely.
-	 (if (and (looking-at-p "[ \t]*$") (not what))
-	     (delete-region (line-end-position 0)
-			    (line-end-position))
-	   ;; If we removed last keyword, do not leave trailing white
-	   ;; space at the end of line.
-	   (let ((p (point)))
-	     (save-excursion
-	       (end-of-line)
-	       (unless (= (skip-chars-backward " \t" p) 0)
-		 (delete-region (point) (line-end-position)))))))
-	(what
-	 (end-of-line)
-	 (insert "\n")
-	 (when org-adapt-indentation
-	   (indent-to-column (1+ (org-outline-level)))))
-	(t nil)))
-     (when what
-       ;; Insert planning keyword.
-       (insert (cl-case what
-		 (closed org-closed-string)
-		 (deadline org-deadline-string)
-		 (scheduled org-scheduled-string)
-		 (otherwise (error "Invalid planning type: %s" what)))
-	       " ")
-       ;; Insert associated timestamp.
-       (let ((ts (org-insert-time-stamp
-		  time
-		  (or org-time-was-given
-		      (and (eq what 'closed) org-log-done-with-time))
-		  (eq what 'closed)
-		  nil nil (list org-end-time-was-given))))
-	 (unless (eolp) (insert " "))
-	 ts)))))
+		    (if (re-search-forward org-keyword-time-not-clock-regexp
+				           (line-end-position)
+				           t)
+		        (match-beginning 0)
+		      (line-end-position))))))
+	     ;; If there is nothing more to add and no more keyword is
+	     ;; left, remove the line completely.
+	     (if (and (looking-at-p "[ \t]*$") (not what))
+	         (delete-region (line-end-position 0)
+			        (line-end-position))
+	       ;; If we removed last keyword, do not leave trailing white
+	       ;; space at the end of line.
+	       (let ((p (point)))
+	         (save-excursion
+	           (end-of-line)
+	           (unless (= (skip-chars-backward " \t" p) 0)
+		     (delete-region (point) (line-end-position)))))))
+	    (what
+	     (end-of-line)
+	     (insert-and-inherit "\n")
+	     (when org-adapt-indentation
+	       (indent-to-column (1+ (org-outline-level)))))
+	    (t nil)))
+         (when what
+           ;; Insert planning keyword.
+           (insert-and-inherit (cl-case what
+		                 (closed org-closed-string)
+		                 (deadline org-deadline-string)
+		                 (scheduled org-scheduled-string)
+		                 (otherwise (error "Invalid planning type: %s" what)))
+	                       " ")
+           ;; Insert associated timestamp.
+           (let ((ts (org-insert-time-stamp
+		      time
+		      (or org-time-was-given
+		          (and (eq what 'closed) org-log-done-with-time))
+		      (eq what 'closed)
+		      nil nil (list org-end-time-was-given))))
+	     (unless (eolp) (insert " "))
+	     ts))))))
 
 
 (defvar org-log-note-marker (make-marker)
 (defvar org-log-note-marker (make-marker)
   "Marker pointing at the entry where the note is to be inserted.")
   "Marker pointing at the entry where the note is to be inserted.")
@@ -10020,13 +10031,19 @@ narrowing."
 		 (throw 'exit nil))))
 		 (throw 'exit nil))))
 	   ;; No drawer found.  Create one, if permitted.
 	   ;; No drawer found.  Create one, if permitted.
 	   (when create
 	   (when create
-	     (unless (bolp) (insert "\n"))
-	     (let ((beg (point)))
-	       (insert ":" drawer ":\n:END:\n")
-	       (org-indent-region beg (point))
-	       (org-flag-region (line-end-position -1)
-                                (1- (point)) t 'outline))
-	     (end-of-line -1)))))
+             ;; 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-and-inherit ":" drawer ":\n:END:\n")
+	         (org-indent-region beg (point))
+	         (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
       (t
        (org-end-of-meta-data org-log-state-notes-insert-after-drawers)
        (org-end-of-meta-data org-log-state-notes-insert-after-drawers)
        (skip-chars-forward " \t\n")
        (skip-chars-forward " \t\n")
@@ -10034,7 +10051,7 @@ narrowing."
        (unless org-log-states-order-reversed
        (unless org-log-states-order-reversed
 	 (org-skip-over-state-notes)
 	 (org-skip-over-state-notes)
 	 (skip-chars-backward " \t\n")
 	 (skip-chars-backward " \t\n")
-	 (forward-line)))))
+	 (beginning-of-line 2)))))
    (if (bolp) (point) (line-beginning-position 2))))
    (if (bolp) (point) (line-beginning-position 2))))
 
 
 (defun org-add-log-setup (&optional purpose state prev-state how extra)
 (defun org-add-log-setup (&optional purpose state prev-state how extra)
@@ -10160,34 +10177,35 @@ EXTRA is additional text that will be inserted into the notes buffer."
       (push note lines))
       (push note lines))
     (when (and lines (not org-note-abort))
     (when (and lines (not org-note-abort))
       (with-current-buffer (marker-buffer org-log-note-marker)
       (with-current-buffer (marker-buffer org-log-note-marker)
-	(org-with-wide-buffer
-	 ;; Find location for the new note.
-	 (goto-char org-log-note-marker)
-	 (set-marker org-log-note-marker nil)
-	 ;; Note associated to a clock is to be located right after
-	 ;; the clock.  Do not move point.
-	 (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"))))
-	 ;; 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)))
-	   (if itemp
-	       (indent-line-to
-		(let ((struct (save-excursion
-				(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))
-	 (let ((ind (org-list-item-body-column (line-beginning-position))))
-	   (dolist (line lines)
-	     (insert "\n")
-	     (indent-line-to ind)
-	     (insert line)))
-	 (message "Note stored")
-	 (org-back-to-heading t)))))
+        (org-fold-core-ignore-modifications
+	    (org-with-wide-buffer
+	     ;; Find location for the new note.
+	     (goto-char org-log-note-marker)
+	     (set-marker org-log-note-marker nil)
+	     ;; Note associated to a clock is to be located right after
+	     ;; the clock.  Do not move point.
+	     (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-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)))
+	       (if itemp
+	           (indent-line-to
+		    (let ((struct (save-excursion
+				    (goto-char itemp) (org-list-struct))))
+		      (org-list-get-ind (org-list-get-top-point struct) struct)))
+	         (org-indent-line)))
+	     (insert-and-inherit (org-list-bullet-string "-") (pop lines))
+	     (let ((ind (org-list-item-body-column (line-beginning-position))))
+	       (dolist (line lines)
+	         (insert-and-inherit "\n")
+	         (indent-line-to ind)
+	         (insert-and-inherit line)))
+	     (message "Note stored")
+	     (org-back-to-heading t))))))
   ;; Don't add undo information when called from `org-agenda-todo'.
   ;; Don't add undo information when called from `org-agenda-todo'.
   (set-window-configuration org-log-note-window-configuration)
   (set-window-configuration org-log-note-window-configuration)
   (with-current-buffer (marker-buffer org-log-note-return-to)
   (with-current-buffer (marker-buffer org-log-note-return-to)
@@ -11318,34 +11336,35 @@ If TAGS is nil or the empty string, all tags are removed.
 
 
 This function assumes point is on a headline."
 This function assumes point is on a headline."
   (org-with-wide-buffer
   (org-with-wide-buffer
-   (let ((tags (pcase tags
-		 ((pred listp) tags)
-		 ((pred stringp) (split-string (org-trim tags) ":" t))
-		 (_ (error "Invalid tag specification: %S" tags))))
-	 (old-tags (org-get-tags nil t))
-	 (tags-change? nil))
-     (when (functionp org-tags-sort-function)
-       (setq tags (sort tags org-tags-sort-function)))
-     (setq tags-change? (not (equal tags old-tags)))
-     (when tags-change?
-       ;; Delete previous tags and any trailing white space.
-       (goto-char (if (org-match-line org-tag-line-re) (match-beginning 1)
-		    (line-end-position)))
-       (skip-chars-backward " \t")
-       (delete-region (point) (line-end-position))
-       ;; Deleting white spaces may break an otherwise empty headline.
-       ;; Re-introduce one space in this case.
-       (unless (org-at-heading-p) (insert " "))
-       (when tags
-	 (save-excursion (insert " " (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))))
-     ;; Align tags, if any.
-     (when tags (org-align-tags))
-     (when tags-change? (run-hooks 'org-after-tags-change-hook)))))
+   (org-fold-core-ignore-modifications
+       (let ((tags (pcase tags
+		     ((pred listp) tags)
+		     ((pred stringp) (split-string (org-trim tags) ":" t))
+		     (_ (error "Invalid tag specification: %S" tags))))
+	     (old-tags (org-get-tags nil t))
+	     (tags-change? nil))
+         (when (functionp org-tags-sort-function)
+           (setq tags (sort tags org-tags-sort-function)))
+         (setq tags-change? (not (equal tags old-tags)))
+         (when tags-change?
+           ;; Delete previous tags and any trailing white space.
+           (goto-char (if (org-match-line org-tag-line-re) (match-beginning 1)
+		        (line-end-position)))
+           (skip-chars-backward " \t")
+           (delete-region (point) (line-end-position))
+           ;; Deleting white spaces may break an otherwise empty headline.
+           ;; Re-introduce one space in this case.
+           (unless (org-at-heading-p) (insert " "))
+           (when 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-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))))))
 
 
 (defun org-change-tag-in-region (beg end tag off)
 (defun org-change-tag-in-region (beg end tag off)
   "Add or remove TAG for each entry in the region.
   "Add or remove TAG for each entry in the region.
@@ -12539,19 +12558,20 @@ decreases scheduled or deadline date by one day."
         ((member property org-special-properties)
         ((member property org-special-properties)
 	 (error "The %s property cannot be set with `org-entry-put'" property))
 	 (error "The %s property cannot be set with `org-entry-put'" property))
         (t
         (t
-	 (let* ((range (org-get-property-block beg 'force))
-	        (end (cdr range))
-	        (case-fold-search t))
-	   (goto-char (car range))
-	   (if (re-search-forward (org-re-property property nil t) end t)
-	       (progn (delete-region (match-beginning 0) (match-end 0))
-		      (goto-char (match-beginning 0)))
-	     (goto-char end)
-	     (insert "\n")
-	     (backward-char))
-	   (insert ":" property ":")
-	   (when value (insert " " value))
-	   (org-indent-line)))))
+         (org-fold-core-ignore-modifications
+	     (let* ((range (org-get-property-block beg 'force))
+	            (end (cdr range))
+	            (case-fold-search t))
+	       (goto-char (car range))
+	       (if (re-search-forward (org-re-property property nil t) end t)
+	           (progn (delete-region (match-beginning 0) (match-end 0))
+		          (goto-char (match-beginning 0)))
+	         (goto-char end)
+	         (insert-and-inherit "\n")
+	         (backward-char))
+	       (insert-and-inherit ":" property ":")
+	       (when value (insert-and-inherit " " value))
+	       (org-indent-line))))))
      (run-hook-with-args 'org-property-changed-functions property value))))
      (run-hook-with-args 'org-property-changed-functions property value))))
 
 
 (defun org-buffer-property-keys (&optional specials defaults columns)
 (defun org-buffer-property-keys (&optional specials defaults columns)
@@ -13705,23 +13725,24 @@ stamp will not contribute to the agenda.
 PRE and POST are optional strings to be inserted before and after the
 PRE and POST are optional strings to be inserted before and after the
 stamp.
 stamp.
 The command returns the inserted time stamp."
 The command returns the inserted time stamp."
-  (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 ""))
-    (when (listp extra)
-      (setq extra (car extra))
-      (if (and (stringp extra)
-	       (string-match "\\([0-9]+\\):\\([0-9]+\\)" extra))
-	  (setq extra (format "-%02d:%02d"
-			      (string-to-number (match-string 1 extra))
-			      (string-to-number (match-string 2 extra))))
-	(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)))
+  (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-and-inherit (or pre ""))
+        (when (listp extra)
+          (setq extra (car extra))
+          (if (and (stringp extra)
+	           (string-match "\\([0-9]+\\):\\([0-9]+\\)" extra))
+	      (setq extra (format "-%02d:%02d"
+			          (string-to-number (match-string 1 extra))
+			          (string-to-number (match-string 2 extra))))
+	    (setq extra nil)))
+        (when extra
+          (setq fmt (concat (substring fmt 0 -1) extra (substring fmt -1))))
+        (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 ()
 (defun org-toggle-time-stamp-overlays ()
   "Toggle the use of custom time stamp formats."
   "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)
       (let ((newtext (concat (match-string 4)
 	                     (org-trim
 	                     (org-trim
 	                      (format org-property-format (match-string 1) (match-string 3))))))
 	                      (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 ()
 (defun org-indent-line ()
   "Indent line depending on context.
   "Indent line depending on context.

+ 3 - 1
lisp/ox.el

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

+ 6 - 6
testing/lisp/test-ob.el

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

+ 3 - 0
testing/lisp/test-org.el

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