summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicolas Goaziou <mail@nicolasgoaziou.fr>2016-10-28 00:35:01 +0200
committerNicolas Goaziou <mail@nicolasgoaziou.fr>2016-12-10 00:32:14 +0100
commit68d8f860cd0b898a02f60ec3b4781f4ddf7d2627 (patch)
treeac00329e36095e81e7c21f8f8a3a2b55a7de202b
parent41a5a660722e849ac72dd90b56b8db47bf1af9ec (diff)
downloadorg-mode-68d8f860cd0b898a02f60ec3b4781f4ddf7d2627.tar.gz
Improve blank line handling in `org-insert-heading'
* lisp/org.el (org--blank-before-heading-p): New function. (org-insert-heading): Use previous function. Major refactoring. * testing/lisp/test-org.el (test-org/insert-heading): (test-org/insert-todo-heading-respect-content): Update tests.
-rw-r--r--lisp/org.el233
-rw-r--r--testing/lisp/test-org.el43
2 files changed, 114 insertions, 162 deletions
diff --git a/lisp/org.el b/lisp/org.el
index 88c5fd5..386e641 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -7914,6 +7914,29 @@ When NEXT is non-nil, check the next line instead."
When NEXT is non-nil, check the next line instead."
(org--line-empty-p 2))
+(defun org--blank-before-heading-p (&optional parent)
+ "Non-nil when an empty line should precede a new heading here.
+When optional argument PARENT is non-nil, consider parent
+headline instead of current one."
+ (pcase (assq 'heading org-blank-before-new-entry)
+ (`(heading . auto)
+ (save-excursion
+ (org-with-limited-levels
+ (unless (and (org-before-first-heading-p)
+ (not (outline-next-heading)))
+ (org-back-to-heading t)
+ (when parent (org-up-heading-safe))
+ (cond ((not (bobp))
+ (org-previous-line-empty-p))
+ ((outline-next-heading)
+ (org-previous-line-empty-p))
+ ;; Ignore trailing spaces on last buffer line.
+ ((progn (skip-chars-backward " \t") (bolp))
+ (org-previous-line-empty-p))
+ (t nil))))))
+ (`(heading . ,value) value)
+ (_ nil)))
+
(defun org-insert-heading (&optional arg invisible-ok top)
"Insert a new heading or an item with the same depth at point.
@@ -7946,151 +7969,77 @@ command.
When optional argument TOP is non-nil, insert a level 1 heading,
unconditionally."
(interactive "P")
- (let ((itemp (and (not top) (org-in-item-p)))
- (may-split (org-get-alist-option org-M-RET-may-split-line 'headline))
- (respect-content (or org-insert-heading-respect-content
- (equal arg '(4))))
- (initial-content ""))
-
+ (let ((blank? (org--blank-before-heading-p (equal arg '(16)))))
(cond
-
- ((or (= (buffer-size) 0)
- (and (not (save-excursion
- (and (ignore-errors (org-back-to-heading invisible-ok))
- (org-at-heading-p))))
- (or arg (not itemp))))
- ;; At beginning of buffer or so high up that only a heading
- ;; makes sense.
- (cond ((and (bolp) (not respect-content)) (insert "* "))
- ((not respect-content)
- (unless may-split (end-of-line))
- (insert "\n* "))
- ((re-search-forward org-outline-regexp-bol nil t)
- (beginning-of-line)
- (insert "* \n")
- (backward-char))
- (t (goto-char (point-max))
- (insert "\n* ")))
+ ((or org-insert-heading-respect-content
+ (member arg '((4) (16)))
+ (and (not invisible-ok)
+ (invisible-p (max (1- (point)) (point-min)))))
+ (let ((level (org-current-level)))
+ ;; Position point at the location of insertion.
+ (if (not level) ;before first headline
+ (org-with-limited-levels (outline-next-heading))
+ ;; Make sure we end up on a visible headline if INVISIBLE-OK
+ ;; is nil.
+ (org-with-limited-levels (org-back-to-heading invisible-ok))
+ (cond ((equal arg '(16))
+ (org-up-heading-safe)
+ (org-end-of-subtree t t))
+ (t
+ (org-end-of-subtree t t))))
+ (unless (bolp) (insert "\n")) ;ensure final newline
+ (unless (and blank? (org-previous-line-empty-p))
+ (org-N-empty-lines-before-current (if blank? 1 0)))
+ (insert (make-string (if (and level (not top)) level 1) ?*) " \n")
+ (forward-char -1)
+ (run-hooks 'org-insert-heading-hook)))
+ ;; At a headline...
+ ((org-at-heading-p)
+ (let ((level (if top 1 (org-current-level))))
+ (cond ((bolp)
+ (when blank? (save-excursion (insert "\n")))
+ (save-excursion (insert (make-string level ?*) " \n"))
+ (unless (and blank? (org-previous-line-empty-p))
+ (org-N-empty-lines-before-current (if blank? 1 0)))
+ (end-of-line))
+ ((and (org-get-alist-option org-M-RET-may-split-line 'headline)
+ (save-excursion
+ (beginning-of-line)
+ (looking-at org-complex-heading-regexp))
+ (org-pos-in-match-range (point) 4))
+ ;; Grab the text that should moved to the new headline.
+ ;; Preserve tags.
+ (let ((split (delete-and-extract-region (point) (match-end 4))))
+ (if (looking-at "[ \t]*$") (replace-match "")
+ (org-set-tags nil t))
+ (end-of-line)
+ (when blank? (insert "\n"))
+ (insert "\n" (make-string level ?*) " ")
+ (when (org-string-nw-p split) (insert split))
+ (insert "\n")
+ (forward-char -1)))
+ (t
+ (end-of-line)
+ (when blank? (insert "\n"))
+ (insert "\n" (make-string level ?*) " \n")
+ (forward-char -1)))
+ (run-hooks 'org-insert-heading-hook)))
+ ;; Within a plain list, call `org-insert-item'.
+ ((and (not top) (org-in-item-p)) (org-insert-item))
+ ;; On regular text, turn line into a headline or split, if
+ ;; appropriate.
+ ((bolp)
+ (insert "* ")
+ (unless (and blank? (org-previous-line-empty-p))
+ (org-N-empty-lines-before-current (if blank? 1 0)))
(run-hooks 'org-insert-heading-hook))
-
- ((and itemp (not (member arg '((4) (16)))) (org-insert-item)))
-
(t
- ;; Maybe move at the end of the subtree
- (when (equal arg '(16))
- (org-up-heading-safe)
- (org-end-of-subtree t))
- ;; Insert a heading
- (save-restriction
- (widen)
- (let* ((level nil)
- (on-heading (org-at-heading-p))
- (empty-line-p (if on-heading
- (org-previous-line-empty-p)
- ;; We will decide later
- nil))
- ;; Get a level string to fall back on.
- (fix-level
- (if (org-before-first-heading-p) "*"
- (save-excursion
- (org-back-to-heading t)
- (when (org-previous-line-empty-p) (setq empty-line-p t))
- (looking-at org-outline-regexp)
- (make-string (1- (length (match-string 0))) ?*))))
- (stars
- (save-excursion
- (condition-case nil
- (if top "* "
- (org-back-to-heading invisible-ok)
- (when (and (not on-heading)
- (featurep 'org-inlinetask)
- (integerp org-inlinetask-min-level)
- (>= (length (match-string 0))
- org-inlinetask-min-level))
- ;; Find a heading level before the inline
- ;; task.
- (while (and (setq level (org-up-heading-safe))
- (>= level org-inlinetask-min-level)))
- (if (org-at-heading-p)
- (org-back-to-heading invisible-ok)
- (error "This should not happen")))
- (unless (and (save-excursion
- (save-match-data
- (org-backward-heading-same-level
- 1 invisible-ok))
- (= (point) (match-beginning 0)))
- (not (org-next-line-empty-p)))
- (setq empty-line-p (or empty-line-p
- (org-previous-line-empty-p))))
- (match-string 0))
- (error (or fix-level "* ")))))
- (blank-a (cdr (assq 'heading org-blank-before-new-entry)))
- (blank (if (eq blank-a 'auto) empty-line-p blank-a)))
-
- ;; If we insert after content, move there and clean up
- ;; whitespace.
- (when respect-content
- (if (not (org-before-first-heading-p))
- (org-end-of-subtree nil t)
- (re-search-forward org-outline-regexp-bol)
- (beginning-of-line 0))
- (skip-chars-backward " \r\t\n")
- (and (not (looking-back "^\\*+" (line-beginning-position)))
- (looking-at "[ \t]+") (replace-match ""))
- (unless (eobp) (forward-char 1))
- (when (looking-at "^\\*")
- (unless (bobp) (backward-char 1))
- (insert "\n")))
-
- ;; If we are splitting, grab the text that should be moved
- ;; to the new headline.
- (when may-split
- (if (org-at-heading-p)
- ;; This is a heading: split intelligently (keeping
- ;; tags).
- (let ((pos (point)))
- (beginning-of-line)
- (let ((case-fold-search nil))
- (unless (looking-at org-complex-heading-regexp)
- (error "This should not happen")))
- (when (and (match-beginning 4)
- (> pos (match-beginning 4))
- (< pos (match-end 4)))
- (setq initial-content (buffer-substring pos (match-end 4)))
- (goto-char pos)
- (delete-region (point) (match-end 4))
- (if (looking-at "[ \t]*$")
- (replace-match "")
- (insert (make-string (length initial-content) ?\s)))
- (setq initial-content (org-trim initial-content)))
- (goto-char pos))
- ;; A normal line.
- (setq initial-content
- (org-trim
- (delete-and-extract-region (point) (line-end-position))))))
-
- ;; If we are at the beginning of the line, insert before it.
- ;; Otherwise, after it.
- (cond
- ((and (bolp) (looking-at "[ \t]*$")))
- ((bolp) (save-excursion (insert "\n")))
- (t (end-of-line)
- (insert "\n")))
-
- ;; Insert the new heading
- (insert stars)
- (just-one-space)
- (insert initial-content)
- (unless (and blank (org-previous-line-empty-p))
- (org-N-empty-lines-before-current (if blank 1 0)))
- ;; Adjust visibility, which may be messed up if we removed
- ;; blank lines while previous entry was hidden.
- (let ((bol (line-beginning-position)))
- (dolist (o (overlays-at (1- bol)))
- (when (and (eq (overlay-get o 'invisible) 'outline)
- (eq (overlay-end o) bol))
- (move-overlay o (overlay-start o) (1- bol)))))
- (run-hooks 'org-insert-heading-hook)))))))
+ (unless (org-get-alist-option org-M-RET-may-split-line 'headline)
+ (end-of-line))
+ (insert "\n* ")
+ (unless (and blank? (org-previous-line-empty-p))
+ (org-N-empty-lines-before-current (if blank? 1 0)))
+ (run-hooks 'org-insert-heading-hook)))))
(defun org-N-empty-lines-before-current (N)
"Make the number of empty lines before current exactly N.
diff --git a/testing/lisp/test-org.el b/testing/lisp/test-org.el
index 99d905e..5aa4460 100644
--- a/testing/lisp/test-org.el
+++ b/testing/lisp/test-org.el
@@ -1182,13 +1182,13 @@
(buffer-string))))
;; In the middle of a headline, split it if allowed.
(should
- (equal "* H\n* 1"
+ (equal "* H\n* 1\n"
(org-test-with-temp-text "* H<point>1"
(let ((org-M-RET-may-split-line '((headline . t))))
(org-insert-heading))
(buffer-string))))
(should
- (equal "* H1\n* "
+ (equal "* H1\n* \n"
(org-test-with-temp-text "* H<point>1"
(let ((org-M-RET-may-split-line '((headline . nil))))
(org-insert-heading))
@@ -1196,19 +1196,19 @@
;; However, splitting cannot happen on TODO keywords, priorities or
;; tags.
(should
- (equal "* TODO H1\n* "
+ (equal "* TODO H1\n* \n"
(org-test-with-temp-text "* TO<point>DO H1"
(let ((org-M-RET-may-split-line '((headline . t))))
(org-insert-heading))
(buffer-string))))
(should
- (equal "* [#A] H1\n* "
+ (equal "* [#A] H1\n* \n"
(org-test-with-temp-text "* [#<point>A] H1"
(let ((org-M-RET-may-split-line '((headline . t))))
(org-insert-heading))
(buffer-string))))
(should
- (equal "* H1 :tag:\n* "
+ (equal "* H1 :tag:\n* \n"
(org-test-with-temp-text "* H1 :ta<point>g:"
(let ((org-M-RET-may-split-line '((headline . t))))
(org-insert-heading))
@@ -1223,13 +1223,13 @@
(org-insert-heading))
(buffer-string))))
(should
- (equal "* H\n- item\n- item 2\n* "
+ (equal "* H\n- item\n- item 2\n* \n"
(org-test-with-temp-text "* H\n- item<point>\n- item 2"
(let ((org-insert-heading-respect-content nil))
(org-insert-heading '(4)))
(buffer-string))))
(should
- (equal "* H\n- item\n* "
+ (equal "* H\n- item\n* \n"
(org-test-with-temp-text "* H\n- item"
(org-cycle)
(goto-char (point-max))
@@ -1252,14 +1252,14 @@
;; point.
(should
(equal
- "* H1\n** H2\n* "
+ "* H1\n** H2\n* \n"
(org-test-with-temp-text "* H1\n** H2"
(let ((org-insert-heading-respect-content nil))
(org-insert-heading '(4)))
(buffer-string))))
(should
(equal
- "* H1\n** H2\n* "
+ "* H1\n** H2\n* \n"
(org-test-with-temp-text "* H<point>1\n** H2"
(let ((org-insert-heading-respect-content nil))
(org-insert-heading '(4)))
@@ -1267,7 +1267,7 @@
;; When called with two universal arguments, insert a new headline
;; at the end of the grandparent subtree.
(should
- (equal "* H1\n** H3\n- item\n** H2\n** "
+ (equal "* H1\n** H3\n- item\n** H2\n** \n"
(org-test-with-temp-text "* H1\n** H3\n- item<point>\n** H2"
(let ((org-insert-heading-respect-content nil))
(org-insert-heading '(16)))
@@ -1275,7 +1275,7 @@
;; When optional TOP-LEVEL argument is non-nil, always insert
;; a level 1 heading.
(should
- (equal "* H1\n** H2\n* "
+ (equal "* H1\n** H2\n* \n"
(org-test-with-temp-text "* H1\n** H2<point>"
(org-insert-heading nil nil t)
(buffer-string))))
@@ -1286,7 +1286,7 @@
(buffer-string))))
;; Corner case: correctly insert a headline after an empty one.
(should
- (equal "* \n* "
+ (equal "* \n* \n"
(org-test-with-temp-text "* <point>"
(org-insert-heading)
(buffer-string)))))
@@ -1300,16 +1300,19 @@
(nth 2 (org-heading-components))))
;; Add headline at the end of the first subtree
(should
- (org-test-with-temp-text "* H1\nH1Body\n** H2\nH2Body"
- (search-forward "H1Body")
- (org-insert-todo-heading-respect-content)
- (and (eobp) (org-at-heading-p))))
+ (equal
+ "* TODO \n"
+ (org-test-with-temp-text "* H1\nH1Body<point>\n** H2\nH2Body"
+ (org-insert-todo-heading-respect-content)
+ (buffer-substring-no-properties (line-beginning-position) (point-max)))))
;; In a list, do not create a new item.
(should
- (org-test-with-temp-text "* H\n- an item\n- another one"
- (search-forward "an ")
- (org-insert-todo-heading-respect-content)
- (and (eobp) (org-at-heading-p)))))
+ (equal
+ "* TODO \n"
+ (org-test-with-temp-text "* H\n- an item\n- another one"
+ (search-forward "an ")
+ (org-insert-todo-heading-respect-content)
+ (buffer-substring-no-properties (line-beginning-position) (point-max))))))
(ert-deftest test-org/clone-with-time-shift ()
"Test `org-clone-subtree-with-time-shift'."