summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicolas Goaziou <mail@nicolasgoaziou.fr>2018-10-23 23:28:38 +0200
committerNicolas Goaziou <mail@nicolasgoaziou.fr>2018-10-23 23:28:38 +0200
commit8da5168dd146505ead60087e334bfc078dddfcbe (patch)
treeaa982d7c76364291a26a373d14f954bf9a45ffd3
parentea47ed0d769627f9114cf916fe88c7b8db6ef68c (diff)
parent17e28d6467d146dc88eaa1ee461e34c395e28f6e (diff)
downloadorg-mode-8da5168dd146505ead60087e334bfc078dddfcbe.tar.gz
Merge branch 'master' into next
-rw-r--r--lisp/org-capture.el160
-rw-r--r--testing/lisp/test-org-capture.el192
2 files changed, 295 insertions, 57 deletions
diff --git a/lisp/org-capture.el b/lisp/org-capture.el
index 153b17f..bb83e07 100644
--- a/lisp/org-capture.el
+++ b/lisp/org-capture.el
@@ -1147,63 +1147,109 @@ may have been stored before."
(defun org-capture-place-item ()
"Place the template as a new plain list item."
- (let* ((txt (org-capture-get :template))
- (target-entry-p (org-capture-get :target-entry-p))
- (ind 0)
- beg end)
- (if (org-capture-get :exact-position)
- (goto-char (org-capture-get :exact-position))
- (cond
- ((not target-entry-p)
- ;; Insert as top-level entry, either at beginning or at end of file
- (setq beg (point-min) end (point-max)))
- (t
- (setq beg (1+ (point-at-eol))
- end (save-excursion (outline-next-heading) (point)))))
- (setq ind nil)
- (if (org-capture-get :prepend)
- (progn
- (goto-char beg)
- (when (org-list-search-forward (org-item-beginning-re) end t)
- (goto-char (match-beginning 0))
- (setq ind (current-indentation))))
- (goto-char end)
- (when (org-list-search-backward (org-item-beginning-re) beg t)
- (setq ind (current-indentation))
- (org-end-of-item)))
- (unless ind (goto-char end)))
- ;; Remove common indentation
- (setq txt (org-remove-indentation txt))
- ;; Make sure this is indeed an item
- (unless (string-match (concat "\\`" (org-item-re)) txt)
- (setq txt (concat "- "
- (mapconcat 'identity (split-string txt "\n")
- "\n "))))
- ;; Prepare surrounding empty lines.
- (unless (bolp) (insert "\n"))
- (org-capture-empty-lines-before)
- (setq beg (point))
- (unless (eolp) (save-excursion (insert "\n")))
- (unless ind
- (org-indent-line)
- (setq ind (current-indentation))
- (delete-region beg (point)))
- ;; Set the correct indentation, depending on context
- (setq ind (make-string ind ?\ ))
- (setq txt (concat ind
- (mapconcat 'identity (split-string txt "\n")
- (concat "\n" ind))
- "\n"))
- ;; Insert item.
- (insert txt)
- (org-capture-empty-lines-after)
- (org-capture-position-for-last-stored beg)
- (setq end (point))
- (org-capture-mark-kill-region beg end)
- (org-capture-narrow beg end)
- (when (or (re-search-backward "%\\?" beg t)
- (re-search-forward "%\\?" end t))
- (replace-match ""))))
+ (let ((prepend? (org-capture-get :prepend))
+ (template (org-remove-indentation (org-capture-get :template)))
+ item)
+ ;; Make template suitable for insertion. In particular, add
+ ;; a main bullet if it is missing.
+ (unless (string-match-p (concat "\\`" (org-item-re)) template)
+ (setq template (concat "- " (mapconcat #'identity
+ (split-string template "\n")
+ "\n "))))
+ ;; Delimit the area where we should look for a plain list.
+ (pcase-let ((`(,beg . ,end)
+ (cond ((org-capture-get :exact-position)
+ ;; User gave a specific position. Start
+ ;; looking for lists from here.
+ (cons (save-excursion
+ (goto-char (org-capture-get :exact-position))
+ (line-beginning-position))
+ (org-entry-end-position)))
+ ((org-capture-get :target-entry-p)
+ ;; At a heading, limit search to its body.
+ (cons (line-beginning-position 2)
+ (org-entry-end-position)))
+ (t
+ ;; Table is not necessarily under a heading.
+ ;; Search whole buffer.
+ (cons (point-min) (point-max))))))
+ ;; Find the first plain list in the delimited area.
+ (goto-char beg)
+ (let ((item-regexp (org-item-beginning-re)))
+ (catch :found
+ (while (re-search-forward item-regexp end t)
+ (when (setq item (org-element-lineage
+ (org-element-at-point) '(plain-list) t))
+ (goto-char (org-element-property (if prepend? :post-affiliated
+ :contents-end)
+ item))
+ (throw :found t)))
+ ;; No list found. Move to the location when to insert
+ ;; template.
+ (goto-char (if prepend? beg end)))))
+ ;; Insert template.
+ (let ((origin (point)))
+ (unless (bolp) (insert "\n"))
+ ;; When a new list is created, always obey to `:empty-lines' and
+ ;; friends.
+ ;;
+ ;; When capturing in an existing list, do not change blank lines
+ ;; above or below the list; consider it to be a stable
+ ;; structure. However, we can control how many blank lines
+ ;; separate items. So obey to `:empty-lines' between items as
+ ;; long as it does not insert more than one empty line. In the
+ ;; specific case of empty lines above, it means we only obey the
+ ;; parameter when appending an item.
+ (unless (and item prepend?)
+ (org-capture-empty-lines-before
+ (and item
+ (not prepend?)
+ (min 1 (or (org-capture-get :empty-lines-before)
+ (org-capture-get :empty-lines)
+ 0)))))
+ (org-capture-position-for-last-stored (point))
+ (let ((beg (line-beginning-position))
+ (end (progn
+ (insert (org-trim template) "\n")
+ (point-marker))))
+ (when item
+ (let ((i (save-excursion
+ (goto-char (org-element-property :post-affiliated item))
+ (current-indentation))))
+ (save-excursion
+ (goto-char beg)
+ (save-excursion
+ (while (< (point) end)
+ (indent-to i)
+ (forward-line)))
+ ;; Pre-pending an item could change the type of the list
+ ;; if there is a mismatch. In this situation,
+ ;; prioritize the existing list.
+ (when prepend?
+ (let ((ordered? (eq 'ordered (org-element-property :type item))))
+ (when (org-xor ordered?
+ (string-match-p "\\`[A-Za-z0-9]\\([.)]\\)"
+ template))
+ (org-cycle-list-bullet (if ordered? "1." "-")))))
+ ;; Eventually repair the list for proper indentation and
+ ;; bullets.
+ (org-list-repair))))
+ ;; Limit number of empty lines. See above for details.
+ (unless (and item (not prepend?))
+ (org-capture-empty-lines-after
+ (and item
+ prepend?
+ (min 1 (or (org-capture-get :empty-lines-after)
+ (org-capture-get :empty-lines)
+ 0)))))
+ (org-capture-mark-kill-region origin (point))
+ ;; ITEM always end with a newline character. Make sure we do
+ ;; not narrow at the beginning of the next line, possibly
+ ;; altering its structure (e.g., when it is a headline).
+ (org-capture-narrow beg (1- end))
+ (when (or (search-backward "%?" beg t)
+ (search-forward "%?" end t))
+ (replace-match ""))))))
(defun org-capture-place-table-line ()
"Place the template as a table line."
diff --git a/testing/lisp/test-org-capture.el b/testing/lisp/test-org-capture.el
index f53c154..9d9ef10 100644
--- a/testing/lisp/test-org-capture.el
+++ b/testing/lisp/test-org-capture.el
@@ -162,6 +162,15 @@
(org-capture-kill))
(buffer-string))))
(should
+ (equal "- A\n - B"
+ (org-test-with-temp-text-in-file "- A\n - B"
+ (let* ((file (buffer-file-name))
+ (org-capture-templates
+ `(("t" "Item" item (file ,file) "- X"))))
+ (org-capture nil "t")
+ (org-capture-kill))
+ (buffer-string))))
+ (should
(equal "| a |\n| b |"
(org-test-with-temp-text-in-file "| a |\n| b |"
(let* ((file (buffer-file-name))
@@ -199,6 +208,189 @@
(org-capture-finalize))
(buffer-string)))))
+(ert-deftest test-org-capture/item ()
+ "Test `item' type in capture template."
+ ;; Insert item in the first plain list found at the target location.
+ (should
+ (equal
+ "* A\n- list 1\n- X\n\n\n1. list 2"
+ (org-test-with-temp-text-in-file "* A\n- list 1\n\n\n1. list 2"
+ (let* ((file (buffer-file-name))
+ (org-capture-templates
+ `(("t" "Item" item (file+headline ,file "A") "- X"))))
+ (org-capture nil "t")
+ (org-capture-finalize))
+ (buffer-string))))
+ (should
+ (equal
+ "Text\n- list 1\n- X\n\n\n1. list 2"
+ (org-test-with-temp-text-in-file "Text\n- list 1\n\n\n1. list 2"
+ (let* ((file (buffer-file-name))
+ (org-capture-templates
+ `(("t" "Item" item (file ,file) "- X"))))
+ (org-capture nil "t")
+ (org-capture-finalize))
+ (buffer-string))))
+ ;; When targeting a specific location, start looking for plain lists
+ ;; from there.
+ (should
+ (equal
+ "* A\n- skip\n\n\n1. here\n2. X\n"
+ (org-test-with-temp-text-in-file "* A\n- skip\n\n\n1. here"
+ (let* ((file (buffer-file-name))
+ (org-capture-templates
+ `(("t" "Item" item (file+regexp ,file "here") "1. X"))))
+ (org-capture nil "t")
+ (org-capture-finalize))
+ (buffer-string))))
+ ;; If there is no such list, create it.
+ (should
+ (equal
+ "* A\n- X\n"
+ (org-test-with-temp-text-in-file "* A"
+ (let* ((file (buffer-file-name))
+ (org-capture-templates
+ `(("t" "Item" item (file+headline ,file "A") "- X"))))
+ (org-capture nil "t")
+ (org-capture-finalize))
+ (buffer-string))))
+ ;; When `:prepend' is non-nil, insert new item as the first item.
+ (should
+ (equal
+ "* A\n- X\n- 1\n- 2"
+ (org-test-with-temp-text-in-file "* A\n- 1\n- 2"
+ (let* ((file (buffer-file-name))
+ (org-capture-templates
+ `(("t" "Item" item (file+headline ,file "A") "- X"
+ :prepend t))))
+ (org-capture nil "t")
+ (org-capture-finalize))
+ (buffer-string))))
+ ;; When `:prepend' is nil, insert new item as the last top-level
+ ;; item.
+ (should
+ (equal
+ "* A\n- 1\n - 2\n- X\n"
+ (org-test-with-temp-text-in-file "* A\n- 1\n - 2"
+ (let* ((file (buffer-file-name))
+ (org-capture-templates
+ `(("t" "Item" item (file+headline ,file "A") "- X"))))
+ (org-capture nil "t")
+ (org-capture-finalize))
+ (buffer-string))))
+ ;; When targeting a specific location, one can insert in a sub-list.
+ (should
+ (equal
+ "* A\n- skip\n - here\n - X\n- skip"
+ (org-test-with-temp-text-in-file "* A\n- skip\n - here\n- skip"
+ (let* ((file (buffer-file-name))
+ (org-capture-templates
+ `(("t" "Item" item (file+regexp ,file "here") "- X"))))
+ (org-capture nil "t")
+ (org-capture-finalize))
+ (buffer-string))))
+ ;; Obey `:empty-lines' when creating a new list.
+ (should
+ (equal
+ "\n- X\n\n\n* H"
+ (org-test-with-temp-text-in-file "\n* H"
+ (let* ((file (buffer-file-name))
+ (org-capture-templates
+ `(("t" "Item" item (file ,file) "- X"
+ :empty-lines-before 1 :empty-lines-after 2 :prepend t))))
+ (org-capture nil "t")
+ (org-capture-finalize))
+ (buffer-string))))
+ ;; Obey `:empty-lines' in an existing list only between items, and
+ ;; only if the value doesn't break the list.
+ (should
+ (equal
+ "- A\n\n- X\nText"
+ (org-test-with-temp-text-in-file "- A\nText"
+ (let* ((file (buffer-file-name))
+ (org-capture-templates
+ `(("t" "Item" item (file ,file) "- X" :empty-lines 1))))
+ (org-capture nil "t")
+ (org-capture-finalize))
+ (buffer-string))))
+ (should
+ (equal
+ "Text\n- X\n\n- A"
+ (org-test-with-temp-text-in-file "Text\n- A"
+ (let* ((file (buffer-file-name))
+ (org-capture-templates
+ `(("t" "Item" item (file ,file) "- X"
+ :prepend t :empty-lines 1))))
+ (org-capture nil "t")
+ (org-capture-finalize))
+ (buffer-string))))
+ (should-not
+ (equal
+ "- A\n\n\n- X"
+ (org-test-with-temp-text-in-file "- A"
+ (let* ((file (buffer-file-name))
+ (org-capture-templates
+ `(("t" "Item" item (file ,file) "- X" :empty-lines 2))))
+ (org-capture nil "t")
+ (org-capture-finalize))
+ (buffer-string))))
+ ;; Preserve list type when pre-pending.
+ (should
+ (equal
+ "1. X\n2. A"
+ (org-test-with-temp-text-in-file "1. A"
+ (let* ((file (buffer-file-name))
+ (org-capture-templates
+ `(("t" "Item" item (file ,file) "- X" :prepend t))))
+ (org-capture nil "t")
+ (org-capture-finalize))
+ (buffer-string))))
+ ;; Handle indentation. Handle multi-lines templates.
+ (should
+ (equal
+ " - A\n - X\n"
+ (org-test-with-temp-text-in-file " - A"
+ (let* ((file (buffer-file-name))
+ (org-capture-templates
+ `(("t" "Item" item (file ,file) "- X"))))
+ (org-capture nil "t")
+ (org-capture-finalize))
+ (buffer-string))))
+ (should
+ (equal
+ " - A\n - X\n Line 2\n"
+ (org-test-with-temp-text-in-file " - A"
+ (let* ((file (buffer-file-name))
+ (org-capture-templates
+ `(("t" "Item" item (file ,file) "- X\n Line 2"))))
+ (org-capture nil "t")
+ (org-capture-finalize))
+ (buffer-string))))
+ ;; Handle incomplete templates.
+ (should
+ (equal
+ "- A\n- X\n"
+ (org-test-with-temp-text-in-file "- A"
+ (let* ((file (buffer-file-name))
+ (org-capture-templates
+ `(("t" "Item" item (file ,file) "X"))))
+ (org-capture nil "t")
+ (org-capture-finalize))
+ (buffer-string))))
+ ;; Do not break next headline.
+ (should-not
+ (equal
+ "- A\n- X\nFoo* H"
+ (org-test-with-temp-text-in-file "- A\n* H"
+ (let* ((file (buffer-file-name))
+ (org-capture-templates
+ `(("t" "Item" item (file ,file) "- X"))))
+ (org-capture nil "t")
+ (goto-char (point-max))
+ (insert "Foo")
+ (org-capture-finalize))
+ (buffer-string)))))
+
(ert-deftest test-org-capture/table-line ()
"Test `table-line' type in capture template."
;; When a only file is specified, use the first table available.