summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicolas Goaziou <n.goaziou@gmail.com>2010-08-19 16:09:45 +0200
committerNicolas Goaziou <n.goaziou@gmail.com>2010-09-01 19:05:54 +0200
commitb5eb7047f36f65ff79b1b01cc893dacc4fa12d63 (patch)
treeba9bc7092a1f171e949a7464c56dcc0b91dfe7fd
parent2cca51027640cb800a47d039981faff9658b2a79 (diff)
downloadorg-mode-b5eb7047f36f65ff79b1b01cc893dacc4fa12d63.tar.gz
Now both indentation and regexps can end lists
* org-list.el (org-list-ending-method): New customizable variable to tell Org Mode how lists end. See docstring.
-rw-r--r--lisp/org-exp.el3
-rw-r--r--lisp/org-list.el304
2 files changed, 233 insertions, 74 deletions
diff --git a/lisp/org-exp.el b/lisp/org-exp.el
index 2151b7a..c16bec8 100644
--- a/lisp/org-exp.el
+++ b/lisp/org-exp.el
@@ -1638,7 +1638,8 @@ These special cookies will later be interpreted by the backend.
(goto-char (point-min))
(while (org-search-forward-unenclosed org-item-beginning-re nil t)
(goto-char (org-list-bottom-point))
- (when (looking-at (org-list-end-re))
+ (when (and (not (eq org-list-ending-method 'indent))
+ (looking-at (org-list-end-re)))
(replace-match "\n"))
(insert end-list-marker)))))
;; We need to divide backends into 3 categories.
diff --git a/lisp/org-list.el b/lisp/org-list.el
index 334f7f9..394a968 100644
--- a/lisp/org-list.el
+++ b/lisp/org-list.el
@@ -149,9 +149,32 @@ spaces instead of one after the bullet in each item of the list."
(const :tag "never" nil)
(regexp)))
+(defcustom org-list-ending-method 'regexp
+ "Determine where plain lists should end.
+
+Valid values are symbols 'regexp, 'indent or 'both.
+
+When set to 'regexp, Org will look into two variables,
+`org-empty-line-terminates-plain-lists' and the more general
+`org-list-end-regexp', to know what will end lists. This is the
+default value.
+
+When set to 'indent, indentation of the last non-blank line will
+determine if point is in a list. If that line is less indented
+than the previous item in the section, if any, list has ended.
+
+When set to 'both, each of the preceding methods must confirm
+that point is in a list."
+ :group 'org-plain-lists
+ :type '(choice
+ (const :tag "With a well defined ending (recommended)" regexp)
+ (const :tag "With indentation of the current line" indent)
+ (const :tag "With both methods" both)))
+
(defcustom org-empty-line-terminates-plain-lists nil
"Non-nil means an empty line ends all plain list levels.
-Otherwise, look for `org-list-end-regexp'."
+This variable only makes sense if `org-list-ending-method' is set
+to 'regexp or 'both."
:group 'org-plain-lists
:type 'boolean)
@@ -295,34 +318,164 @@ the end of the nearest terminator from max."
;; we want to be on the first line of the list ender
(match-beginning 0)))))
-(defun org-list-search-unenclosed-generic (search skip len re bound noerr)
+(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."
+ (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 for RE with SEARCH outside blocks and protected places."
- (let ((in-block-p
- (lambda ()
- (let ((case-fold-search t))
- (when (save-excursion
- (and (funcall search "^[ \t]*#\\+\\(begin\\|end\\)_" bound t)
- (= (length (match-string 1)) len)))
- ;; We're in a block: get out of it and resume searching
- (goto-char (funcall skip 0)))))))
- (catch 'exit
- (let ((origin (point)))
- (while t
- (unless (funcall search re bound noerr)
- (throw 'exit (and (goto-char (if (booleanp noerr) origin bound)) nil)))
- (unless (or (get-text-property (match-beginning 0) 'org-protected)
- (save-match-data (funcall in-block-p)))
- (throw 'exit (point))))))))
+ (catch 'exit
+ (let ((origin (point)))
+ (while t
+ (unless (funcall search re bound noerr)
+ (throw 'exit (and (goto-char (if (booleanp noerr) origin bound))
+ nil)))
+ (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 at protected places."
+ "Like `re-search-backward' but don't stop inside blocks or protected places."
(org-list-search-unenclosed-generic
- #'re-search-backward #'match-beginning 5 regexp (or bound (point-min)) noerror))
+ #'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 at protected places."
+ "Like `re-search-forward' but don't stop inside blocks or protected places."
(org-list-search-unenclosed-generic
- #'re-search-forward #'match-end 3 regexp (or bound (point-max)) noerror))
+ #'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.
+
+Return the position of the previous item, if applicable."
+ (save-excursion
+ (beginning-of-line)
+ ;; do not start searching at a blank line or inside a block
+ (while (or (and (org-list-maybe-skip-block #'re-search-backward limit)
+ (goto-char (1- (point-at-bol))))
+ (looking-at "^[ \t]*$"))
+ (skip-chars-backward " \r\t\n")
+ (beginning-of-line))
+ (or (and (org-at-item-p) (point-at-bol))
+ (let ((ind (org-get-indentation)))
+ (catch 'exit
+ (while t
+ (cond
+ ((or (bobp) (< (point) limit)) (throw 'exit nil))
+ ((and (not (looking-at "[ \t]*$"))
+ (not (org-list-maybe-skip-block
+ #'re-search-backward limit))
+ (< (org-get-indentation) ind))
+ (throw 'exit (and (org-at-item-p) (point-at-bol))))
+ (t (beginning-of-line 0)))))))))
+
+(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."
+ (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-terminator-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, or nil if not in a list.
+
+List ending is determined by regexp. See
+`org-list-ending-method'. for more information."
+ (save-excursion
+ (and (org-list-in-item-p-with-regexp limit)
+ (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-terminator-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 or nil if not in a list.
+
+List ending is determined by regexp. See
+`org-list-ending-method'. for more information."
+ (save-excursion
+ (and (org-in-item-p)
+ (let ((pos (point)))
+ ;; 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-terminator-between (min pos limit) limit t) limit)))))
+
+(defun org-list-top-point-with-indent (limit)
+ "Return point just before list ending or nil if not in a list.
+
+List ending is determined by indentation of text. See
+`org-list-ending-method'. for more information."
+ (save-excursion
+ (let ((prev-p (org-list-in-item-p-with-indent limit)))
+ (and prev-p
+ (catch 'exit
+ (while t
+ (cond
+ ((not prev-p) (throw 'exit (1+ (point-at-eol))))
+ ((= limit prev-p) (throw 'exit limit))
+ (t
+ (goto-char prev-p)
+ (beginning-of-line 0)
+ (setq prev-p (org-list-in-item-p-with-indent limit))))))))))
+
+(defun org-list-bottom-point-with-indent (limit)
+ "Return point just before list ending or nil if not in a list.
+
+List ending is determined by the indentation of text. See
+`org-list-ending-method' for more information."
+ (save-excursion
+ (let* ((ind (save-excursion
+ (ignore-errors (org-beginning-of-item))
+ (org-get-indentation)))
+ (end-item (lambda ()
+ (save-excursion
+ (catch 'end
+ (while t
+ (beginning-of-line 2)
+ (cond
+ ((>= (point) limit) (throw 'end limit))
+ ((or (looking-at "^[ \t]*$")
+ (org-list-maybe-skip-block
+ #'re-search-forward limit)
+ (> (org-get-indentation) ind)))
+ (t (throw 'end (point-at-bol))))))))))
+ (and (org-in-item-p)
+ (catch 'exit
+ (while t
+ (goto-char (funcall end-item))
+ (if (looking-at org-item-beginning-re)
+ (setq ind (org-get-indentation))
+ (skip-chars-backward " \r\t\n")
+ (throw 'exit (1+ (point-at-eol))))))))))
(defun org-list-at-regexp-after-bullet-p (regexp)
"Is point at a list item with REGEXP after bullet?"
@@ -393,7 +546,8 @@ function ends."
usr-blank)
(cond
;; Trivial cases where there should be none.
- ((or org-empty-line-terminates-plain-lists
+ ((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)
@@ -465,20 +619,18 @@ function ends."
;;; Predicates
(defun org-in-item-p ()
- "Is the cursor inside a plain list ?"
+ "Is the cursor inside a plain list?
+This checks `org-list-ending-method'."
(unless (let ((outline-regexp org-outline-regexp)) (org-at-heading-p))
- (save-excursion
- (let* ((limit (save-excursion (outline-previous-heading)))
- ;; Move to eol so current line can be matched by `org-item-re'.
- (actual-pos (goto-char (point-at-eol)))
- (last-item-start (save-excursion
- (org-search-backward-unenclosed org-item-beginning-re limit t)))
- (list-ender (org-list-terminator-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))))))
+ (let ((bound (or (save-excursion (outline-previous-heading))
+ (point-min))))
+ (cond
+ ((eq org-list-ending-method 'indent)
+ (org-list-in-item-p-with-indent bound))
+ ((eq org-list-ending-method 'both)
+ (and (org-list-in-item-p-with-indent bound)
+ (org-list-in-item-p-with-regexp bound)))
+ (t (org-list-in-item-p-with-regexp bound))))))
(defun org-list-first-item-p ()
"Is this item the first item in a plain list?
@@ -486,7 +638,8 @@ Assume point is at an item."
(save-excursion
(beginning-of-line)
(let ((ind (org-get-indentation)))
- (or (not (org-search-backward-unenclosed org-item-beginning-re (org-list-top-point) t))
+ (or (not (org-search-backward-unenclosed
+ org-item-beginning-re (org-list-top-point) t))
(< (org-get-indentation) ind)))))
(defun org-at-item-p ()
@@ -502,7 +655,8 @@ Assume point is at an item."
(defun org-at-item-timer-p ()
"Is point at a line starting a plain list item with a timer?"
- (org-list-at-regexp-after-bullet-p "\\([0-9]+:[0-9]+:[0-9]+\\)[ \t]+::[ \t]+"))
+ (org-list-at-regexp-after-bullet-p
+ "\\([0-9]+:[0-9]+:[0-9]+\\)[ \t]+::[ \t]+"))
(defun org-at-item-description-p ()
"Is point at a description list item?"
@@ -537,34 +691,32 @@ A checkbox is blocked if all of the following conditions are fulfilled:
;;; Navigate
(defun org-list-top-point ()
- "Return point at the top level item in a list, or nil if not in a list."
- (save-excursion
- (and (org-in-item-p)
- (let ((pos (point-at-eol))
- (bound (or (outline-previous-heading) (point-min))))
- ;; 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-terminator-between bound pos) bound))
- ;; From there, search down our list.
- (org-search-forward-unenclosed org-item-beginning-re pos t)
- (point-at-bol)))))
+ (let ((limit (or (save-excursion (outline-previous-heading))
+ (point-min))))
+ (cond
+ ((eq org-list-ending-method 'indent)
+ (org-list-top-point-with-indent limit))
+ ((eq org-list-ending-method 'both)
+ (max (org-list-top-point-with-regexp limit)
+ (org-list-top-point-with-indent limit)))
+ (t (org-list-top-point-with-regexp limit)))))
(defun org-list-bottom-point ()
- "Return point just before list ending or nil if not in a list."
- (save-excursion
- (and (org-in-item-p)
- (let ((pos (point))
- (bound (or (and (let ((outline-regexp org-outline-regexp))
- ;; Use default regexp because folding
- ;; changes OUTLINE-REGEXP.
- (outline-next-heading))
- (skip-chars-backward " \t\r\n")
- (1+ (point-at-eol)))
- (point-max))))
- ;; 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-terminator-between (min pos bound) bound t) bound)))))
+ (let ((limit (or (save-excursion
+ (and (let ((outline-regexp org-outline-regexp))
+ ;; Use default regexp because folding
+ ;; changes OUTLINE-REGEXP.
+ (outline-next-heading))
+ (skip-chars-backward " \r\t\n")
+ (1+ (point-at-eol))))
+ (point-max))))
+ (cond
+ ((eq org-list-ending-method 'indent)
+ (org-list-bottom-point-with-indent limit))
+ ((eq org-list-ending-method 'both)
+ (min (org-list-bottom-point-with-regexp limit)
+ (org-list-bottom-point-with-indent limit)))
+ (t (org-list-bottom-point-with-regexp limit)))))
(defun org-beginning-of-item ()
"Go to the beginning of the current hand-formatted item.
@@ -1032,16 +1184,21 @@ Initial position is restored after the changes."
(match-string 1)))
(old-body-ind (+ (length old-bul) old-ind))
(new-body-ind (+ (length new-bul) new-ind)))
- ;; Replace bullet
+ ;; 1. Shift item's body
+ (unless (= old-body-ind new-body-ind)
+ (org-shift-item-indentation (- new-body-ind old-body-ind)))
+ ;; 2. Replace bullet
(unless (equal new-bul old-bul)
- (save-excursion (replace-match new-bul nil nil nil 1)))
- ;; Indent item to appropriate column
+ (save-excursion
+ (looking-at "[ \t]*\\(\\S-+[ \t]*\\)")
+ (replace-match new-bul nil nil nil 1)))
+ ;; 3. Indent item to appropriate column
(unless (= new-ind old-ind)
- (delete-region (point-at-bol) (match-beginning 1))
- (indent-to new-ind))
- ;; Shift item's body
- (unless (= old-body-ind new-body-ind)
- (org-shift-item-indentation (- new-body-ind old-body-ind))))))
+ (delete-region (point-at-bol)
+ (progn
+ (skip-chars-forward " \t")
+ (point)))
+ (indent-to new-ind)))))
;; Remove ancestor if it is left.
(struct-to-apply (if (or (not ancestor) (= 0 ancestor))
(cdr struct)
@@ -1680,7 +1837,8 @@ sublevels as a list of strings."
(when delete
(delete-region start end)
(save-match-data
- (when (looking-at (org-list-end-re))
+ (when (and (not (eq org-list-ending-method 'indent))
+ (looking-at (org-list-end-re)))
(replace-match "\n"))))
(setq output (nreverse output))
(push ltype output)))