December 26, 2007

Bulk view

Emacs: Choosing between Org and Planner

jaaronfarr asked me why I switched from Planner to Org. Both of them are popular personal information managers for Emacs, and both of them have practically all the features I need. They both do a good job at helping people
manage tasks, schedule, and notes. If you have a few
months to explore this, I suggest that you try both for at least a
month each. On the other hand, if you want quick results, some time
thinking about how you plan can save you more time later.

I tried out Org because I was working on a chapter about schedule
management and it wasn’t fair to just rely on the manual or the
mailing list. In the beginning, I
felt frustrated by the lack of things I’d gotten used to in Planner:
the freedom to edit anything on my day page, little conveniences like +2tue to mean two Tuesdays from now (which Carsten has just added), publishing my blog…

After two months of using Org almost every day, I’m starting to
understand it. I’ve come to appreciate the ease of working with an
outline. I love the way it clocks time. I find the daily and weekly
views really helpful. I’ve hacked stuff for it: time/load estimation,
time reporting, next action summaries, agenda publishing… I’m fairly deeply

As a geek, I have to confess—I like Planner more. Planner is more fun
to code. Maybe it’s because I’ve spent years with it, and I know my
way around the source.

Maybe it’s because we split Planner up into lots of little pieces that
can be reused and advised. Maybe the modularity of Planner is because
chunks of the code were written on a computer with a teensy screen,
which forced me to write functions that fit 80×48 characters. (See,
that limitation was there for a reason!) There are plenty of entry
points. From time to time, I still find myself copying an entire
function in order to change something in the middle, but usually I can
just get away with wrapping something around something else. With Org,
I find myself doing a lot of copy-and-paste programming. I’d fix this
by breaking the functions down into smaller bits, but I don’t have the
brainspace right now. Maybe after the book.

Org is better for my brain, though. It gives me a better overview of
both the ground-level tasks (what am I going to do right now, today,
this week) as well as the 50,000-foot view (what are my big projects)?
Planner’s good at the ground-level tasks, but the overview’s always
been a little awkward because it has to visit a number of files to get
a big picture. Org handles that easily.

And the one-place-for-data thing of Org is pretty cool, too. Org
dynamically generates reports, which could take a bit longer if you
have a large Org file. Planner copies data wherever it makes sense, so
you’d have a copy of the task on your day page and a copy of the task
on your plan page. Plan pages can get out of sync unless you’re either
religious about using planner-edit-task-description and other
functions to edit your tasks, or you use planner-id and you’re lucky.
Timeclock entries get out of sync, too. You can trust Org more than
Planner in terms of consistency.

So now I’m kinda in the middle of these two modules. I use Org for all
my work tasks, and I’m moving towards using it for all of my personal
tasks as well. But I still keep my blog entries in Planner, even
though they get mirrored into a WordPress blog on my web server.

*What would I recommend?*

It depends on the way you think. If you’re the kind of person who was
never happy with day planners because you needed more space to doodle,
write notes, move things around, add other things, try out Planner. If
you like outlines and organizing your tasks into projects, try out
Org.

Note that just because you work that way now doesn’t mean you’ll work
that way in the future. Don’t worry. Emacs will adapt. You can switch
between Planner and Org fairly easily. Just give yourself a week or so
to adjust (or a month if you’ve customized your old tool extensively
and miss lots of things about it). Use the tool every day, and you’ll
be fine.

Have you tried out both? Or have you tried out one of them and are curious about the other? I’d love to learn from your experience or answer your questions.

On Technorati: , , ,

Random Emacs symbol: malayalam-composition-function – Function: Compose Malayalam characters in REGION, or STRING if specified.

Bugfix: Time estimation

This is a slightly better version of sacha/org-calculate-free-time
that can understand prioritized tasks. Also, if you’re working after
midnight, it takes into account the need to sleep. That is, it doesn’t
treat _now_ as the starting point for the free-time calculation unless
it’s after the start time, so if you’re up at 12:30 writing Emacs Lisp
code, Emacs doesn’t assume you’ve got 450 extra minutes of work time.

This is what the result looks like:

Day-agenda:
Wednesday 26 December 2007
   8:00......  --------------------
  10:00......  --------------------
  12:00......  --------------------
 Scheduled:  12:00-21:00  TODO Christmas festivities with W-'s family
  14:00......  --------------------
  16:00......  --------------------
  18:00......  --------------------
  20:00......  --------------------
  22:00......  --------------------
 Scheduled:               TODO [#A] 120 Write Planner and Org comparison for tasks
 Scheduled:               TODO 30 Start on my letter for 2007
 Scheduled:               TODO 60 Respond to e-mail
 Scheduled:               TODO 30 Donate when my charity budget hits $1000
 Scheduled:               WAITING 30 Send money to Ateneo CS dept
 In 919 d.:               101 things in 1001 days
90.0% load: 270 minutes to be scheduled, 300 minutes free, 30 minutes ga

And here’s the code:

(defun sacha/org-agenda-load (match)
  "Can be included in `org-agenda-custom-commands'."
  (let ((inhibit-read-only t)
        (time (sacha/org-calculate-free-time
               ;; today
               (calendar-gregorian-from-absolute org-starting-day)
               ;; now if today AND after starting time, else start of day
               (if (= org-starting-day
                      (time-to-days (current-time)))
                   (max
                    (let* ((now (decode-time))
                           (cur-hour (nth 2 now))
                           (cur-min (nth 1 now)))
                      (+ (* cur-hour 60) cur-min))
                    (let ((start (car (elt org-agenda-time-grid 2))))
                      (+ (* (/ start 100) 60) (% start 100))))
                 (let ((start (car (elt org-agenda-time-grid 2))))
                   (+ (* (/ start 100) 60) (% start 100))))
                 ;; until the last time in my time grid
               (let ((last (car (last (elt org-agenda-time-grid 2)))))
                 (+ (* (/ last 100) 60) (% last 100))))))
    (goto-char (point-max))
    (insert (format
             "%.1f%% load: %d minutes to be scheduled, %d minutes free, %d minutes gap"
             (/ (car time) (* .01 (cdr time)))
             (car time)
             (cdr time)
             (- (cdr time) (car time))))))

(defun sacha/org-calculate-free-time (date start-time end-of-day)
  "Return a cons cell of the form (TASK-TIME . FREE-TIME) for DATE, given START-TIME and END-OF-DAY.
DATE is a list of the form (MONTH DAY YEAR).
START-TIME and END-OF-DAY are the number of minutes past midnight."
  (save-window-excursion
  (let ((files org-agenda-files)
        (total-unscheduled 0)
        (total-gap 0)
        file
        rtn
        rtnall
        entry
        (last-timestamp start-time)
        scheduled-entries)
    (while (setq file (car files))
      (catch 'nextfile
        (org-check-agenda-file file)
        (setq rtn (org-agenda-get-day-entries file date :scheduled :timestamp))
        (setq rtnall (append rtnall rtn)))
      (setq files (cdr files)))
    ;; For each item on the list
    (while (setq entry (car rtnall))
      (let ((time (get-text-property 1 'time entry)))
        (cond
         ((and time (string-match "\\([^-]+\\)-\\([^-]+\\)" time))
          (setq scheduled-entries (cons (cons
                                         (save-match-data (appt-convert-time (match-string 1 time)))
                                         (save-match-data (appt-convert-time (match-string 2 time))))
                                        scheduled-entries)))
         ((and time
               (string-match "\\([^-]+\\)\\.+" time)
               (string-match "^[A-Z]+ \\(\\[#[A-Z]\\] \\)?\\([0-9]+\\)" (get-text-property 1 'txt entry)))
          (setq scheduled-entries
                (let ((start (and (string-match "\\([^-]+\\)\\.+" time)
                                 (appt-convert-time (match-string 2 time)))))
                  (cons (cons start
                              (and (string-match "^[A-Z]+ \\(\\[#[A-Z]\\] \\)?\\([0-9]+\\)" (get-text-property 1 'txt entry))
                                   (+ start (string-to-number (match-string 2 (get-text-property 1 'txt entry))))))
                        scheduled-entries))))
         ((string-match "^[A-Z]+ \\(\\[#[A-Z]\\] \\)?\\([0-9]+\\)" (get-text-property 1 'txt entry))
          (setq total-unscheduled (+ (string-to-number
                                      (match-string 2 (get-text-property 1 'txt entry)))
                                     total-unscheduled)))))
      (setq rtnall (cdr rtnall)))
    ;; Sort the scheduled entries by time
    (setq scheduled-entries (sort scheduled-entries (lambda (a b) (< (car a) (car b)))))

    (while scheduled-entries
      (let ((start (car (car scheduled-entries)))
            (end (cdr (car scheduled-entries))))
      (cond
       ;; are we in the middle of this timeslot?
       ((and (>= last-timestamp start)
             (<= last-timestamp end))
        ;; move timestamp later, no change to time
        (setq last-timestamp end))
       ;; are we completely before this timeslot?
       ((< last-timestamp start)
        ;; add gap to total, skip to the end
        (setq total-gap (+ (- start last-timestamp) total-gap))
        (setq last-timestamp end)))
      (setq scheduled-entries (cdr scheduled-entries))))
    (if (< last-timestamp end-of-day)
        (setq total-gap (+ (- end-of-day last-timestamp) total-gap)))
    (cons total-unscheduled total-gap))))

On Technorati: ,

Random Emacs symbol: gnus-summary-display-while-building - Variable: If non-nil, show and update the summary buffer as it's being built.