diff options
author | Nicolas Goaziou <n.goaziou@gmail.com> | 2010-08-19 16:09:45 +0200 |
---|---|---|
committer | Nicolas Goaziou <n.goaziou@gmail.com> | 2010-09-01 19:05:54 +0200 |
commit | b5eb7047f36f65ff79b1b01cc893dacc4fa12d63 (patch) | |
tree | ba9bc7092a1f171e949a7464c56dcc0b91dfe7fd | |
parent | 2cca51027640cb800a47d039981faff9658b2a79 (diff) | |
download | org-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.el | 3 | ||||
-rw-r--r-- | lisp/org-list.el | 304 |
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))) |