diff options
Diffstat (limited to 'lisp')
-rw-r--r-- | lisp/acdw-org.el | 187 |
1 files changed, 187 insertions, 0 deletions
diff --git a/lisp/acdw-org.el b/lisp/acdw-org.el new file mode 100644 index 0000000..1522f78 --- /dev/null +++ b/lisp/acdw-org.el | |||
@@ -0,0 +1,187 @@ | |||
1 | ;;; acdw-org.el -*- lexical-binding: t; coding: utf-8-unix -*- | ||
2 | ;; | ||
3 | ;; Author: Various | ||
4 | ;; URL: https://tildegit.org/acdw/emacs | ||
5 | ;; | ||
6 | ;; This file is NOT part of GNU Emacs. | ||
7 | ;; | ||
8 | ;;; License: | ||
9 | ;; | ||
10 | ;; Everyone is permitted to do whatever with this software, without | ||
11 | ;; limitation. This software comes without any warranty whatsoever, | ||
12 | ;; but with two pieces of advice: | ||
13 | ;; - Don't hurt yourself. | ||
14 | ;; - Make good choices. | ||
15 | ;; | ||
16 | ;;; Commentary: | ||
17 | ;; | ||
18 | ;; This file is for the weird little `org-mode' functions that just take up | ||
19 | ;; space in my main init file. I've tried to give credit where credit is due. | ||
20 | ;; | ||
21 | ;;; Code: | ||
22 | |||
23 | ;;; unpackaged.el: https://github.com/alphapapa/unpackaged.el | ||
24 | |||
25 | ;;; ORG-RETURN-DWIM | ||
26 | |||
27 | (defun unpackaged/org-element-descendant-of (type element) | ||
28 | "Return non-nil if ELEMENT is a descendant of TYPE. | ||
29 | TYPE should be an element type, like `item' or `paragraph'. | ||
30 | ELEMENT should be a list like that returned by | ||
31 | `org-element-context'." | ||
32 | ;; MAYBE: Use `org-element-lineage'. | ||
33 | (when-let* ((parent (org-element-property :parent element))) | ||
34 | (or (eq type (car parent)) | ||
35 | (unpackaged/org-element-descendant-of type parent)))) | ||
36 | |||
37 | (defun unpackaged/org-return-dwim (&optional default) | ||
38 | "A helpful replacement for `org-return'. With prefix, | ||
39 | call `org-return'. | ||
40 | |||
41 | On headings, move point to position after entry content. In | ||
42 | lists, insert a new item or end the list, with checkbox if | ||
43 | appropriate. In tables, insert a new row or end the table." | ||
44 | ;; Inspired by John Kitchin: | ||
45 | ;; http://kitchingroup.cheme.cmu.edu/blog/2017/04/09/A-better-return-in-org-mode/ | ||
46 | (interactive "P") | ||
47 | (if default | ||
48 | (org-return) | ||
49 | (cond | ||
50 | ;; Act depending on context around point. | ||
51 | |||
52 | ;; NOTE: I prefer RET to not follow links, but by uncommenting | ||
53 | ;; this block, links will be followed. | ||
54 | ;; FURTHER NOTE: Ideally, I would follow links unless point | ||
55 | ;; /appeared/ to be at the end of the line (even if it's still | ||
56 | ;; inside the link) -- when it would do `org-return'. That | ||
57 | ;; would take some /doing/, however. | ||
58 | |||
59 | ;; ((eq 'link (car (org-element-context))) | ||
60 | ;; ;; Link: Open it. | ||
61 | ;; (org-open-at-point-global)) | ||
62 | |||
63 | ((org-at-heading-p) | ||
64 | ;; Heading: Move to position after entry content. NOTE: This is | ||
65 | ;; probably the most interesting feature of this function. | ||
66 | (let ((heading-start (org-entry-beginning-position))) | ||
67 | (goto-char (org-entry-end-position)) | ||
68 | (cond ((and (org-at-heading-p) | ||
69 | (= heading-start (org-entry-beginning-position))) | ||
70 | ;; Entry ends on its heading; add newline after | ||
71 | (end-of-line) | ||
72 | (insert "\n\n")) | ||
73 | (t | ||
74 | ;; Entry ends after its heading; back up | ||
75 | (forward-line -1) | ||
76 | (end-of-line) | ||
77 | (when (org-at-heading-p) | ||
78 | ;; At the same heading | ||
79 | (forward-line) | ||
80 | (insert "\n") | ||
81 | (forward-line -1)) | ||
82 | ;; FIXME: looking-back is supposed to be called with | ||
83 | ;; more arguments. | ||
84 | (while (not (looking-back (rx | ||
85 | (repeat 3 | ||
86 | (seq (optional blank) | ||
87 | "\n"))) | ||
88 | nil)) | ||
89 | (insert "\n")) | ||
90 | (forward-line -1))))) | ||
91 | |||
92 | ((org-at-item-checkbox-p) | ||
93 | ;; Checkbox: Insert new item with checkbox. | ||
94 | (org-insert-todo-heading nil)) | ||
95 | |||
96 | ((org-in-item-p) | ||
97 | ;; Plain list. Yes, this gets a little complicated... | ||
98 | (let ((context (org-element-context))) | ||
99 | (if (or (eq 'plain-list (car context)) ; First item in list | ||
100 | (and (eq 'item (car context)) | ||
101 | (not (eq (org-element-property | ||
102 | :contents-begin context) | ||
103 | (org-element-property | ||
104 | :contents-end context)))) | ||
105 | ;; Element in list item, e.g. a link | ||
106 | (unpackaged/org-element-descendant-of 'item context)) | ||
107 | ;; Non-empty item: Add new item. | ||
108 | (org-insert-item) | ||
109 | ;; Empty item: Close the list. | ||
110 | ;; TODO: Do this with org functions rather than operating | ||
111 | ;; on the text. Can't seem to find the right function. | ||
112 | (delete-region (line-beginning-position) (line-end-position)) | ||
113 | (insert "\n")))) | ||
114 | |||
115 | ((when (fboundp 'org-inlinetask-in-task-p) | ||
116 | (org-inlinetask-in-task-p)) | ||
117 | ;; Inline task: Don't insert a new heading. | ||
118 | (org-return)) | ||
119 | |||
120 | ((org-at-table-p) | ||
121 | (cond ((save-excursion | ||
122 | (beginning-of-line) | ||
123 | ;; See `org-table-next-field'. | ||
124 | (cl-loop with end = (line-end-position) | ||
125 | for cell = (org-element-table-cell-parser) | ||
126 | always (equal (org-element-property | ||
127 | :contents-begin cell) | ||
128 | (org-element-property | ||
129 | :contents-end cell)) | ||
130 | while (re-search-forward "|" end t))) | ||
131 | ;; Empty row: end the table. | ||
132 | (delete-region (line-beginning-position) | ||
133 | (line-end-position)) | ||
134 | (org-return)) | ||
135 | (t | ||
136 | ;; Non-empty row: call `org-return'. | ||
137 | (org-return)))) | ||
138 | (t | ||
139 | ;; All other cases: call `org-return'. | ||
140 | (org-return))))) | ||
141 | |||
142 | ;;; ORG-FIX-BLANK-LINES ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
143 | |||
144 | (defun unpackaged/org-fix-blank-lines (&optional prefix) | ||
145 | "Ensure that blank lines exist between headings and | ||
146 | between headings and their contents. With prefix, operate on | ||
147 | whole buffer. Ensures that blank lines exist after each | ||
148 | headings's drawers." | ||
149 | (interactive "P") | ||
150 | (org-map-entries (lambda () | ||
151 | (org-with-wide-buffer | ||
152 | ;; `org-map-entries' narrows the buffer, which | ||
153 | ;; prevents us from seeing newlines before the | ||
154 | ;; current heading, so we do this part widened. | ||
155 | (while (not (looking-back "\n\n" nil)) | ||
156 | ;; Insert blank lines before heading. | ||
157 | (insert "\n"))) | ||
158 | (let ((end (org-entry-end-position))) | ||
159 | ;; Insert blank lines before entry content | ||
160 | (forward-line) | ||
161 | (while (and (org-at-planning-p) | ||
162 | (< (point) (point-max))) | ||
163 | ;; Skip planning lines | ||
164 | (forward-line)) | ||
165 | (while (re-search-forward | ||
166 | org-drawer-regexp end t) | ||
167 | ;; Skip drawers. You might think that | ||
168 | ;; `org-at-drawer-p' would suffice, but for | ||
169 | ;; some reason it doesn't work correctly when | ||
170 | ;; operating on hidden text. This works, taken | ||
171 | ;; from `org-agenda-get-some-entry-text'. | ||
172 | (re-search-forward "^[ \t]*:END:.*\n?" end t) | ||
173 | (goto-char (match-end 0))) | ||
174 | (unless (or (= (point) (point-max)) | ||
175 | (org-at-heading-p) | ||
176 | (looking-at-p "\n")) | ||
177 | (insert "\n")))) | ||
178 | t (if prefix | ||
179 | nil | ||
180 | 'tree))) | ||
181 | |||
182 | (defun acdw/hook--org-mode-fix-blank-lines () | ||
183 | (when (eq major-mode 'org-mode) | ||
184 | (let ((current-prefix-arg 4)) | ||
185 | (call-interactively #'unpackaged/org-fix-blank-lines)))) | ||
186 | |||
187 | (provide 'acdw-org) | ||