source: main/org-autofocus.el

Last change on this file was main,19, checked in by David Owen <dsowen@…>, 3 years ago

Added ORG-AF--END-OF-TASK, ORG-AF--NEXT-TASK, ORG-AF--FIRST-TASK, and ORG-AF--PREV-MARK; and fixed hiding before/between dotted tasks

File size: 9.1 KB
Line 
1;;; org-autofocus.el --- Mark Forster's Auto Focus system for Org  -*- lexical-binding: t; -*-
2
3;; Copyright (C) 2020 David Owen
4
5;; Author: David Owen <dsowen@fugue88.ws>
6;; Keywords: autofocus
7;; Package-Requires: (org)
8
9;;; Commentary:
10
11;; This minor mode works primarily by lightly analyzing the Org Agenda todo list
12;; and adding some extra text properties, and by the addition of a TOUCHED
13;; property to tasks' property drawers.
14;;
15;; Tasks are identified by looking at the beginning of a line for the TYPE
16;; property that Org adds.  The code does /not/ verify that the property's value
17;; is "todo".  It's assumed that a task runs from the beginning of a line to the
18;; end of the same line.
19;;
20;; A task is "dotted" or "marked" by setting the DISPLAY properties of the first
21;; character of the line to "*".
22;;
23;; A range of tasks is hidden by setting the INVISIBLE property of the entire
24;; line to T.  Because this hides the newline, the range of hidden tasks
25;; actually appears at the beginning of the line for the following unhidden
26;; task; see ORG-AF--BEG-OF-TASK.
27;;
28;; A marked task is dimmed by adding an overlay with FACE set to
29;; 'ORG-AGENDA-DIMMED-TODO-FACE.
30;;
31;; Properties are added as one property representing the state
32;; (e.g. ORG-AF-MARKED) set to T for easy property testing, and the CATEGORY
33;; property set to the state (e.g. 'ORG-AF-MARKED) to handle the actual visual
34;; characteristics.  See variable ORG-AF-MARKED and following functions for an
35;; example.
36;;
37;; Tasks are sorted according to the value of the TOUCHED property;
38;; least-recently-touched tasks are followed by most-recently-touched tasks.
39;; Any task that doesn't already have the property is touched at the time of
40;; sorting.
41;;
42;; The user may mark a task by pressing "."; all tasks between it and the
43;; previous marked task will be hidden, and all previous marked tasks dimmed.
44;; The idea is that hidden and dimmed tasks are not to be manipulated any
45;; further, until subsquent marked tasks have been handled in some way.
46;;
47;; The user may defer a task by pressing "d"; the task will be unmarked,
48;; touched, and sorted to the end.
49;;
50;; Finally, the user may complete a task by pressing "c" or "t"; the task will
51;; be unmarked, completed by Org's code, touched, and sorted to the end.
52
53;;; Known issues:
54
55;; Keymaps and commands are not sufficiently controlled.  A user may manipulate
56;; a task in a way this minor mode doesn't handle.  A user may also manipulate
57;; tasks that are dimmed.
58
59;; Redo'ing the agenda loses the minor mode; it's necessary to run ORG-AUTOFOCUS
60;; every time the minor mode is desired.
61
62;; I haven't yet found a way to enable this minor mode without using a command
63;; that wraps the call to ORG-AGENDA with some overriding bindings in place.
64;; This is the primary reason that redo isn't supported.
65
66;;; Code:
67
68(require 'org-agenda)
69
70
71(defvar org-af-marked 'org-af-marked)
72(put 'org-af-marked 'org-af-marked t)
73(put 'org-af-marked 'display "*")
74
75(defun org-af--mark (point)
76  "Mark the task at POINT."
77  (let ((o (make-overlay point (1+ point))))
78    (overlay-put o 'category org-af-marked)))
79
80(defun org-af--unmark (point)
81  "Unmark the task at POINT."
82  (remove-overlays point (1+ point) org-af-marked t))
83
84(defun org-af--marked-p (point)
85  "Test whether the task at POINT is marked."
86  (get-char-property point 'org-af-marked))
87
88
89(defvar org-af-hidden 'org-af-hidden)
90(put 'org-af-hidden 'org-af-hidden t)
91(put 'org-af-hidden 'invisible t)
92
93
94(defvar org-af-dimmed 'org-af-dimmed)
95(put 'org-af-dimmed 'org-af-dimmed t)
96(put 'org-af-dimmed 'face 'org-agenda-dimmed-todo-face)
97(put 'org-af-dimmed 'evaporate t)
98
99(defun org-af--dim (start end)
100  "Dim the task from START to END."
101  (let ((o (make-overlay start end)))
102    (overlay-put o 'category org-af-dimmed)))
103
104(defun org-af--undim (start end)
105  "Undim the task from START to END."
106  (remove-overlays start end 'org-af-dimmed t))
107
108(defun org-af--dimmed-p (point)
109  "Test whether the task at POINT is dimmed."
110  (get-char-property point 'org-af-dimmed))
111
112
113(defun org-af--beg-of-task ()
114  "Move point to the beginning of the task at point.
115Skips over any hidden tasks the are on the same line."
116  (beginning-of-line)
117  (when (get-text-property (point) 'org-af-hidden)
118    (goto-char (next-single-property-change (point) 'org-af-hidden))))
119
120
121(defun org-af--end-of-task (pos)
122  "Return the position of the end of the task at POS (excluding end of line)."
123  (next-single-property-change pos 'org-hd-marker))
124
125
126(defun org-af--next-task (from-pos)
127  "Return the position of the next visible task, from FROM-POS."
128  (setf from-pos (next-single-property-change from-pos 'org-hd-marker))
129  (while (eq (get-text-property from-pos 'org-todo-blocked) 'invisible)
130    ;; The EOL doesn't have an ORG-HD-MARKER property, so search twice to skip
131    ;; over it.
132    (setf from-pos (next-single-property-change from-pos 'org-hd-marker)
133          from-pos (next-single-property-change from-pos 'org-hd-marker)))
134  from-pos)
135
136
137(defun org-af--first-task ()
138  "Return the position of the first visible task."
139  (org-af--next-task 1))
140
141
142(defun org-af--prev-mark (pos)
143  "Return the position of the first mark previous to POS, or NIL if none are previous."
144  (let* ((prev (previous-single-char-property-change pos 'org-af-marked)))
145    (if (= prev 1)
146        nil
147      (1- prev))))
148
149
150(defun org-af-mark ()
151  "Mark or unmark the current task, dim the previous marked task if any, and hide any tasks between."
152  (interactive)
153  (org-af--beg-of-task)
154
155  (let ((inhibit-read-only t)
156        (task-point (point))
157        marked)
158
159    (unless (org-af--dimmed-p task-point)
160      (setq marked (not (org-af--marked-p task-point)))
161      (if marked
162          (org-af--mark task-point)
163        (org-af--unmark task-point))
164
165      (save-excursion
166        (let ((prev-marked (org-af--prev-mark task-point))
167              hide-from)
168
169          (cond
170           (prev-marked
171            (let ((eot (org-af--end-of-task prev-marked)))
172              (if marked
173                  (org-af--dim prev-marked eot)
174                (org-af--undim prev-marked eot))
175              (setf hide-from (org-af--next-task eot))))
176
177           (t
178            (setf hide-from (org-af--first-task))))
179
180          (if marked
181              (add-text-properties hide-from task-point
182                                   '(category org-af-hidden))
183            (remove-text-properties hide-from task-point
184                                    '(category org-af-hidden))))))))
185
186
187(defun org-af--timestamp ()
188  "Return the current local time, with nanoseconds, formatted as an ISO string."
189  (format-time-string "%Y-%m-%d %H:%M:%S.%N" (current-time)))
190
191
192(defun org-af-defer ()
193  "Defer the current task and update the display, unless NO-REDISPLAY."
194  (interactive)
195  (org-af--beg-of-task)
196  (if (org-af--dimmed-p (point))
197      (message "dimmed; skipping")
198    (when (org-af--marked-p (point))
199      (org-af-mark))
200
201    ;; This comes from the guts of the org-agenda-set-property command.
202    (let* ((hdmarker (or (org-get-at-bol 'org-hd-marker)
203                         (org-agenda-error)))
204           (buffer (marker-buffer hdmarker))
205           (pos (marker-position hdmarker))
206           (inhibit-read-only t)
207           (time (org-af--timestamp)))
208      (org-with-remote-undo buffer
209        (with-current-buffer buffer
210          (widen)
211          (goto-char pos)
212          (org-show-context 'agenda)
213          (org-set-property "TOUCHED" time))))
214
215    (save-excursion
216      (let* ((inhibit-read-only t)
217             (text (delete-and-extract-region (point)
218                                              (1+ (line-end-position)))))
219        (goto-char (point-max))
220        (insert text)))))
221
222
223(defun org-af-complete ()
224  "Complete to current task.
225The task is unmarked (if marked), completed by Org, touched, then
226sorted to the end of the list."
227  (interactive)
228  (org-af--beg-of-task)
229  (when (org-af--marked-p (point))
230    (org-af-mark))
231  (org-agenda-todo)
232  (org-af-defer))
233
234
235(defvar org-autofocus-mode-map (make-sparse-keymap))
236(define-key org-autofocus-mode-map "." 'org-af-mark)
237(define-key org-autofocus-mode-map "d" 'org-af-defer)
238(define-key org-autofocus-mode-map "c" 'org-af-complete)
239(define-key org-autofocus-mode-map "t" 'org-af-complete)
240
241
242(defun org-af--touched (entry)
243  "Touch ENTRY by setting its ``TOUCHED'' property to the current time."
244  (let* ((marker (get-text-property 0 'org-hd-marker entry))
245         (touched (org-entry-get marker "TOUCHED")))
246    (unless touched
247      (setq touched (org-af--timestamp))
248      (org-entry-put marker "TOUCHED" touched))
249    touched))
250
251
252(defun org-af--cmp (a b)
253  "Compare A to B.
254If A > B, return +1; if A < B, return -1; else, return NIL."
255  (let* ((a-touched (org-af--touched a))
256         (b-touched (org-af--touched b))
257         (c (compare-strings a-touched nil nil b-touched nil nil)))
258    (cond
259     ((eq c t)  nil)
260     ((< c 0)   -1)
261     ((> c 0)   +1)
262     (t (error "%S and %S compared as %S" a-touched b-touched c)))))
263
264
265(define-minor-mode org-autofocus-mode nil nil " AF" org-autofocus-mode-map)
266
267
268;;;###autoload
269(defun org-autofocus ()
270  "Display the Org Agenda Todo list in AutoFocus mode."
271  (interactive)
272  (let ((org-agenda-cmp-user-defined 'org-af--cmp)
273        (org-agenda-sorting-strategy '(user-defined-up)))
274    (org-agenda nil "t")
275    (org-autofocus-mode)))
276
277
278(provide 'org-autofocus)
279;;; org-autofocus.el ends here
Note: See TracBrowser for help on using the repository browser.