Commit | Line | Data |
---|---|---|
c8d0cf5c | 1 | ;;; org-inlinetask.el --- Tasks independent of outline hierarchy |
26bd9e87 | 2 | |
ba318903 | 3 | ;; Copyright (C) 2009-2014 Free Software Foundation, Inc. |
c8d0cf5c CD |
4 | ;; |
5 | ;; Author: Carsten Dominik <carsten at orgmode dot org> | |
6 | ;; Keywords: outlines, hypermedia, calendar, wp | |
7 | ;; Homepage: http://orgmode.org | |
26bd9e87 GM |
8 | |
9 | ;; This file is part of GNU Emacs. | |
10 | ||
11 | ;; GNU Emacs is free software: you can redistribute it and/or modify | |
54a0dee5 | 12 | |
c8d0cf5c | 13 | ;; it under the terms of the GNU General Public License as published by |
26bd9e87 GM |
14 | ;; the Free Software Foundation, either version 3 of the License, or |
15 | ;; (at your option) any later version. | |
c8d0cf5c CD |
16 | |
17 | ;; GNU Emacs is distributed in the hope that it will be useful, | |
18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
20 | ;; GNU General Public License for more details. | |
21 | ||
22 | ;; You should have received a copy of the GNU General Public License | |
26bd9e87 GM |
23 | ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. |
24 | ||
c8d0cf5c CD |
25 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
26 | ;; | |
27 | ;;; Commentary: | |
28 | ;; | |
29 | ;; This module implements inline tasks in Org-mode. Inline tasks are | |
271672fa BG |
30 | ;; tasks that have all the properties of normal outline nodes, |
31 | ;; including the ability to store meta data like scheduling dates, | |
32 | ;; TODO state, tags and properties. However, these nodes are treated | |
33 | ;; specially by the visibility cycling. | |
c8d0cf5c | 34 | ;; |
271672fa BG |
35 | ;; Visibility cycling exempts these nodes from cycling. So whenever |
36 | ;; their parent is opened, so are these tasks. This will only work | |
37 | ;; with `org-cycle', so if you are also using other commands to | |
38 | ;; show/hide entries, you will occasionally find these tasks to behave | |
39 | ;; like all other outline nodes, seemingly splitting the text of the | |
40 | ;; parent into children. | |
c8d0cf5c | 41 | ;; |
271672fa BG |
42 | ;; Special fontification of inline tasks, so that they can be |
43 | ;; immediately recognized. From the stars of the headline, only the | |
44 | ;; first and the last two will be visible, the others will be hidden | |
45 | ;; using the `org-hide' face. | |
c8d0cf5c | 46 | ;; |
271672fa BG |
47 | ;; An inline task is identified solely by a minimum outline level, |
48 | ;; given by the variable `org-inlinetask-min-level', default 15. | |
c8d0cf5c | 49 | ;; |
3ab2c837 BG |
50 | ;; If you need to have a time planning line (DEADLINE etc), drawers, |
51 | ;; for example LOGBOOK of PROPERTIES, or even normal text as part of | |
52 | ;; the inline task, you must add an "END" headline with the same | |
53 | ;; number of stars. | |
c8d0cf5c | 54 | ;; |
3ab2c837 BG |
55 | ;; As an example, here are two valid inline tasks: |
56 | ;; | |
57 | ;; **************** TODO a small task | |
58 | ;; | |
59 | ;; and | |
60 | ;; | |
61 | ;; **************** TODO another small task | |
c8d0cf5c CD |
62 | ;; DEADLINE: <2009-03-30 Mon> |
63 | ;; :PROPERTIES: | |
64 | ;; :SOMETHING: or other | |
65 | ;; :END: | |
66 | ;; And here is some extra text | |
67 | ;; **************** END | |
68 | ;; | |
69 | ;; Also, if you want to use refiling and archiving for inline tasks, | |
70 | ;; The END line must be present to make things work properly. | |
71 | ;; | |
c7cf0ebc BG |
72 | ;; Note that you should not try to use inline tasks within plain list, |
73 | ;; visibility cycling is known to be problematic when doing so. | |
74 | ;; | |
8bfe682a | 75 | ;; This package installs one new command: |
c8d0cf5c CD |
76 | ;; |
77 | ;; C-c C-x t Insert a new inline task with END line | |
78 | ||
86fbb8ca | 79 | ;;; Code: |
c8d0cf5c CD |
80 | |
81 | (require 'org) | |
82 | ||
83 | (defgroup org-inlinetask nil | |
84 | "Options concerning inline tasks in Org mode." | |
85 | :tag "Org Inline Tasks" | |
86 | :group 'org-structure) | |
87 | ||
88 | (defcustom org-inlinetask-min-level 15 | |
89 | "Minimum level a headline must have before it is treated as an inline task. | |
8223b1d2 BG |
90 | Don't set it to something higher than `29' or clocking will break since this |
91 | is the hardcoded maximum number of stars `org-clock-sum' will work with. | |
92 | ||
c8d0cf5c CD |
93 | It is strongly recommended that you set `org-cycle-max-level' not at all, |
94 | or to a number smaller than this one. In fact, when `org-cycle-max-level' is | |
95 | not set, it will be assumed to be one less than the value of smaller than | |
96 | the value of this variable." | |
97 | :group 'org-inlinetask | |
afe98dfa CD |
98 | :type '(choice |
99 | (const :tag "Off" nil) | |
100 | (integer))) | |
c8d0cf5c | 101 | |
8223b1d2 BG |
102 | (defcustom org-inlinetask-show-first-star nil |
103 | "Non-nil means display the first star of an inline task as additional marker. | |
104 | When nil, the first star is not shown." | |
105 | :tag "Org Inline Tasks" | |
3c8b09ca BG |
106 | :group 'org-structure |
107 | :type 'boolean) | |
8223b1d2 | 108 | |
c8d0cf5c CD |
109 | (defvar org-odd-levels-only) |
110 | (defvar org-keyword-time-regexp) | |
111 | (defvar org-drawer-regexp) | |
112 | (defvar org-complex-heading-regexp) | |
113 | (defvar org-property-end-re) | |
114 | ||
afe98dfa | 115 | (defcustom org-inlinetask-default-state nil |
86fbb8ca CD |
116 | "Non-nil means make inline tasks have a TODO keyword initially. |
117 | This should be the state `org-inlinetask-insert-task' should use by | |
118 | default, or nil of no state should be assigned." | |
119 | :group 'org-inlinetask | |
372d7b21 | 120 | :version "24.1" |
86fbb8ca CD |
121 | :type '(choice |
122 | (const :tag "No state" nil) | |
123 | (string :tag "Specific state"))) | |
124 | ||
125 | (defun org-inlinetask-insert-task (&optional no-state) | |
126 | "Insert an inline task. | |
afe98dfa | 127 | If prefix arg NO-STATE is set, ignore `org-inlinetask-default-state'." |
86fbb8ca | 128 | (interactive "P") |
e66ba1df BG |
129 | ;; Error when inside an inline task, except if point was at its very |
130 | ;; beginning, in which case the new inline task will be inserted | |
131 | ;; before this one. | |
132 | (when (and (org-inlinetask-in-task-p) | |
133 | (not (and (org-inlinetask-at-task-p) (bolp)))) | |
134 | (error "Cannot nest inline tasks")) | |
c8d0cf5c | 135 | (or (bolp) (newline)) |
e66ba1df BG |
136 | (let* ((indent (if org-odd-levels-only |
137 | (1- (* 2 org-inlinetask-min-level)) | |
138 | org-inlinetask-min-level)) | |
139 | (indent-string (concat (make-string indent ?*) " "))) | |
140 | (insert indent-string | |
141 | (if (or no-state (not org-inlinetask-default-state)) | |
142 | "\n" | |
143 | (concat org-inlinetask-default-state " \n")) | |
144 | indent-string "END\n")) | |
c8d0cf5c CD |
145 | (end-of-line -1)) |
146 | (define-key org-mode-map "\C-c\C-xt" 'org-inlinetask-insert-task) | |
147 | ||
acedf35c CD |
148 | (defun org-inlinetask-outline-regexp () |
149 | "Return string matching an inline task heading. | |
150 | The number of levels is controlled by `org-inlinetask-min-level'." | |
151 | (let ((nstars (if org-odd-levels-only | |
152 | (1- (* org-inlinetask-min-level 2)) | |
153 | org-inlinetask-min-level))) | |
154 | (format "^\\(\\*\\{%d,\\}\\)[ \t]+" nstars))) | |
155 | ||
3ab2c837 BG |
156 | (defun org-inlinetask-at-task-p () |
157 | "Return true if point is at beginning of an inline task." | |
158 | (save-excursion | |
159 | (beginning-of-line) | |
160 | (and (looking-at (concat (org-inlinetask-outline-regexp) "\\(.*\\)")) | |
161 | (not (string-match "^end[ \t]*$" (downcase (match-string 2))))))) | |
162 | ||
afe98dfa CD |
163 | (defun org-inlinetask-in-task-p () |
164 | "Return true if point is inside an inline task." | |
165 | (save-excursion | |
3ab2c837 BG |
166 | (beginning-of-line) |
167 | (let* ((case-fold-search t) | |
168 | (stars-re (org-inlinetask-outline-regexp)) | |
afe98dfa | 169 | (task-beg-re (concat stars-re "\\(?:.*\\)")) |
3ab2c837 BG |
170 | (task-end-re (concat stars-re "END[ \t]*$"))) |
171 | (or (org-looking-at-p task-beg-re) | |
afe98dfa | 172 | (and (re-search-forward "^\\*+[ \t]+" nil t) |
3ab2c837 | 173 | (progn (beginning-of-line) (org-looking-at-p task-end-re))))))) |
afe98dfa | 174 | |
acedf35c CD |
175 | (defun org-inlinetask-goto-beginning () |
176 | "Go to the beginning of the inline task at point." | |
177 | (end-of-line) | |
3ab2c837 BG |
178 | (let ((case-fold-search t) |
179 | (inlinetask-re (org-inlinetask-outline-regexp))) | |
180 | (re-search-backward inlinetask-re nil t) | |
181 | (when (org-looking-at-p (concat inlinetask-re "END[ \t]*$")) | |
182 | (re-search-backward inlinetask-re nil t)))) | |
acedf35c CD |
183 | |
184 | (defun org-inlinetask-goto-end () | |
e66ba1df BG |
185 | "Go to the end of the inline task at point. |
186 | Return point." | |
187 | (save-match-data | |
188 | (beginning-of-line) | |
189 | (let* ((case-fold-search t) | |
190 | (inlinetask-re (org-inlinetask-outline-regexp)) | |
191 | (task-end-re (concat inlinetask-re "END[ \t]*$"))) | |
192 | (cond | |
193 | ((looking-at task-end-re) (forward-line)) | |
194 | ((looking-at inlinetask-re) | |
195 | (forward-line) | |
196 | (cond | |
197 | ((looking-at task-end-re) (forward-line)) | |
198 | ((looking-at inlinetask-re)) | |
199 | ((org-inlinetask-in-task-p) | |
200 | (re-search-forward inlinetask-re nil t) | |
201 | (forward-line)))) | |
202 | (t (re-search-forward inlinetask-re nil t) | |
203 | (forward-line))) | |
204 | (point)))) | |
acedf35c CD |
205 | |
206 | (defun org-inlinetask-get-task-level () | |
207 | "Get the level of the inline task around. | |
208 | This assumes the point is inside an inline task." | |
209 | (save-excursion | |
210 | (end-of-line) | |
211 | (re-search-backward (org-inlinetask-outline-regexp) nil t) | |
212 | (- (match-end 1) (match-beginning 1)))) | |
213 | ||
3ab2c837 BG |
214 | (defun org-inlinetask-promote () |
215 | "Promote the inline task at point. | |
216 | If the task has an end part, promote it. Also, prevents level from | |
217 | going below `org-inlinetask-min-level'." | |
218 | (interactive) | |
219 | (if (not (org-inlinetask-in-task-p)) | |
220 | (error "Not in an inline task") | |
221 | (save-excursion | |
222 | (let* ((lvl (org-inlinetask-get-task-level)) | |
223 | (next-lvl (org-get-valid-level lvl -1)) | |
224 | (diff (- next-lvl lvl)) | |
225 | (down-task (concat (make-string next-lvl ?*))) | |
226 | beg) | |
227 | (if (< next-lvl org-inlinetask-min-level) | |
228 | (error "Cannot promote an inline task at minimum level") | |
229 | (org-inlinetask-goto-beginning) | |
230 | (setq beg (point)) | |
231 | (replace-match down-task nil t nil 1) | |
232 | (org-inlinetask-goto-end) | |
233 | (if (eobp) (beginning-of-line) (forward-line -1)) | |
234 | (unless (= (point) beg) | |
235 | (replace-match down-task nil t nil 1) | |
236 | (when org-adapt-indentation | |
237 | (goto-char beg) | |
238 | (org-fixup-indentation diff)))))))) | |
239 | ||
240 | (defun org-inlinetask-demote () | |
241 | "Demote the inline task at point. | |
242 | If the task has an end part, also demote it." | |
243 | (interactive) | |
244 | (if (not (org-inlinetask-in-task-p)) | |
245 | (error "Not in an inline task") | |
246 | (save-excursion | |
247 | (let* ((lvl (org-inlinetask-get-task-level)) | |
248 | (next-lvl (org-get-valid-level lvl 1)) | |
249 | (diff (- next-lvl lvl)) | |
250 | (down-task (concat (make-string next-lvl ?*))) | |
251 | beg) | |
252 | (org-inlinetask-goto-beginning) | |
253 | (setq beg (point)) | |
254 | (replace-match down-task nil t nil 1) | |
255 | (org-inlinetask-goto-end) | |
256 | (if (eobp) (beginning-of-line) (forward-line -1)) | |
257 | (unless (= (point) beg) | |
258 | (replace-match down-task nil t nil 1) | |
259 | (when org-adapt-indentation | |
260 | (goto-char beg) | |
261 | (org-fixup-indentation diff))))))) | |
262 | ||
8bfe682a CD |
263 | (defun org-inlinetask-get-current-indentation () |
264 | "Get the indentation of the last non-while line above this one." | |
265 | (save-excursion | |
266 | (beginning-of-line 1) | |
267 | (skip-chars-backward " \t\n") | |
268 | (beginning-of-line 1) | |
269 | (or (org-at-item-p) | |
270 | (looking-at "[ \t]*")) | |
271 | (goto-char (match-end 0)) | |
272 | (current-column))) | |
c8d0cf5c | 273 | |
e66ba1df BG |
274 | (defvar org-indent-indentation-per-level) ; defined in org-indent.el |
275 | ||
276 | (defface org-inlinetask | |
277 | (org-compatible-face 'shadow '((t (:bold t)))) | |
278 | "Face for inlinetask headlines." | |
279 | :group 'org-faces) | |
280 | ||
c8d0cf5c | 281 | (defun org-inlinetask-fontify (limit) |
e66ba1df | 282 | "Fontify the inline tasks down to LIMIT." |
c8d0cf5c CD |
283 | (let* ((nstars (if org-odd-levels-only |
284 | (1- (* 2 (or org-inlinetask-min-level 200))) | |
285 | (or org-inlinetask-min-level 200))) | |
286 | (re (concat "^\\(\\*\\)\\(\\*\\{" | |
8223b1d2 BG |
287 | (format "%d" (- nstars 3)) |
288 | ",\\}\\)\\(\\*\\* .*\\)")) | |
e66ba1df | 289 | ;; Virtual indentation will add the warning face on the first |
8223b1d2 | 290 | ;; star. Thus, in that case, only hide it. |
e66ba1df BG |
291 | (start-face (if (and (org-bound-and-true-p org-indent-mode) |
292 | (> org-indent-indentation-per-level 1)) | |
293 | 'org-hide | |
294 | 'org-warning))) | |
c8d0cf5c | 295 | (while (re-search-forward re limit t) |
8223b1d2 BG |
296 | (if org-inlinetask-show-first-star |
297 | (add-text-properties (match-beginning 1) (match-end 1) | |
298 | `(face ,start-face font-lock-fontified t))) | |
299 | (add-text-properties (match-beginning | |
300 | (if org-inlinetask-show-first-star 2 1)) | |
301 | (match-end 2) | |
c8d0cf5c CD |
302 | '(face org-hide font-lock-fontified t)) |
303 | (add-text-properties (match-beginning 3) (match-end 3) | |
e66ba1df | 304 | '(face org-inlinetask font-lock-fontified t))))) |
c8d0cf5c | 305 | |
3ab2c837 BG |
306 | (defun org-inlinetask-toggle-visibility () |
307 | "Toggle visibility of inline task at point." | |
308 | (let ((end (save-excursion | |
309 | (org-inlinetask-goto-end) | |
310 | (if (bolp) (1- (point)) (point)))) | |
311 | (start (save-excursion | |
312 | (org-inlinetask-goto-beginning) | |
313 | (point-at-eol)))) | |
314 | (cond | |
315 | ;; Nothing to show/hide. | |
316 | ((= end start)) | |
317 | ;; Inlinetask was folded: expand it. | |
318 | ((get-char-property (1+ start) 'invisible) | |
271672fa BG |
319 | (outline-flag-region start end nil) |
320 | (org-cycle-hide-drawers 'children)) | |
3ab2c837 BG |
321 | (t (outline-flag-region start end t))))) |
322 | ||
c8d0cf5c CD |
323 | (defun org-inlinetask-remove-END-maybe () |
324 | "Remove an END line when present." | |
325 | (when (looking-at (format "\\([ \t]*\n\\)*\\*\\{%d,\\}[ \t]+END[ \t]*$" | |
326 | org-inlinetask-min-level)) | |
327 | (replace-match ""))) | |
328 | ||
c8d0cf5c CD |
329 | (eval-after-load "org" |
330 | '(add-hook 'org-font-lock-hook 'org-inlinetask-fontify)) | |
331 | ||
332 | (provide 'org-inlinetask) | |
333 | ||
334 | ;;; org-inlinetask.el ends here |