emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[elpa] externals/orgalist 4c4326e 1/2: Initial version, sent to emacs-or


From: Stefan Monnier
Subject: [elpa] externals/orgalist 4c4326e 1/2: Initial version, sent to emacs-orgmode list
Date: Mon, 30 Apr 2018 11:59:52 -0400 (EDT)

branch: externals/orgalist
commit 4c4326e2142dfbc8ed1b45745ebd16fece24a46c
Author: Nicolas Goaziou <address@hidden>
Commit: Stefan Monnier <address@hidden>

    Initial version, sent to emacs-orgmode list
---
 orgalist.el | 916 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 916 insertions(+)

diff --git a/orgalist.el b/orgalist.el
new file mode 100644
index 0000000..0263046
--- /dev/null
+++ b/orgalist.el
@@ -0,0 +1,916 @@
+;;; orgalist.el --- Manage Org lists in non-Org buffers  -*- lexical-binding: 
t; -*-
+
+;; Copyright (C) 2017  Nicolas Goaziou
+
+;; Author: Nicolas Goaziou <address@hidden>
+;; Keywords: convenience
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This library provides Org mode's plain lists in non-Org buffers.
+
+;; More specifically, it supports syntax for numbered, unnumbered,
+;; description items, checkboxes, and counter cookies.
+
+;; Besides, the following features are supported:
+
+;; - Navigation (M-<up>, M-<down>)
+;; - Indentation (M-<left>, M-<right>, M-S-<left>, M-S-<right>, TAB)
+;; - Re-ordering (M-S-<up>, M-S-<down>)
+;; - Item insertion (M-RET)
+;; - Toggling checkboxes (C-c C-c)
+;; - Cycling bullets (C-c -)
+;; - Sorting items (C-c ^)
+;; - Filling items (M-q)
+;; - Auto filling (when Auto Fill mode is enabled)
+
+;; The library also implements radio lists:
+
+;; Call the `orgalist-insert-radio-list' function to insert a radio list
+;; template in HTML, LaTeX, and Texinfo mode documents.  Sending and
+;; receiving radio lists works is the same as for radio tables (see
+;; Org manual for details) except for these differences:
+
+;; - Orgalist minor mode must be active;
+;; - Use the "ORGLST" keyword instead of "ORGTBL";
+;; - `M-x orgalist-send-list' works only on the first list item.
+
+;; Built-in translator functions are: `org-list-to-latex',
+;; `org-list-to-html' and `org-list-to-texinfo'.  They use the
+;; `org-list-to-generic' translator function.  See its documentation for
+;; parameters for accurate customizations of lists.  Here is a LaTeX
+;; example:
+
+;;   % BEGIN RECEIVE ORGLST to-buy
+;;   % END RECEIVE ORGLST to-buy
+;;   \begin{comment}
+;;
+;;   #+ORGLST: SEND to-buy org-list-to-latex
+;;   - a new house
+;;   - a new computer
+;;     + a new keyboard
+;;     + a new mouse
+;;   - a new life
+;;   \end{comment}
+
+;; `M-x orgalist-send-list' on "a new house" inserts the translated
+;; LaTeX list in-between the "BEGIN RECEIVE" and "END RECEIVE" marker
+;; lines.
+
+
+;;; Code:
+
+(require 'easymenu)
+(require 'org-list)
+
+(defvar mail-header-separator)
+(defvar message-signature-separator)
+
+
+;;; Configuration variables
+
+(defgroup orgalist nil
+  "Manage plain lists in non-Org buffers."
+  :tag "Orgalist"
+  :group 'org)
+
+(defcustom orgalist-context-function
+  '((message-mode . orgalist-message-mode-context))
+  "Alist between major modes and list context functions.
+A list context function determines the boundaries of the buffer
+that can contain an Org list.  When no major mode is found, or the
+context function returns nil, consider the whole buffer."
+  :group 'orgalist
+  :type '(repeat
+          (list (symbol :tag "Major mode")
+                (function :tag "Function"))))
+
+(defcustom orgalist-ordered-checkboxes nil
+  "When non-nil, only tick checkboxes in order.
+In this case, a checkbox can only be checked when every checkbox
+before it is checked too."
+  :group 'orgalist
+  :type 'boolean
+  :safe #'booleanp)
+
+(defcustom orgalist-radio-list-templates
+  '((latex-mode "% BEGIN RECEIVE ORGLST %n
+% END RECEIVE ORGLST %n
+\\begin{comment}
+#+ORGLST: SEND %n org-list-to-latex
+-
+\\end{comment}\n")
+    (texinfo-mode "@c BEGIN RECEIVE ORGLST %n
address@hidden END RECEIVE ORGLST %n
address@hidden
+#+ORGLST: SEND %n org-list-to-texinfo
+-
address@hidden ignore\n")
+    (html-mode "<!-- BEGIN RECEIVE ORGLST %n -->
+<!-- END RECEIVE ORGLST %n -->
+<!--
+#+ORGLST: SEND %n org-list-to-html
+-
+-->\n"))
+  "Templates for radio lists in different major modes.
+All occurrences of %n in a template will be replaced with the name of the
+list, obtained by prompting the user."
+  :group 'orgalist
+  :type '(repeat
+          (list (symbol :tag "Major mode")
+                (string :tag "Format"))))
+
+
+;;; Mode specific context functions
+
+(defun orgalist-message-mode-context ()
+  "Return boundaries of message body if point is in body.
+Otherwise, return nil."
+  (save-excursion
+    (cond ((re-search-backward message-signature-separator nil t)
+           ;; After signature.
+           (cons (line-beginning-position 2) (point-max)))
+          ((re-search-backward
+            (concat "^" (regexp-quote mail-header-separator) "\n") nil t)
+           (goto-char (match-end 0))
+           (cons (point)
+                 (if (re-search-forward message-signature-separator nil t)
+                     (match-beginning 0)
+                   (point-max))))
+          ((re-search-forward
+            (concat "^" (regexp-quote mail-header-separator) "\n") nil t)
+           ;; In header.
+           nil)
+          (t nil))))
+
+
+;;; Internal variables
+
+(defvar orgalist--menu nil
+  "The Orgalist menu.")
+
+(defconst orgalist--item-re
+  (concat
+   "^[ \t]*\\(\\(?:[-+]\\|\\(?:[0-9]+\\|[A-Za-z]\\)\\.\\)\\(?:[ \t]+\\|$\\)\\)"
+   "\\(?:address@hidden([0-9]+\\|[A-Za-z]\\)\\][ \t]*\\)?"
+   "\\(?:\\(\\[[- xX]\\]\\)\\(?:[ \t]+\\|$\\)\\)?"
+   "\\(?:\\(.*\\)[ \t]+::\\(?:[ \t]+\\|$\\)\\)?")
+  "Match a list item and puts everything into groups:
+group 1: bullet
+group 2: counter
+group 3: checkbox
+group 4: description tag")
+
+(defvar-local orgalist--cycling-state nil
+  "Current cycling state when cycling indentation.")
+
+
+;;; Internal functions
+
+(defun orgalist--call-in-item (fun pos)
+  "Call function FUN with buffer narrowed to item starting at POS."
+  (let* ((struct (save-excursion (goto-char pos) (orgalist--struct)))
+         (next (or (org-list-has-child-p pos struct)
+                   (org-list-get-item-end pos struct)))
+         (fill-prefix
+          (make-string (length (org-list-get-bullet pos struct))
+                       ?\s)))
+    (save-restriction
+      (narrow-to-region pos next)
+      (funcall fun))))
+
+(defun orgalist--boundaries ()
+  "Return buffer boundaries, as a cons cell, where lists are acceptable.
+Return nil if Orgalist mode is not active."
+  (and orgalist-mode
+       (let ((f (cdr (assq major-mode orgalist-context-function))))
+         (or (and (functionp f) (funcall f))
+             (cons (point-min) (point-max))))))
+
+(defun orgalist--indentation ()
+  "Return current line's indentation."
+  (save-excursion
+    (beginning-of-line)
+    (skip-chars-forward "[ \t]")
+    (current-column)))
+
+(defun orgalist--at-item-p ()
+  "Non-nil if point is at an item."
+  (and (orgalist--boundaries)            ;check context
+       (save-excursion
+         (beginning-of-line)
+         (looking-at-p
+          "[ \t]*\\(?:[-+]\\|\\(?:[a-zA-Z]\\|[0-9]+\\)\\.\\)\\([ 
\t]\\|$\\)"))))
+
+(defun orgalist--in-item-p ()
+  "Return item beginning position when in a plain list, nil otherwise."
+  (let ((boundaries (orgalist--boundaries)))
+    (when boundaries
+      (save-excursion
+        (beginning-of-line)
+        (let ((lim-up (car (orgalist--boundaries)))
+              ;; Indentation isn't meaningful when point starts at an
+              ;; empty line .
+              (ind-ref (if (looking-at-p "^[ \t]*$") 10000
+                         (orgalist--indentation))))
+          (if (looking-at orgalist--item-re) (point)
+            ;; Detect if cursor in the middle of blank lines after a list.
+            (let ((end-bounds nil))
+              (when (and (setq end-bounds (org-in-regexp "^[ \t]*\n[ \t]*\n" 
2))
+                         (>= (point) (car end-bounds))
+                         (< (point) (cdr end-bounds)))
+                (goto-char (car end-bounds))
+                (forward-line -1)))
+            ;; Look for an item, less indented that reference line.
+            (catch 'exit
+              (while t
+                (let ((ind (orgalist--indentation)))
+                  (cond
+                   ;; This is exactly what we want.
+                   ((and (looking-at orgalist--item-re) (< ind ind-ref))
+                    (throw 'exit (point)))
+                   ;; At upper bound of search or looking at the end
+                   ;; of a previous list: search is over.
+                   ((<= (point) lim-up) (throw 'exit nil))
+                   ((looking-at "^[ \t]*\n[ \t]*\n") (throw 'exit nil))
+                   ;; Skip blank lines.
+                   ((looking-at "^[ \t]*$") (forward-line -1))
+                   ;; Text at column 0 cannot belong to a list: stop.
+                   ((= 0 ind) (throw 'exit nil))
+                   ;; Normal text less indented than reference line,
+                   ;; take it as new reference.
+                   ((< ind ind-ref)
+                    (setq ind-ref ind)
+                    (forward-line -1))
+                   (t (forward-line -1))))))))))))
+
+(defun orgalist--struct ()
+  "Return structure of list at point.
+
+A list structure is an alist where key is point at item, and
+values are:
+
+1. indentation,
+2. bullet with trailing white space,
+3. bullet counter, if any,
+4. checkbox, if any,
+5. description tag, if any,
+6. position at item end.
+
+Thus the following list, where numbers in parens are
+line beginning positions:
+
+- [X] first item                             (1)
+  1. sub-item 1                              (18)
+  5. address@hidden sub-item 2                         (34)
+  some other text belonging to first item    (55)
+- last item                                  (97)
+  + tag :: description                       (109)
+                                             (131)
+gets the following structure:
+
+ ((1 0 \"- \"  nil \"[X]\" nil 97)
+  (18 2 \"1. \"  nil nil nil 34)
+  (34 2 \"5. \" \"5\" nil nil 55)
+  (97 0 \"- \"  nil nil nil 131)
+  (109 2 \"+ \" nil nil \"tag\" 131))
+
+Assume point is at an item."
+  (save-excursion
+    (beginning-of-line)
+    (pcase-let* ((`(,lim-up . ,lim-down) (orgalist--boundaries))
+                 (text-min-ind 10000)
+                 (beg-cell (cons (point) (orgalist--indentation)))
+                 (itm-lst nil)
+                 (itm-lst-2 nil)
+                 (end-lst nil)
+                 (end-lst-2 nil)
+                 (struct nil)
+                 (assoc-at-point
+                  ;; Return association at point.
+                  (lambda (ind)
+                    (looking-at orgalist--item-re)
+                    (let ((bullet (match-string-no-properties 1)))
+                      (list (point)
+                            ind
+                            bullet
+                            (match-string-no-properties 2) ;counter
+                            (match-string-no-properties 3) ;checkbox
+                            ;; Description tag.
+                            (and (string-match-p "[-+*]" bullet)
+                                 (match-string-no-properties 4))))))
+                 (end-before-blank
+                  (lambda ()
+                    ;; Ensure list ends at the first blank line.
+                    (skip-chars-backward " \r\t\n")
+                    (min (line-beginning-position 2) lim-down))))
+      ;; Read list from starting item to its beginning, and save top
+      ;; item position and indentation in BEG-CELL. Also store ending
+      ;; position of items in END-LST.
+      (save-excursion
+        (catch 'exit
+          (while t
+            (let ((ind (orgalist--indentation)))
+              (cond
+               ((<= (point) lim-up)
+                ;; At upward limit: if we ended at an item, store it,
+                ;; else dismiss useless data recorded above BEG-CELL.
+                ;; Jump to part 2.
+                (throw 'exit
+                       (setq itm-lst
+                             (if (not (looking-at orgalist--item-re))
+                                 (memq (assq (car beg-cell) itm-lst) itm-lst)
+                               (setq beg-cell (cons (point) ind))
+                               (cons (funcall assoc-at-point ind) itm-lst)))))
+               ;; Looking at a list ending regexp.  Dismiss useless
+               ;; data recorded above BEG-CELL.  Jump to part 2.
+               ((looking-at "^[ \t]*\n[ \t]*\n")
+                (throw 'exit
+                       (setq itm-lst (memq (assq (car beg-cell) itm-lst)
+                                           itm-lst))))
+               ;; Point is at an item. Add data to ITM-LST. It may
+               ;; also end a previous item: save it in END-LST. If ind
+               ;; is less or equal than BEG-CELL and there is no end
+               ;; at this ind or lesser, this item becomes the new
+               ;; BEG-CELL.
+               ((looking-at orgalist--item-re)
+                (push (funcall assoc-at-point ind) itm-lst)
+                (push (cons ind (point)) end-lst)
+                (when (< ind text-min-ind) (setq beg-cell (cons (point) ind)))
+                (forward-line -1))
+               ;; Skip blank lines.
+               ((looking-at "^[ \t]*$")
+                (forward-line -1))
+               ;; From there, point is not at an item.  Interpret
+               ;; line's indentation:
+               ;; - text at column 0 is necessarily out of any list.
+               ;;   Dismiss data recorded above BEG-CELL.  Jump to
+               ;;   part 2.
+               ;; - any other case may be an ending position for an
+               ;;   hypothetical item above.  Store it and proceed.
+               ((= ind 0)
+                (throw 'exit
+                       (setq itm-lst
+                             (memq (assq (car beg-cell) itm-lst) itm-lst))))
+               (t
+                (when (< ind text-min-ind) (setq text-min-ind ind))
+                (push (cons ind (point)) end-lst)
+                (forward-line -1)))))))
+      ;; Read list from starting point to its end, that is until we
+      ;; get out of context, or that a non-item line is less or
+      ;; equally indented than BEG-CELL's cdr.  Also, store ending
+      ;; position of items in END-LST-2.
+      (catch 'exit
+        (while t
+          (let ((ind (orgalist--indentation)))
+            (cond
+             ((>= (point) lim-down)
+              ;; At downward limit: this is de facto the end of the
+              ;; list.  Save point as an ending position, and jump to
+              ;; part 3.
+              (throw 'exit
+                     (push (cons 0 (funcall end-before-blank)) end-lst-2)))
+             ;; Looking at a list ending regexp.  Save point as an
+             ;; ending position and jump to part 3.
+             ((looking-at org-list-end-re)
+              (throw 'exit (push (cons 0 (point)) end-lst-2)))
+             ((looking-at orgalist--item-re)
+              ;; Point is at an item.  Add data to ITM-LST-2. It may
+              ;; also end a previous item, so save it in END-LST-2.
+              (push (funcall assoc-at-point ind) itm-lst-2)
+              (push (cons ind (point)) end-lst-2)
+              (forward-line 1))
+             ;; Skip blank lines along the way.
+             ((looking-at "^[ \t]*$")
+              (forward-line 1))
+             ;; Ind is lesser or equal than BEG-CELL's.  The list is
+             ;; over: store point as an ending position and jump to
+             ;; part 3.
+             ((<= ind (cdr beg-cell))
+              (throw 'exit
+                     (push (cons 0 (funcall end-before-blank)) end-lst-2)))
+             ;; Else, if ind is lesser or equal than previous item's,
+             ;; this is an ending position: store it.  In any case,
+             ;; skip block or drawer at point, and move to next line.
+             (t
+              (when (<= ind (nth 1 (car itm-lst-2)))
+                (push (cons ind (point)) end-lst-2))
+              (forward-line 1))))))
+      (setq struct (nconc itm-lst (cdr (nreverse itm-lst-2))))
+      (setq end-lst (nconc end-lst (cdr (nreverse end-lst-2))))
+      ;; Associate each item to its end position.
+      (dolist (item struct)
+        (pcase-let ((`(,pos ,ind . ,_) item))
+          ;; Remove end candidates behind current item.
+          (while (<= (cdar end-lst) pos) (pop end-lst))
+          ;; Add end position to item assoc.
+          (let ((old-end (nthcdr 6 item))
+                (new-end (assoc-default ind end-lst '<=)))
+            (if old-end
+                (setcar old-end new-end)
+              (setcdr item (append (cdr item) (list new-end)))))))
+      ;; Return STRUCT.
+      struct)))
+
+(defun orgalist--goto-following-item (previous?)
+  (let* ((struct (orgalist--struct))
+         (prevs (org-list-prevs-alist struct))
+         (next (funcall (if previous? #'org-list-get-prev-item
+                          #'org-list-get-next-item)
+                        (line-beginning-position) struct prevs)))
+    (unless next
+      (error (if previous? "On first item" "On last item")))
+    (goto-char next)))
+
+(defun orgalist--move-item (up?)
+  (let* ((col (current-column))
+         (item (line-beginning-position))
+         (struct (orgalist--struct))
+         (next-item (funcall (if up? #'org-list-get-prev-item
+                               #'org-list-get-next-item)
+                             item struct (org-list-prevs-alist struct))))
+    (unless next-item
+      (user-error (if up? "Cannot move this item further up"
+                    "Cannot move this item further down")))
+    (setq struct
+          (org-list-swap-items (if up? next-item item)
+                               (if up? item next-item)
+                               struct))
+    (unless up?
+      (let ((prevs (org-list-prevs-alist struct)))
+        (goto-char (org-list-get-next-item item struct prevs))))
+    (org-list-write-struct struct (org-list-parents-alist struct))
+    (move-to-column col)))
+
+(defun orgalist--auto-fill ()
+  "Auto fill function."
+  (unless (org-match-line "^[ \t]*$")
+    (let ((item? (orgalist--in-item-p)))
+      (when item?
+        (orgalist--call-in-item normal-auto-fill-function item?)))))
+
+(defun orgalist--while-at-item (cmd)
+  "Return CMD when point is at a list item."
+  (when (orgalist--at-item-p) cmd))
+
+(defun orgalist--while-in-item (cmd)
+  "Return CMD when point is in a list item."
+  (when (orgalist--in-item-p) cmd))
+
+
+;;; Bindings and menu
+
+(defconst orgalist--maybe-fill
+  '(menu-item "" orgalist-fill-item :filter orgalist--while-in-item))
+
+(defconst orgalist--maybe-previous
+  '(menu-item "" orgalist-previous-item :filter orgalist--while-in-item))
+
+(defconst orgalist--maybe-next
+  '(menu-item "" orgalist-next-item :filter orgalist--while-in-item))
+
+(defconst orgalist--maybe-insert
+  '(menu-item "" orgalist-insert-item :filter orgalist--while-in-item))
+
+(defconst orgalist--maybe-move-up
+  '(menu-item "" orgalist-move-item-up :filter orgalist--while-at-item))
+
+(defconst orgalist--maybe-move-down
+  '(menu-item "" orgalist-move-item-down :filter orgalist--while-at-item))
+
+(defconst orgalist--maybe-outdent
+  '(menu-item "" orgalist-outdent-item :filter orgalist--while-at-item))
+
+(defconst orgalist--maybe-indent
+  '(menu-item "" orgalist-indent-item :filter orgalist--while-at-item))
+
+(defconst orgalist--maybe-outdent-tree
+  '(menu-item "" orgalist-outdent-item-tree :filter orgalist--while-at-item))
+
+(defconst orgalist--maybe-indent-tree
+  '(menu-item "" orgalist-indent-item-tree :filter orgalist--while-at-item))
+
+(defconst orgalist--maybe-cycle-bullet
+  '(menu-item "" orgalist-cycle-bullet :filter orgalist--while-at-item))
+
+(defconst orgalist--maybe-cycle-indentation
+  '(menu-item "" orgalist-cycle-indentation :filter orgalist--while-at-item))
+
+(defconst orgalist--maybe-check
+  '(menu-item "" orgalist-check-item :filter orgalist--while-at-item))
+
+(defconst orgalist--maybe-sort
+  '(menu-item "" orgalist-sort-items :filter orgalist--while-at-item))
+
+(defconst orgalist-mode-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map (kbd "M-q") orgalist--maybe-fill)
+    (define-key map (kbd "M-<up>") orgalist--maybe-previous)
+    (define-key map (kbd "M-<down>") orgalist--maybe-next)
+    (define-key map (kbd "M-RET") orgalist--maybe-insert)
+    (define-key map (kbd "M-S-<up>") orgalist--maybe-move-up)
+    (define-key map (kbd "M-S-<down>") orgalist--maybe-move-down)
+    (define-key map (kbd "M-<left>") orgalist--maybe-outdent)
+    (define-key map (kbd "M-<right>") orgalist--maybe-indent)
+    (define-key map (kbd "M-S-<left>") orgalist--maybe-outdent-tree)
+    (define-key map (kbd "M-S-<right>") orgalist--maybe-indent-tree)
+    (define-key map (kbd "C-c -") orgalist--maybe-cycle-bullet)
+    (define-key map (kbd "C-c C-c") orgalist--maybe-check)
+    (define-key map (kbd "C-c ^") orgalist--maybe-sort)
+    (define-key map (kbd "TAB") orgalist--maybe-cycle-indentation)
+    map))
+
+(easy-menu-define orgalist--menu
+  orgalist-mode-map
+  "Menu used when Orgalist mode is active."
+  '("Orgalist" :visible orgalist-mode
+    "----"
+    ["Insert item" orgalist-insert-item :active (orgalist--in-item-p)]
+    "----"
+    ["Check item" orgalist-check-item :active (orgalist--at-item-p)]
+    ["Cycle item" orgalist-cycle-bullet :active (orgalist--at-item-p)]
+    "----"
+    ["Previous item" orgalist-previous-item :active (orgalist--in-item-p)]
+    ["Next item" orgalist-next-item :active (orgalist--in-item-p)]
+    ["Move item up" orgalist-move-item-up :active (orgalist--at-item-p)]
+    ["Move item down" orgalist-move-item-gdown :active (orgalist--at-item-p)]
+    "---"
+    ["Indent item" orgalist-indent-item :active (orgalist--at-item-p)]
+    ["Indent item tree" orgalist-indent-item-tree :active 
(orgalist--at-item-p)]
+    ["Outdent item" orgalist-outdent-item :active (orgalist--at-item-p)]
+    ["Outdent item tree" orgalist-outdent-item-tree :active 
(orgalist--at-item-p)]
+    "---"
+    ["Sort items" orgalist-sort-items :active (orgalist--at-item-p)]))
+
+
+;;; Minor mode definition
+
+(define-minor-mode orgalist-mode
+  "Toggle the minor mode `orgalist-mode'.
+
+This mode is for using Org mode plain lists commands in other
+major modes.
+
+key             binding
+---             -------
+M-q             `orgalist-fill-item'
+M-RET           `orgalist-insert-item'
+M-<up>          `orgalist-previous-item'
+M-<down>        `orgalist-next-item'
+M-S-<up>        `orgalist-move-item-up'
+M-S-<down>      `orgalist-move-item-down'
+M-<left>        `orgalist-outdent-item'
+M-<right>       `orgalist-indent-item'
+M-S-<left>      `orgalist-outdent-item-tree'
+M-S-<right>     `orgalist-indent-item-tree'
+C-c -           `orgalist-cycle-bullet'
+C-c ^           `orgalist-sort-items'
+C-c C-c         `orgalist-check-item'
+TAB             `orgalist-cycle-indentation'"
+  :lighter " olst"
+  (cond
+   (orgalist-mode
+    (when (derived-mode-p 'org-mode)
+      (user-error "Cannot activate Orgalist mode in an Org buffer"))
+    (setq-local org-list-allow-alphabetical t)
+    (setq-local org-list-automatic-rules nil)
+    (setq-local org-list-demote-modify-bullet nil)
+    (setq-local org-list-two-spaces-after-bullet-regexp nil)
+    (add-function :before-until normal-auto-fill-function 
#'orgalist--auto-fill))
+   (t
+    (remove-function normal-auto-fill-function #'orgalist--auto-fill))))
+
+
+;;; Public functions
+
+;;;###autoload
+(defun orgalist-fill-item ()
+  "Fill item as a paragraph."
+  (interactive)
+  (let ((item (orgalist--in-item-p)))
+    (unless item (user-error "Not in a list"))
+    (orgalist--call-in-item #'fill-paragraph item)))
+
+;;;###autoload
+(defun orgalist-previous-item ()
+  "Move to the beginning of the previous item.
+Throw an error when at first item."
+  (interactive)
+  (let ((item (orgalist--in-item-p)))
+    (unless item (user-error "Not in a list"))
+    (goto-char item)
+    (orgalist--goto-following-item t)))
+
+;;;###autoload
+(defun orgalist-next-item ()
+  "Move to the beginning of the next item.
+Throw an error when at last item."
+  (interactive)
+  (let ((item (orgalist--in-item-p)))
+    (unless item (user-error "Not in a list"))
+    (goto-char item)
+    (orgalist--goto-following-item nil)))
+
+;;;###autoload
+(defun orgalist-insert-item (&optional checkbox)
+  "Insert a new item at the current level.
+
+If cursor is before first character after bullet of the item, the
+new item will be created before the current one.
+
+If CHECKBOX is non-nil, add a checkbox next to the bullet."
+  (interactive "P")
+  (let ((item? (orgalist--in-item-p)))
+    (unless item? (user-error "Not in a list"))
+    (let* ((struct (save-excursion (goto-char item?) (orgalist--struct)))
+           (prevs (org-list-prevs-alist struct))
+           ;; If we're in a description list, ask for the new term.
+           (desc
+            (and (eq 'descriptive (org-list-get-list-type item? struct prevs))
+                 " :: ")))
+      (setq struct (org-list-insert-item (point) struct prevs checkbox desc))
+      (org-list-write-struct struct (org-list-parents-alist struct))
+      (looking-at orgalist--item-re)
+      (goto-char (if (and (match-beginning 4)
+                          (save-match-data
+                            (string-match "\\." (match-string 1))))
+                     (match-beginning 4)
+                   (match-end 0)))
+      (when desc (backward-char 1)))))
+
+;;;###autoload
+(defun orgalist-move-item-down ()
+  "Move the item at point down, i.e. swap with following item.
+Sub-items (items with larger indentation) are considered part of
+the item, so this really moves item trees."
+  (interactive)
+  (unless (orgalist--at-item-p) (user-error "Not in a list"))
+  (orgalist--move-item nil))
+
+;;;###autoload
+(defun orgalist-move-item-up ()
+  "Move the item at point up, i.e. swap with previous item.
+Sub-items (items with larger indentation) are considered part of
+the item, so this really moves item trees."
+  (interactive)
+  (unless (orgalist--at-item-p) (user-error "Not in a list"))
+  (orgalist--move-item t))
+
+;;;###autoload
+(defun orgalist-cycle-indentation ()
+  "Cycle levels of indentation of an empty item.
+The first run indents the item, if applicable.  Subsequent runs
+outdent it at meaningful levels in the list.  When done, item is
+put back at its original position with its original bullet."
+  (interactive)
+  (unless (orgalist--at-item-p) (user-error "Not in a list"))
+  (let* ((struct (orgalist--struct))
+         (ind (org-list-get-ind (line-beginning-position) struct))
+         (bullet (org-trim (buffer-substring (line-beginning-position)
+                                             (line-end-position)))))
+    ;; Accept empty items or if cycle has already started.
+    (if (and (not (eq last-command 'orgalist-cycle-indentation))
+             (save-excursion
+               (beginning-of-line)
+               (looking-at orgalist--item-re))
+             (< (match-end 0)
+                (save-excursion
+                  (goto-char (org-list-get-item-end
+                              (line-beginning-position) struct))
+                  (skip-chars-backward " \r\t\n")
+                  (point))))
+        (progn
+          (setq this-command 'identity)
+          (error "Cannot move item"))
+      (setq this-command 'orgalist-cycle-indentation)
+      ;; When in the middle of the cycle, try to outdent first.  If
+      ;; it fails, and point is still at initial position, indent.
+      ;; Else, re-create it at its original position.
+      (if (eq last-command 'orgalist-cycle-indentation)
+          (cond
+           ((ignore-errors (org-list-indent-item-generic -1 t struct)))
+           ((and (= ind (car orgalist--cycling-state))
+                 (ignore-errors (org-list-indent-item-generic 1 t struct))))
+           (t (delete-region (line-beginning-position) (line-end-position))
+              (indent-to-column (car orgalist--cycling-state))
+              (insert (cdr orgalist--cycling-state) " ")
+              ;; Break cycle
+              (setq this-command 'identity)))
+        ;; If a cycle is starting, remember indentation and bullet,
+        ;; then try to indent.  If it fails, try to outdent.
+        (setq orgalist--cycling-state (cons ind bullet))
+        (cond
+         ((ignore-errors (org-list-indent-item-generic 1 t struct)))
+         ((ignore-errors (org-list-indent-item-generic -1 t struct)))
+         (t (user-error "Cannot move item")))))))
+
+;;;###autoload
+(defun orgalist-cycle-bullet ()
+  "Cycle through the different itemize/enumerate bullets.
+This cycle the entire list level through the sequence:
+
+   `-'  ->  `+'  ->  `1.'  ->  `a.'"
+  (interactive)
+  (unless (orgalist--at-item-p) (user-error "Not in a list"))
+  (save-excursion
+    (beginning-of-line)
+    (let* ((struct (orgalist--struct))
+           (parents (org-list-parents-alist struct))
+           (prevs (org-list-prevs-alist struct))
+           (list-beg (org-list-get-first-item (point) struct prevs))
+           (bullet (org-list-get-bullet list-beg struct))
+           (current (cond ((string-match "[A-Za-z]\\." bullet) "a.")
+                          ((string-match "\\." bullet) "1.")
+                          (t (org-trim bullet))))
+           (bullet-list
+            (append '("-" "+")
+                    ;; Description items cannot be numbered.
+                    (unless (org-at-item-description-p) '("1."))
+                    (unless (org-at-item-description-p) '("a."))))
+           (new (or (cadr (member current bullet-list))
+                    (car bullet-list))))
+      ;; Use a short variation of `org-list-write-struct' as there's
+      ;; no need to go through all the steps.
+      (let ((old-struct (copy-tree struct)))
+        (org-list-set-bullet list-beg struct (org-list-bullet-string new))
+        (org-list-struct-fix-bul struct prevs)
+        (org-list-struct-fix-ind struct parents)
+        (org-list-struct-apply-struct struct old-struct)))))
+
+;;;###autoload
+(defun orgalist-outdent-item ()
+  "Outdent a local list item, but not its children."
+  (interactive)
+  (unless (orgalist--at-item-p) (user-error "Not in a list"))
+  (org-list-indent-item-generic -1 t (orgalist--struct)))
+
+;;;###autoload
+(defun orgalist-indent-item ()
+  "Indent a local list item, but not its children."
+  (interactive)
+  (unless (orgalist--at-item-p) (user-error "Not in a list"))
+  (org-list-indent-item-generic 1 t (orgalist--struct)))
+
+;;;###autoload
+(defun orgalist-outdent-item-tree ()
+  "Outdent a local list item including its children."
+  (interactive)
+  (unless (orgalist--at-item-p) (user-error "Not in a list"))
+  (org-list-indent-item-generic -1 nil (orgalist--struct)))
+
+;;;###autoload
+(defun orgalist-indent-item-tree ()
+  "Indent a local list item including its children."
+  (interactive)
+  (unless (orgalist--at-item-p) (user-error "Not in a list"))
+  (org-list-indent-item-generic 1 nil (orgalist--struct)))
+
+;;;###autoload
+(defun orgalist-check-item (&optional arg)
+  "Toggle the checkbox in the current line.
+
+With prefix ARG, add or remove checkboxes.  With double prefix,
+set checkbox to [-].
+
+In any case, fix indentation, bullets and checkboxes in the list
+at point."
+  (interactive "P")
+  (unless (orgalist--at-item-p) (user-error "Not in a list"))
+  (save-excursion
+    (beginning-of-line)
+    (catch :repair-only
+      (let* ((regexp (concat "[ 
\t]*\\(?:[-+]\\|\\(?:[a-zA-Z]\\|[0-9]+\\)\\.\\)"
+                             "\\(?:[ 
address@hidden(?:[a-zA-Z]\\|[0-9]+\\)\\]\\)?"
+                             "[ \t]+\\(\\[[- xX]\\]\\)"))
+             (struct (orgalist--struct))
+             (struct-copy (copy-tree struct))
+             (parents (org-list-parents-alist struct))
+             (prevs (org-list-prevs-alist struct))
+             (current (and (looking-at regexp) (match-string 1)))
+             (new
+              (cond
+               ((equal arg '(16)) "[-]")
+               ((equal arg '(4)) (if current nil "[ ]"))
+               ((not current)
+                (throw :repair-only (org-list-write-struct struct parents)))
+               ((member current '("[X]" "[x]")) "[ ]")
+               (t "[X]"))))
+        (org-list-set-checkbox (point) struct new)
+        (when (org-list-struct-fix-box
+               struct parents prevs orgalist-ordered-checkboxes)
+          (org-list-write-struct struct-copy parents)
+          (error "Checkbox blocked because of unchecked box"))
+        (org-list-struct-apply-struct struct struct-copy)))))
+
+;;;###autoload
+(defun orgalist-sort-items (with-case sorting-type)
+  "Sort list items.
+
+The cursor may be at any item of the list that should be sorted.
+Sublists are not sorted.
+
+Comparing entries ignores case by default.  However, with an
+optional argument WITH-CASE, the sorting considers case as well.
+
+The command prompts for the SORTING-TYPE, which needs to be
+a character among ?n ?N ?a ?A ?x ?X.  Here is the detailed meaning
+of each character:
+
+n   Numerically, by converting the beginning of the item to a number.
+a   Alphabetically.  Only the first line of item is checked.
+x   By \"checked\" status of a check list.
+
+Capital letters reverse the sort order."
+  (interactive
+   (list current-prefix-arg
+         (progn
+           (message "Sort plain list: [a/A]lpha  [n/N]umeric  [x/X]checked:")
+           (read-char-exclusive))))
+  (unless (memq sorting-type '(?a ?A ?n ?N ?x ?X))
+    (user-error "Invalid sorting type"))
+  (let ((org-after-sorting-entries-or-items-hook nil))
+    (org-sort-list with-case sorting-type)))
+
+;;;###autoload
+(defun orgalist-insert-radio-list ()
+  "Insert a radio list template appropriate for current major mode."
+  (interactive)
+  (let* ((e (or (cl-assoc-if #'derived-mode-p orgalist-radio-list-templates)
+                (error "No radio list setup defined for %S" major-mode)))
+         (name (read-string "List name: "))
+         (txt (replace-regexp-in-string "%n" name (nth 1 e) t t)))
+    (unless (bolp) (insert "\n"))
+    (save-excursion (insert txt))))
+
+;;;###autoload
+(defun orgalist-send-list (&optional maybe)
+  "Send a transformed version of this list to the receiver position.
+With argument MAYBE, fail quietly if no transformation is defined
+for this list."
+  (interactive)
+  (catch 'exit
+    (unless (orgalist--at-item-p) (error "Not at a list item"))
+    (save-excursion
+      (let ((case-fold-search t))
+        (re-search-backward "^[ \t]*#\\+ORGLST:" nil t)
+        (unless (looking-at
+                 "[ \t]*#\\+ORGLST:[ \t]+SEND[ \t]+\\(\\S-+\\)[ \t]+\\([^ 
\t\n]+\\)")
+          (if maybe (throw 'exit nil)
+            (error "Don't know how to transform this list")))))
+    (let* ((name (regexp-quote (match-string 1)))
+           (transform (intern (match-string 2)))
+           (bottom-point
+            (save-excursion
+              (re-search-forward
+               "\\(\\\\end{comment}\\|@end ignore\\|-->\\)" nil t)
+              (match-beginning 0)))
+           (top-point
+            (progn
+              (re-search-backward "#\\+ORGLST" nil t)
+              (re-search-forward (org-item-beginning-re) bottom-point t)
+              (match-beginning 0)))
+           (plain-list (save-excursion
+                         (goto-char top-point)
+                         (org-list-to-lisp))))
+      (unless (fboundp transform)
+        (error "No such transformation function %s" transform))
+      (let ((txt (funcall transform plain-list)))
+        ;; Find the insertion(s) place(s).
+        (save-excursion
+          (goto-char (point-min))
+          (let ((receiver-count 0)
+                (begin-re (format "BEGIN +RECEIVE +ORGLST +%s\\([ \t]\\|$\\)"
+                                  name))
+                (end-re (format "END +RECEIVE +ORGLST +%s\\([ \t]\\|$\\)"
+                                name)))
+            (while (re-search-forward begin-re nil t)
+              (cl-incf receiver-count)
+              (let ((beg (line-beginning-position 2)))
+                (unless (re-search-forward end-re nil t)
+                  (user-error "Cannot find end of receiver location at %d" 
beg))
+                (beginning-of-line)
+                (delete-region beg (point))
+                (insert txt "\n")))
+            (cond
+             ((> receiver-count 1)
+              (message "List converted and installed at receiver locations"))
+             ((= receiver-count 1)
+              (message "List converted and installed at receiver location"))
+             (t (user-error "No valid receiver location found")))))))))
+
+
+(provide 'orgalist)
+;;; orgalist.el ends here



reply via email to

[Prev in Thread] Current Thread [Next in Thread]