forked from mirrors/org-mode
org-list: Fix infloop when inserting item in post sub-list area
* lisp/org-list.el (org-list-insert-item): Do not infloop when insertion happens in an item, after a sub-list. Small refactoring. * testing/lisp/test-org-list.el (test-org-list/insert-item): Add tests.
This commit is contained in:
parent
acd1635966
commit
1ae1f8f2df
235
lisp/org-list.el
235
lisp/org-list.el
|
@ -1233,125 +1233,122 @@ after the bullet. Cursor will be after this text once the
|
||||||
function ends.
|
function ends.
|
||||||
|
|
||||||
This function modifies STRUCT."
|
This function modifies STRUCT."
|
||||||
(let ((case-fold-search t))
|
(let* ((case-fold-search t)
|
||||||
;; 1. Get information about list: ITEM containing POS, position of
|
;; Get information about list: ITEM containing POS, position
|
||||||
;; point with regards to item start (BEFOREP), blank lines
|
;; of point with regards to item start (BEFOREP), blank lines
|
||||||
;; number separating items (BLANK-NB), if we're allowed to
|
;; number separating items (BLANK-NB), if we're allowed to
|
||||||
;; (SPLIT-LINE-P).
|
;; (SPLIT-LINE-P).
|
||||||
(let* ((item (goto-char (catch :exit
|
(item
|
||||||
(let ((inner-item 0))
|
(catch :exit
|
||||||
(pcase-dolist (`(,i . ,_) struct)
|
;; Do not initialize I as top item as we don't know if the
|
||||||
(cond
|
;; list is correctly structured.
|
||||||
((= i pos) (throw :exit i))
|
(let ((i nil))
|
||||||
((< i pos) (setq inner-item i))
|
(pcase-dolist (`(,start ,_ ,_ ,_ ,_ ,_ ,end) struct)
|
||||||
(t (throw :exit inner-item))))
|
(cond
|
||||||
inner-item))))
|
((> start pos) (throw :exit i))
|
||||||
(item-end (org-list-get-item-end item struct))
|
((< end pos) nil) ;skip sub-lists before point
|
||||||
(item-end-no-blank (org-list-get-item-end-before-blank item struct))
|
(t (setq i start))))
|
||||||
(beforep
|
(or i (org-list-get-top-point struct)))))
|
||||||
(progn
|
(item-end (org-list-get-item-end item struct))
|
||||||
(looking-at org-list-full-item-re)
|
(item-end-no-blank (org-list-get-item-end-before-blank item struct))
|
||||||
(<= pos
|
(beforep
|
||||||
(cond
|
(progn
|
||||||
((not (match-beginning 4)) (match-end 0))
|
(goto-char item)
|
||||||
;; Ignore tag in a non-descriptive list.
|
(looking-at org-list-full-item-re)
|
||||||
((save-match-data (string-match "[.)]" (match-string 1)))
|
(<= pos
|
||||||
(match-beginning 4))
|
(cond
|
||||||
(t (save-excursion
|
((not (match-beginning 4)) (match-end 0))
|
||||||
(goto-char (match-end 4))
|
;; Ignore tag in a non-descriptive list.
|
||||||
(skip-chars-forward " \t")
|
((save-match-data (string-match "[.)]" (match-string 1)))
|
||||||
(point)))))))
|
(match-beginning 4))
|
||||||
(split-line-p (org-get-alist-option org-M-RET-may-split-line 'item))
|
(t (save-excursion
|
||||||
(blank-nb (org-list-separating-blank-lines-number
|
(goto-char (match-end 4))
|
||||||
pos struct prevs))
|
(skip-chars-forward " \t")
|
||||||
;; 2. Build the new item to be created. Concatenate same
|
(point)))))))
|
||||||
;; bullet as item, checkbox, text AFTER-BULLET if
|
(split-line-p (org-get-alist-option org-M-RET-may-split-line 'item))
|
||||||
;; provided, and text cut from point to end of item
|
(blank-nb (org-list-separating-blank-lines-number pos struct prevs))
|
||||||
;; (TEXT-CUT) to form item's BODY. TEXT-CUT depends on
|
;; Build the new item to be created. Concatenate same bullet
|
||||||
;; BEFOREP and SPLIT-LINE-P. The difference of size
|
;; as item, checkbox, text AFTER-BULLET if provided, and text
|
||||||
;; between what was cut and what was inserted in buffer
|
;; cut from point to end of item (TEXT-CUT) to form item's
|
||||||
;; is stored in SIZE-OFFSET.
|
;; BODY. TEXT-CUT depends on BEFOREP and SPLIT-LINE-P. The
|
||||||
(ind (org-list-get-ind item struct))
|
;; difference of size between what was cut and what was
|
||||||
(ind-size (if indent-tabs-mode
|
;; inserted in buffer is stored in SIZE-OFFSET.
|
||||||
(+ (/ ind tab-width) (mod ind tab-width))
|
(ind (org-list-get-ind item struct))
|
||||||
ind))
|
(ind-size (if indent-tabs-mode
|
||||||
(bullet (org-list-bullet-string (org-list-get-bullet item struct)))
|
(+ (/ ind tab-width) (mod ind tab-width))
|
||||||
(box (when checkbox "[ ]"))
|
ind))
|
||||||
(text-cut
|
(bullet (org-list-bullet-string (org-list-get-bullet item struct)))
|
||||||
(and (not beforep) split-line-p
|
(box (and checkbox "[ ]"))
|
||||||
(progn
|
(text-cut
|
||||||
(goto-char pos)
|
(and (not beforep)
|
||||||
;; If POS is greater than ITEM-END, then point is
|
split-line-p
|
||||||
;; in some white lines after the end of the list.
|
(progn
|
||||||
;; Those must be removed, or they will be left,
|
(goto-char pos)
|
||||||
;; stacking up after the list.
|
;; If POS is greater than ITEM-END, then point is in
|
||||||
(when (< item-end pos)
|
;; some white lines after the end of the list. Those
|
||||||
(delete-region (1- item-end) (point-at-eol)))
|
;; must be removed, or they will be left, stacking up
|
||||||
(skip-chars-backward " \r\t\n")
|
;; after the list.
|
||||||
(setq pos (point))
|
(when (< item-end pos)
|
||||||
(delete-and-extract-region pos item-end-no-blank))))
|
(delete-region (1- item-end) (point-at-eol)))
|
||||||
(body (concat bullet (when box (concat box " ")) after-bullet
|
(skip-chars-backward " \r\t\n")
|
||||||
(and text-cut
|
(setq pos (point))
|
||||||
(if (string-match "\\`[ \t]+" text-cut)
|
(delete-and-extract-region pos item-end-no-blank))))
|
||||||
(replace-match "" t t text-cut)
|
(body
|
||||||
text-cut))))
|
(concat bullet
|
||||||
(item-sep (make-string (1+ blank-nb) ?\n))
|
(and box (concat box " "))
|
||||||
(item-size (+ ind-size (length body) (length item-sep)))
|
after-bullet
|
||||||
(size-offset (- item-size (length text-cut))))
|
(and text-cut
|
||||||
;; 4. Insert effectively item into buffer.
|
(if (string-match "\\`[ \t]+" text-cut)
|
||||||
(goto-char item)
|
(replace-match "" t t text-cut)
|
||||||
(indent-to-column ind)
|
text-cut))))
|
||||||
(insert body item-sep)
|
(item-sep (make-string (1+ blank-nb) ?\n))
|
||||||
;; 5. Add new item to STRUCT.
|
(item-size (+ ind-size (length body) (length item-sep)))
|
||||||
(mapc (lambda (e)
|
(size-offset (- item-size (length text-cut))))
|
||||||
(let ((p (car e)) (end (nth 6 e)))
|
;; Insert effectively item into buffer.
|
||||||
(cond
|
(goto-char item)
|
||||||
;; Before inserted item, positions don't change but
|
(indent-to-column ind)
|
||||||
;; an item ending after insertion has its end shifted
|
(insert body item-sep)
|
||||||
;; by SIZE-OFFSET.
|
;; Add new item to STRUCT.
|
||||||
((< p item)
|
(dolist (e struct)
|
||||||
(when (> end item) (setcar (nthcdr 6 e) (+ end size-offset))))
|
(let ((p (car e)) (end (nth 6 e)))
|
||||||
;; Trivial cases where current item isn't split in
|
(cond
|
||||||
;; two. Just shift every item after new one by
|
;; Before inserted item, positions don't change but an item
|
||||||
;; ITEM-SIZE.
|
;; ending after insertion has its end shifted by SIZE-OFFSET.
|
||||||
((or beforep (not split-line-p))
|
((< p item)
|
||||||
(setcar e (+ p item-size))
|
(when (> end item)
|
||||||
(setcar (nthcdr 6 e) (+ end item-size)))
|
(setcar (nthcdr 6 e) (+ end size-offset))))
|
||||||
;; Item is split in two: elements before POS are just
|
;; Item where insertion happens may be split in two parts.
|
||||||
;; shifted by ITEM-SIZE. In the case item would end
|
;; In this case, move start by ITEM-SIZE and end by
|
||||||
;; after split POS, ending is only shifted by
|
;; SIZE-OFFSET.
|
||||||
;; SIZE-OFFSET.
|
((and (= p item) (not beforep) split-line-p)
|
||||||
((< p pos)
|
(setcar e (+ p item-size))
|
||||||
(setcar e (+ p item-size))
|
(setcar (nthcdr 6 e) (+ end size-offset)))
|
||||||
(if (< end pos)
|
;; Items starting after modified item fall into two
|
||||||
(setcar (nthcdr 6 e) (+ end item-size))
|
;; categories. If item was split, and current item was
|
||||||
(setcar (nthcdr 6 e) (+ end size-offset))))
|
;; located after split point, it was moved to the new
|
||||||
;; Elements after POS are moved into new item.
|
;; item. Practically speaking, this means that the part
|
||||||
;; Length of ITEM-SEP has to be removed as ITEM-SEP
|
;; between body start of body and split point was removed.
|
||||||
;; doesn't appear in buffer yet.
|
;; So we compute the offset and shit item's positions
|
||||||
((< p item-end)
|
;; accordingly. In any other case, the item was simply
|
||||||
(setcar e (+ p size-offset (- item pos (length item-sep))))
|
;; shifted by ITEM-SIZE.
|
||||||
(if (= end item-end)
|
((and split-line-p (not beforep) (>= p pos))
|
||||||
(setcar (nthcdr 6 e) (+ item item-size))
|
(let ((offset (- pos item ind (length bullet) (length after-bullet))))
|
||||||
(setcar (nthcdr 6 e)
|
(setcar e (- p offset))
|
||||||
(+ end size-offset
|
(setcar (nthcdr 6 e) (- end offset))))
|
||||||
(- item pos (length item-sep))))))
|
(t
|
||||||
;; Elements at ITEM-END or after are only shifted by
|
(setcar e (+ p item-size))
|
||||||
;; SIZE-OFFSET.
|
(setcar (nthcdr 6 e) (+ end item-size))))))
|
||||||
(t (setcar e (+ p size-offset))
|
(push (list item ind bullet nil box nil (+ item item-size)) struct)
|
||||||
(setcar (nthcdr 6 e) (+ end size-offset))))))
|
(setq struct (sort struct #'car-less-than-car))
|
||||||
struct)
|
;; If not BEFOREP, new item must appear after ITEM, so exchange
|
||||||
(push (list item ind bullet nil box nil (+ item item-size)) struct)
|
;; ITEM with the next item in list. Position cursor after bullet,
|
||||||
(setq struct (sort struct (lambda (e1 e2) (< (car e1) (car e2)))))
|
;; counter, checkbox, and label.
|
||||||
;; 6. If not BEFOREP, new item must appear after ITEM, so
|
(if beforep
|
||||||
;; exchange ITEM with the next item in list. Position cursor
|
(goto-char item)
|
||||||
;; after bullet, counter, checkbox, and label.
|
(setq struct (org-list-swap-items item (+ item item-size) struct))
|
||||||
(if beforep
|
(goto-char (org-list-get-next-item
|
||||||
(goto-char item)
|
item struct (org-list-prevs-alist struct))))
|
||||||
(setq struct (org-list-swap-items item (+ item item-size) struct))
|
struct))
|
||||||
(goto-char (org-list-get-next-item
|
|
||||||
item struct (org-list-prevs-alist struct))))
|
|
||||||
struct)))
|
|
||||||
|
|
||||||
(defun org-list-delete-item (item struct)
|
(defun org-list-delete-item (item struct)
|
||||||
"Remove ITEM from the list and return the new structure.
|
"Remove ITEM from the list and return the new structure.
|
||||||
|
|
|
@ -607,9 +607,11 @@ b. Item 2<point>"
|
||||||
(should-error (org-move-item-up)))
|
(should-error (org-move-item-up)))
|
||||||
;; ... unless `org-list-use-circular-motion' is non-nil. In this
|
;; ... unless `org-list-use-circular-motion' is non-nil. In this
|
||||||
;; case, move to the first item.
|
;; case, move to the first item.
|
||||||
(org-test-with-temp-text "- item 1\n- item 2\n- item 3"
|
(should
|
||||||
(let ((org-list-use-circular-motion t)) (org-move-item-up))
|
(equal "- item 2\n- item 3\n- item 1"
|
||||||
(should (equal (buffer-string) "- item 2\n- item 3\n- item 1")))
|
(org-test-with-temp-text "- item 1\n- item 2\n- item 3"
|
||||||
|
(let ((org-list-use-circular-motion t)) (org-move-item-up))
|
||||||
|
(buffer-string))))
|
||||||
;; Preserve item visibility.
|
;; Preserve item visibility.
|
||||||
(org-test-with-temp-text "* Headline\n- item 1\n body 1\n- item 2\n body 2"
|
(org-test-with-temp-text "* Headline\n- item 1\n body 1\n- item 2\n body 2"
|
||||||
(let ((org-cycle-include-plain-lists t))
|
(let ((org-cycle-include-plain-lists t))
|
||||||
|
@ -784,7 +786,20 @@ b. Item 2<point>"
|
||||||
(org-cycle)
|
(org-cycle)
|
||||||
(org-insert-item)
|
(org-insert-item)
|
||||||
(list (get-char-property (line-beginning-position 0) 'invisible)
|
(list (get-char-property (line-beginning-position 0) 'invisible)
|
||||||
(get-char-property (line-end-position 2) 'invisible)))))))
|
(get-char-property (line-end-position 2) 'invisible))))))
|
||||||
|
;; Test insertion in area after a sub-list. In particular, if point
|
||||||
|
;; is right at the end of the previous sub-list, still insert
|
||||||
|
;; a sub-item in that list.
|
||||||
|
(should
|
||||||
|
(= 2
|
||||||
|
(org-test-with-temp-text "- item\n - sub-list\n<point> resume item"
|
||||||
|
(org-insert-item)
|
||||||
|
(current-indentation))))
|
||||||
|
(should
|
||||||
|
(= 0
|
||||||
|
(org-test-with-temp-text "- item\n - sub-list\n resume item<point>"
|
||||||
|
(org-insert-item)
|
||||||
|
(current-indentation)))))
|
||||||
|
|
||||||
(ert-deftest test-org-list/repair ()
|
(ert-deftest test-org-list/repair ()
|
||||||
"Test `org-list-repair' specifications."
|
"Test `org-list-repair' specifications."
|
||||||
|
|
Loading…
Reference in New Issue