+(defconst org-item-beginning-re (concat "^" (org-item-re))
+ "Regexp matching the beginning of a plain list item.")
+
+(defun org-list-ending-between (min max &optional firstp)
+ "Find the position of a list ending between MIN and MAX, or nil.
+This function looks for `org-list-end-re' outside a block.
+
+If FIRSTP in non-nil, return the point at the beginning of the
+nearest valid terminator from MIN. Otherwise, return the point at
+the end of the nearest terminator from MAX."
+ (save-excursion
+ (let* ((start (if firstp min max))
+ (end (if firstp max min))
+ (search-fun (if firstp
+ #'org-search-forward-unenclosed
+ #'org-search-backward-unenclosed))
+ (list-end-p (progn
+ (goto-char start)
+ (funcall search-fun (org-list-end-re) end t))))
+ ;; Is there a valid list ending somewhere ?
+ (and list-end-p
+ ;; we want to be on the first line of the list ender
+ (match-beginning 0)))))
+
+(defun org-list-maybe-skip-block (search limit)
+ "Return non-nil value if point is in a block, skipping it on the way.
+It looks for the boundary of the block in SEARCH direction,
+stopping at LIMIT."
+ (save-match-data
+ (let ((case-fold-search t)
+ (boundary (if (eq search 're-search-forward) 3 5)))
+ (when (save-excursion
+ (and (funcall search "^[ \t]*#\\+\\(begin\\|end\\)_" limit t)
+ (= (length (match-string 1)) boundary)))
+ ;; We're in a block: get out of it
+ (goto-char (match-beginning 0))))))
+
+(defun org-list-search-unenclosed-generic (search re bound noerr)
+ "Search a string outside blocks and protected places.
+Arguments SEARCH, RE, BOUND and NOERR are similar to those in
+`search-forward', `search-backward', `re-search-forward' and
+`re-search-backward'."
+ (catch 'exit
+ (let ((origin (point)))
+ (while t
+ ;; 1. No match: return to origin or bound, depending on NOERR.
+ (unless (funcall search re bound noerr)
+ (throw 'exit (and (goto-char (if (memq noerr '(t nil)) origin bound))
+ nil)))
+ ;; 2. Match not in block or protected: return point. Else
+ ;; skip the block and carry on.
+ (unless (or (get-text-property (match-beginning 0) 'org-protected)
+ (org-list-maybe-skip-block search bound))
+ (throw 'exit (point)))))))
+
+(defun org-search-backward-unenclosed (regexp &optional bound noerror)
+ "Like `re-search-backward' but don't stop inside blocks or protected places.
+Arguments REGEXP, BOUND and NOERROR are similar to those used in
+`re-search-backward'."
+ (org-list-search-unenclosed-generic
+ #'re-search-backward regexp (or bound (point-min)) noerror))
+
+(defun org-search-forward-unenclosed (regexp &optional bound noerror)
+ "Like `re-search-forward' but don't stop inside blocks or protected places.
+Arguments REGEXP, BOUND and NOERROR are similar to those used in
+`re-search-forward'."
+ (org-list-search-unenclosed-generic
+ #'re-search-forward regexp (or bound (point-max)) noerror))
+
+(defun org-list-in-item-p-with-indent (limit)
+ "Is the cursor inside a plain list?
+Plain lists are considered ending when a non-blank line is less
+indented than the previous item within LIMIT."
+ (save-excursion
+ (beginning-of-line)
+ (cond
+ ;; do not start searching inside a block...
+ ((org-list-maybe-skip-block #'re-search-backward limit))
+ ;; ... or at a blank line
+ ((looking-at "^[ \t]*$")
+ (skip-chars-backward " \r\t\n")
+ (beginning-of-line)))
+ (beginning-of-line)
+ (or (org-at-item-p)
+ (let* ((case-fold-search t)
+ (ind-ref (org-get-indentation))
+ ;; Ensure there is at least an item above
+ (up-item-p (save-excursion
+ (org-search-backward-unenclosed
+ org-item-beginning-re limit t))))
+ (and up-item-p
+ (catch 'exit
+ (while t
+ (cond
+ ((org-at-item-p)
+ (throw 'exit (< (org-get-indentation) ind-ref)))
+ ((looking-at "^[ \t]*$")
+ (skip-chars-backward " \r\t\n")
+ (beginning-of-line))
+ ((looking-at "^[ \t]*#\\+end_")
+ (re-search-backward "^[ \t]*#\\+begin_"))
+ (t
+ (setq ind-ref (min (org-get-indentation) ind-ref))
+ (forward-line -1))))))))))
+
+(defun org-list-in-item-p-with-regexp (limit)
+ "Is the cursor inside a plain list?
+Plain lists end when `org-list-end-regexp' is matched, or at a
+blank line if `org-empty-line-terminates-plain-lists' is true.
+
+Argument LIMIT specifies the upper-bound of the search."
+ (save-excursion
+ (let* ((actual-pos (goto-char (point-at-eol)))
+ ;; Moved to eol so current line can be matched by
+ ;; `org-item-re'.
+ (last-item-start (save-excursion
+ (org-search-backward-unenclosed
+ org-item-beginning-re limit t)))
+ (list-ender (org-list-ending-between
+ last-item-start actual-pos)))
+ ;; We are in a list when we are on an item line or when we can
+ ;; find an item before point and there is no valid list ender
+ ;; between it and the point.
+ (and last-item-start (not list-ender)))))
+
+(defun org-list-top-point-with-regexp (limit)
+ "Return point at the top level item in a list.
+Argument LIMIT specifies the upper-bound of the search.
+
+List ending is determined by regexp. See
+`org-list-ending-method'. for more information."
+ (save-excursion
+ (let ((pos (point-at-eol)))
+ ;; Is there some list above this one ? If so, go to its ending.
+ ;; Otherwise, go back to the heading above or bob.
+ (goto-char (or (org-list-ending-between limit pos) limit))
+ ;; From there, search down our list.
+ (org-search-forward-unenclosed org-item-beginning-re pos t)
+ (point-at-bol))))
+
+(defun org-list-bottom-point-with-regexp (limit)
+ "Return point just before list ending.
+Argument LIMIT specifies the lower-bound of the search.
+
+List ending is determined by regexp. See
+`org-list-ending-method'. for more information."
+ (save-excursion
+ (let ((pos (org-get-item-beginning)))
+ ;; The list ending is either first point matching
+ ;; `org-list-end-re', point at first white-line before next
+ ;; heading, or eob.
+ (or (org-list-ending-between (min pos limit) limit t) limit))))
+
+(defun org-list-top-point-with-indent (limit)
+ "Return point at the top level in a list.
+Argument LIMIT specifies the upper-bound of the search.
+
+List ending is determined by indentation of text. See
+`org-list-ending-method'. for more information."
+ (save-excursion
+ (let ((case-fold-search t))
+ (let ((item-ref (goto-char (org-get-item-beginning)))
+ (ind-ref 10000))
+ (forward-line -1)
+ (catch 'exit
+ (while t
+ (let ((ind (+ (or (get-text-property (point) 'original-indentation) 0)
+ (org-get-indentation))))
+ (cond
+ ((looking-at "^[ \t]*:END:")
+ (throw 'exit item-ref))
+ ((<= (point) limit)
+ (throw 'exit
+ (if (and (org-at-item-p) (< ind ind-ref))
+ (point-at-bol)
+ item-ref)))
+ ((looking-at "^[ \t]*$")
+ (skip-chars-backward " \r\t\n")
+ (beginning-of-line))
+ ((looking-at "^[ \t]*#\\+end_")
+ (re-search-backward "^[ \t]*#\\+begin_"))
+ ((not (org-at-item-p))
+ (setq ind-ref (min ind ind-ref))
+ (forward-line -1))
+ ((>= ind ind-ref)
+ (throw 'exit item-ref))
+ (t
+ (setq item-ref (point-at-bol) ind-ref 10000)
+ (forward-line -1))))))))))
+
+(defun org-list-bottom-point-with-indent (limit)
+ "Return point just before list ending or nil if not in a list.
+Argument LIMIT specifies the lower-bound of the search.
+
+List ending is determined by the indentation of text. See
+`org-list-ending-method' for more information."
+ (save-excursion
+ (let ((ind-ref (progn
+ (goto-char (org-get-item-beginning))
+ (org-get-indentation)))
+ (case-fold-search t))
+ ;; do not start inside a block
+ (org-list-maybe-skip-block #'re-search-forward limit)
+ (beginning-of-line)
+ (catch 'exit
+ (while t
+ (skip-chars-forward " \t")
+ (let ((ind (+ (or (get-text-property (point) 'original-indentation) 0)
+ (org-get-indentation))))
+ (cond
+ ((or (>= (point) limit)
+ (looking-at ":END:"))
+ (throw 'exit (progn
+ ;; Ensure bottom is just after a
+ ;; non-blank line.
+ (skip-chars-backward " \r\t\n")
+ (min (point-max) (1+ (point-at-eol))))))
+ ((= (point) (point-at-eol))
+ (skip-chars-forward " \r\t\n")
+ (beginning-of-line))
+ ((org-at-item-p)
+ (setq ind-ref ind)
+ (forward-line 1))
+ ((<= ind ind-ref)
+ (throw 'exit (progn
+ ;; Again, ensure bottom is just after a
+ ;; non-blank line.
+ (skip-chars-backward " \r\t\n")
+ (min (point-max) (1+ (point-at-eol))))))
+ ((looking-at "#\\+begin_")
+ (re-search-forward "[ \t]*#\\+end_")
+ (forward-line 1))
+ (t (forward-line 1)))))))))
+
+(defun org-list-at-regexp-after-bullet-p (regexp)
+ "Is point at a list item with REGEXP after bullet?"
+ (and (org-at-item-p)
+ (save-excursion
+ (goto-char (match-end 0))
+ ;; Ignore counter if any
+ (when (looking-at "\\(?:\\[@\\(?:start:\\)?[0-9]+\\][ \t]*\\)?")
+ (goto-char (match-end 0)))
+ (looking-at regexp))))
+
+(defun org-list-get-item-same-level (search-fun pos limit pre-move)
+ "Return point at the beginning of next item at the same level.
+Search items using function SEARCH-FUN, from POS to LIMIT. It
+uses PRE-MOVE before search. Return nil if no item was found."
+ (save-excursion
+ (goto-char pos)
+ (let* ((start (org-get-item-beginning))
+ (ind (progn (goto-char start) (org-get-indentation))))
+ ;; We don't want to match the current line.
+ (funcall pre-move)
+ ;; Skip any sublist on the way
+ (while (and (funcall search-fun org-item-beginning-re limit t)
+ (> (org-get-indentation) ind)))
+ (when (and (/= (point-at-bol) start) ; Have we moved ?
+ (= (org-get-indentation) ind))
+ (point-at-bol)))))
+
+(defun org-list-separating-blank-lines-number (pos top bottom)
+ "Return number of blank lines that should separate items in list.
+POS is the position of point to be considered.
+
+TOP and BOTTOM are respectively position of list beginning and
+list ending.
+
+Assume point is at item's beginning. If the item is alone, apply
+some heuristics to guess the result."
+ (save-excursion
+ (let ((insert-blank-p
+ (cdr (assq 'plain-list-item org-blank-before-new-entry)))
+ usr-blank)
+ (cond
+ ;; Trivial cases where there should be none.
+ ((or (and (not (eq org-list-ending-method 'indent))
+ org-empty-line-terminates-plain-lists)
+ (not insert-blank-p)) 0)
+ ;; When `org-blank-before-new-entry' says so, it is 1.
+ ((eq insert-blank-p t) 1)
+ ;; plain-list-item is 'auto. Count blank lines separating
+ ;; neighbours items in list.
+ (t (let ((next-p (org-get-next-item (point) bottom)))
+ (cond
+ ;; Is there a next item?
+ (next-p (goto-char next-p)
+ (org-back-over-empty-lines))
+ ;; Is there a previous item?
+ ((org-get-previous-item (point) top)
+ (org-back-over-empty-lines))
+ ;; User inserted blank lines, trust him
+ ((and (> pos (org-end-of-item-before-blank bottom))
+ (> (save-excursion
+ (goto-char pos)
+ (skip-chars-backward " \t")
+ (setq usr-blank (org-back-over-empty-lines))) 0))
+ usr-blank)
+ ;; Are there blank lines inside the item ?
+ ((save-excursion
+ (org-search-forward-unenclosed
+ "^[ \t]*$" (org-end-of-item-before-blank bottom) t)) 1)
+ ;; No parent: no blank line.
+ (t 0))))))))
+
+(defun org-list-insert-item-generic (pos &optional checkbox after-bullet)
+ "Insert a new list item at POS.
+If POS is before first character after bullet of the item, the
+new item will be created before the current one.
+
+Insert a checkbox if CHECKBOX is non-nil, and string AFTER-BULLET
+after the bullet. Cursor will be after this text once the
+function ends."
+ (goto-char pos)
+ ;; Is point in a special block?
+ (when (org-in-regexps-block-p
+ "^[ \t]*#\\+\\(begin\\|BEGIN\\)_\\([a-zA-Z0-9_]+\\)"
+ '(concat "^[ \t]*#\\+\\(end\\|END\\)_" (match-string 2)))
+ (if (not (cdr (assq 'insert org-list-automatic-rules)))
+ ;; Rule in `org-list-automatic-rules' forbids insertion.
+ (error "Cannot insert item inside a block")
+ ;; Else, move before it prior to add a new item.
+ (end-of-line)
+ (re-search-backward "^[ \t]*#\\+\\(begin\\|BEGIN\\)_" nil t)
+ (end-of-line 0)))
+ (let* ((true-pos (point))
+ (top (org-list-top-point))
+ (bottom (copy-marker (org-list-bottom-point)))
+ (bullet (and (goto-char (org-get-item-beginning))
+ (org-list-bullet-string (org-get-bullet))))
+ (ind (org-get-indentation))
+ (before-p (progn
+ ;; Description item: text starts after colons.
+ (or (org-at-item-description-p)
+ ;; At a checkbox: text starts after it.
+ (org-at-item-checkbox-p)
+ ;; Otherwise, text starts after bullet.
+ (org-at-item-p))
+ (<= true-pos (match-end 0))))
+ (blank-lines-nb (org-list-separating-blank-lines-number
+ true-pos top bottom))
+ (insert-fun
+ (lambda (text)
+ ;; insert bullet above item in order to avoid bothering
+ ;; with possible blank lines ending last item.
+ (goto-char (org-get-item-beginning))
+ (org-indent-to-column ind)
+ (insert (concat bullet (when checkbox "[ ] ") after-bullet))
+ ;; Stay between after-bullet and before text.
+ (save-excursion
+ (insert (concat text (make-string (1+ blank-lines-nb) ?\n))))
+ (unless before-p
+ ;; store bottom: exchanging items doesn't change list
+ ;; bottom point but will modify marker anyway
+ (setq bottom (marker-position bottom))
+ (let ((col (current-column)))
+ (org-list-exchange-items
+ (org-get-item-beginning) (org-get-next-item (point) bottom)
+ bottom)
+ ;; recompute next-item: last sexp modified list
+ (goto-char (org-get-next-item (point) bottom))
+ (org-move-to-column col)))
+ ;; checkbox update might modify bottom point, so use a
+ ;; marker here
+ (setq bottom (copy-marker bottom))
+ (when checkbox (org-update-checkbox-count-maybe))
+ (org-list-repair nil top bottom))))
+ (goto-char true-pos)
+ (cond
+ (before-p (funcall insert-fun nil) t)
+ ;; Can't split item: insert bullet at the end of item.
+ ((not (org-get-alist-option org-M-RET-may-split-line 'item))
+ (funcall insert-fun nil) t)
+ ;; else, insert a new bullet along with everything from point
+ ;; down to last non-blank line of item.
+ (t
+ (delete-horizontal-space)
+ ;; Get pos again in case previous command modified line.
+ (let* ((pos (point))
+ (end-before-blank (org-end-of-item-before-blank bottom))
+ (after-text
+ (when (< pos end-before-blank)
+ (prog1
+ (delete-and-extract-region pos end-before-blank)
+ ;; delete any blank line at and before point.
+ (beginning-of-line)
+ (while (looking-at "^[ \t]*$")
+ (delete-region (point-at-bol) (1+ (point-at-eol)))
+ (beginning-of-line 0))))))
+ (funcall insert-fun after-text) t)))))
+
+(defvar org-last-indent-begin-marker (make-marker))
+(defvar org-last-indent-end-marker (make-marker))
+
+(defun org-list-indent-item-generic (arg no-subtree top bottom)
+ "Indent a local list item including its children.
+When number ARG is a negative, item will be outdented, otherwise
+it will be indented.
+
+If a region is active, all items inside will be moved.
+
+If NO-SUBTREE is non-nil, only indent the item itself, not its
+children.
+
+TOP and BOTTOM are respectively position at item beginning and at
+item ending.
+
+Return t if successful."
+ (let* ((regionp (org-region-active-p))
+ (rbeg (and regionp (region-beginning)))
+ (rend (and regionp (region-end))))
+ (cond
+ ((and regionp
+ (goto-char rbeg)
+ (not (org-search-forward-unenclosed org-item-beginning-re rend t)))
+ (error "No item in region"))
+ ((not (org-at-item-p))
+ (error "Not on an item"))
+ (t
+ ;; Are we going to move the whole list?
+ (let* ((specialp (and (cdr (assq 'indent org-list-automatic-rules))
+ (not no-subtree)
+ (= top (point-at-bol)))))
+ ;; Determine begin and end points of zone to indent. If moving
+ ;; more than one item, ensure we keep them on subsequent moves.
+ (unless (and (memq last-command '(org-shiftmetaright org-shiftmetaleft))
+ (memq this-command '(org-shiftmetaright org-shiftmetaleft)))
+ (if regionp
+ (progn
+ (set-marker org-last-indent-begin-marker rbeg)
+ (set-marker org-last-indent-end-marker rend))
+ (set-marker org-last-indent-begin-marker (point-at-bol))
+ (set-marker org-last-indent-end-marker
+ (save-excursion
+ (cond
+ (specialp bottom)
+ (no-subtree (org-end-of-item-or-at-child bottom))
+ (t (org-get-end-of-item bottom)))))))
+ ;; Get everything ready
+ (let* ((beg (marker-position org-last-indent-begin-marker))
+ (end (marker-position org-last-indent-end-marker))
+ (struct (org-list-struct
+ beg end top (if specialp end bottom) (< arg 0)))
+ (origins (org-list-struct-origins struct))
+ (beg-item (assq beg struct)))
+ (cond
+ ;; Special case: moving top-item with indent rule
+ (specialp
+ (let* ((level-skip (org-level-increment))
+ (offset (if (< arg 0) (- level-skip) level-skip))
+ (top-ind (nth 1 beg-item)))
+ (if (< (+ top-ind offset) 0)
+ (error "Cannot outdent beyond margin")
+ ;; Change bullet if necessary
+ (when (and (= (+ top-ind offset) 0)
+ (string-match "*" (nth 2 beg-item)))
+ (setcdr beg-item (list (nth 1 beg-item)
+ (org-list-bullet-string "-"))))
+ ;; Shift ancestor
+ (let ((anc (car struct)))
+ (setcdr anc (list (+ (nth 1 anc) offset) "" nil)))
+ (org-list-struct-fix-struct struct origins)
+ (org-list-struct-apply-struct struct end))))
+ ;; Forbidden move
+ ((and (< arg 0)
+ (or (and no-subtree
+ (not regionp)
+ (org-list-struct-get-child beg-item struct))
+ (let ((last-item (save-excursion
+ (goto-char end)
+ (skip-chars-backward " \r\t\n")
+ (goto-char (org-get-item-beginning))
+ (org-list-struct-assoc-at-point))))
+ (org-list-struct-get-child last-item struct))))
+ (error "Cannot outdent an item without its children"))
+ ;; Normal shifting
+ (t
+ (let* ((shifted-ori (if (< arg 0)
+ (org-list-struct-outdent beg end origins)
+ (org-list-struct-indent beg end origins struct))))
+ (org-list-struct-fix-struct struct shifted-ori)
+ (org-list-struct-apply-struct struct bottom))))))))))
+
+;;; Predicates
+
+(defun org-in-item-p ()
+ "Is the cursor inside a plain list?
+This checks `org-list-ending-method'."
+ (unless (let ((outline-regexp org-outline-regexp)) (org-at-heading-p))
+ (let* ((prev-head (save-excursion (outline-previous-heading)))
+ (bound (if prev-head
+ (or (save-excursion
+ (let ((case-fold-search t))
+ (re-search-backward "^[ \t]*:END:" prev-head t)))
+ prev-head)
+ (point-min))))
+ (cond
+ ((eq org-list-ending-method 'regexp)
+ (org-list-in-item-p-with-regexp bound))
+ ((eq org-list-ending-method 'indent)
+ (org-list-in-item-p-with-indent bound))
+ (t (and (org-list-in-item-p-with-regexp bound)
+ (org-list-in-item-p-with-indent bound)))))))
+
+(defun org-list-first-item-p (top)
+ "Is this item the first item in a plain list?
+Assume point is at an item.
+
+TOP is the position of list's top-item."
+ (save-excursion
+ (beginning-of-line)
+ (let ((ind (org-get-indentation)))
+ (or (not (org-search-backward-unenclosed org-item-beginning-re top t))
+ (< (org-get-indentation) ind)))))
+