Org ad hoc code, quick hacks and workarounds

{Back to Worg's index}

This page is for ad hoc bits of code. Feel free to add quick hacks and workaround. Go crazy.

Table of Contents

Automatically add an appointment when clocking in a task

;; Make sure you have a sensible value for `appt-message-warning-time'
(defvar bzg-org-clock-in-appt-delay 100
  "Number of minutes for setting an appointment by clocking-in")

This function let's you add an appointment for the current entry. This can be useful when you need a reminder.

(defun bzg-org-clock-in-add-appt (&optional n)
  "Add an appointment for the Org entry at point in N minutes."
  (interactive)
  (save-excursion
    (org-back-to-heading t)
    (looking-at org-complex-heading-regexp)
    (let* ((msg (match-string-no-properties 4))
           (ct-time (decode-time))
           (appt-min (+ (cadr ct-time)
                        (or n bzg-org-clock-in-appt-delay)))
           (appt-time ; define the time for the appointment
            (progn (setf (cadr ct-time) appt-min) ct-time)))
      (appt-add (format-time-string
                 "%H:%M" (apply 'encode-time appt-time)) msg)
      (if (interactive-p) (message "New appointment for %s" msg)))))

You can advise org-clock-in so that C-c C-x C-i will automatically add an appointment:

(defadvice org-clock-in (after org-clock-in-add-appt activate)
  "Add an appointment when clocking a task in."
  (bzg-org-clock-in-add-appt))

You may also want to delete the associated appointment when clocking out. This function does this:

(defun bzg-org-clock-out-delete-appt nil
  "When clocking out, delete any associated appointment."
  (interactive)
  (save-excursion
    (org-back-to-heading t)
    (looking-at org-complex-heading-regexp)
    (let* ((msg (match-string-no-properties 4)))
      (setq appt-time-msg-list
            (delete nil
                    (mapcar
                     (lambda (appt)
                       (if (not (string-match (regexp-quote msg)
                                              (cadr appt))) appt))
                     appt-time-msg-list)))
      (appt-check))))

And here is the advice for org-clock-out (C-c C-x C-o)

(defadvice org-clock-out (before org-clock-out-delete-appt activate)
  "Delete an appointment when clocking a task out."
  (bzg-org-clock-out-delete-appt))

IMPORTANT: You can add appointment by clocking in in both an org-mode and an org-agenda-mode buffer. But clocking out from agenda buffer with the advice above will bring an error.

Use Org-mode with Screen [Andrew Hyatt]

"The general idea is that you start a task in which all the work will take place in a shell. This usually is not a leaf-task for me, but usually the parent of a leaf task. From a task in your org-file, M-x ash-org-screen will prompt for the name of a session. Give it a name, and it will insert a link. Open the link at any time to go the screen session containing your work!"

http://article.gmane.org/gmane.emacs.orgmode/5276

(require 'term)

(defun ash-org-goto-screen (name)                                              
  "Open the screen with the specified name in the window"                      
  (interactive "MScreen name: ")                                              
  (let ((screen-buffer-name (ash-org-screen-buffer-name name)))                
    (if (member screen-buffer-name                                            
                (mapcar 'buffer-name (buffer-list)))                          
        (switch-to-buffer screen-buffer-name)                                  
      (switch-to-buffer (ash-org-screen-helper name "-dr")))))  

(defun ash-org-screen-buffer-name (name)
  "Returns the buffer name corresponding to the screen name given."
  (concat "*screen " name "*"))

(defun ash-org-screen-helper (name arg)
  ;; Pick the name of the new buffer.
  (let ((term-ansi-buffer-name
         (generate-new-buffer-name 
          (ash-org-screen-buffer-name name))))
    (setq term-ansi-buffer-name
          (term-ansi-make-term 
           term-ansi-buffer-name "/usr/bin/screen" nil arg name))
    (set-buffer term-ansi-buffer-name)
    (term-mode)
    (term-char-mode)
    (term-set-escape-char ?\C-x)
    term-ansi-buffer-name))

(defun ash-org-screen (name)
  "Start a screen session with name"
  (interactive "MScreen name: ")
  (save-excursion
    (ash-org-screen-helper name "-S"))
  (insert-string (concat "[[screen:" name "]]")))

;; And don't forget to add ("screen" . "elisp:(ash-org-goto-screen
;; \"%s\")") to org-link-abbrev-alist.

Org Agenda + Appt + Zenity

Russell Adams posted this setup on the list. It make sure your agenda appointments are known by Emacs, and it displays warnings in a zenity popup window.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; For org appointment reminders

;; Get appointments for today
(defun my-org-agenda-to-appt ()
  (interactive)
  (setq appt-time-msg-list nil)
  (let ((org-deadline-warning-days 0))    ;; will be automatic in org 5.23
        (org-agenda-to-appt)))

;; Run once, activate and schedule refresh
(my-org-agenda-to-appt)
(appt-activate t)
(run-at-time "24:01" nil 'my-org-agenda-to-appt)

; 5 minute warnings
(setq appt-message-warning-time 15)
(setq appt-display-interval 5)

; Update appt each time agenda opened.
(add-hook 'org-finalize-agenda-hook 'my-org-agenda-to-appt)

; Setup zenify, we tell appt to use window, and replace default function
(setq appt-display-format 'window)
(setq appt-disp-window-function (function my-appt-disp-window))

(defun my-appt-disp-window (min-to-app new-time msg)                      
  (save-window-excursion (shell-command (concat 
    "/usr/bin/zenity --info --title='Appointment' --text='" 
    msg "' &") nil nil)))

Org-Mode + gnome-osd

Richard Riley uses gnome-osd in interaction with Org-Mode to display appointments. You can look at the code on the emacswiki.

remind2org

From Detlef Steuer

http://article.gmane.org/gmane.emacs.orgmode/5073

remind (http://www.roaringpenguin.com/products/remind) is a very powerful
command line calendaring program. Its features superseed the possibilities
of orgmode in the area of date specifying, so that I want to use it
combined with orgmode.

Using the script below I'm able use remind and incorporate its output in my
agenda views.  The default of using 13 months look ahead is easily
changed. It just happens I sometimes like to look a year into the
future. :-)

org-remember-anything

Anything users may find the snippet below interesting:

(defvar org-remember-anything
  '((name . "Org Remember")
    (candidates . (lambda () (mapcar 'car org-remember-templates)))
    (action . (lambda (name)
                (let* ((orig-template org-remember-templates)
                       (org-remember-templates
                        (list (assoc name orig-template))))
                  (call-interactively 'org-remember))))))

You can add it to your 'anything-sources' variable and open remember directly from anything. I imagine this would be more interesting for people with many remember templatesm, so that you are out of keys to assign those to. You should get something like this:

Reload Org

This function by Bernt Hansen reloads Org's compiled files. This is useful when you update and compile Org often.

By default it reloads compiled org files. If you call it with a prefix argument it reloads source files.

;; Your org-mode directory

(setq my-org-mode-git-directory "~/git/org-mode")

(defun org-reload-org (&optional source)
  "Reload Compiled Org lisp files."
  (interactive "p")
  (message "source is %s" source)
  (if (equal source 4)
      (setq my-org-files "\\.el\\'")
    (setq my-org-files "\\.elc\\'"))
  (mapc (lambda(f) (load (car f)))
        (directory-files-and-attributes (concat my-org-mode-git-directory "/lisp") t my-org-files)))

(defun org-reload-org nil
  "Reload Org lisp files."
  (interactive)
  (mapc (lambda(f) (load (car f)))
        (directory-files-and-attributes 
          (concat my-org-mode-git-directory "/lisp") t "\\.elc\\'")))

Normally you want to use the compiled files since they are faster. If you run into a bug and want to generate a useful backtrace for the maintainer you can reload the source files and enter debugger on error with

C-u M-x org-reload-org

and turn on the "Enter Debugger On Error" option. Redo the action that generates the error and cut and paste the resulting backtrace into an email. To switch back to the compiled version just reload again with

M-x org-reload-org

Split horizontally for agenda

If you would like to split the frame into two side-by-side windows when displaying the agenda, try this hack from Jan Rehders, which uses the `toggle-window-split' from

http://www.emacswiki.org/cgi-bin/wiki/ToggleWindowSplit

;; Patch org-mode to use vertical splitting
(defadvice org-prepare-agenda (after org-fix-split)
  (toggle-window-split))
(ad-activate 'org-prepare-agenda)

Highlight the agenda line under cursor

This is useful to make sure what task you are operating on.

(add-hook 'org-agenda-mode-hook '(lambda () (hl-line-mode 1)))

Under XEmacs:

;; hl-line seems to be only for emacs
(require 'highline)
(add-hook 'org-agenda-mode-hook '(lambda () (highline-mode 1)))

;; highline-mode does not work straightaway in tty mode.
;; I use a black background
(custom-set-faces
  '(highline-face ((((type tty) (class color)) 
                    (:background "white" :foreground "black")))))

Remove time grid lines that are in an appointment

The agenda shows lines for the time grid. Some people think that these lines are a distraction when there are appointments at those times. You can get rid of the lines which coincide exactly with the beginning of an appointment. Michael Ekstrand has written a piece of advice that also removes lines that are somewhere inside an appointment:

(defun org-time-to-minutes (time)
  "Convert an HHMM time to minutes"
  (+ (* (/ time 100) 60) (% time 100)))

(defun org-time-from-minutes (minutes)
  "Convert a number of minutes to an HHMM time"
  (+ (* (/ minutes 60) 100) (% minutes 60)))

(defadvice org-agenda-add-time-grid-maybe (around mde-org-agenda-grid-tweakify
                                                  (list ndays todayp))
  (if (member 'remove-match (car org-agenda-time-grid))
      (flet ((extract-window
              (line)
              (let ((start (get-text-property 1 'time-of-day line))
                    (dur (get-text-property 1 'duration line)))
                (cond
                 ((and start dur)
                  (cons start
                        (org-time-from-minutes
                         (+ dur (org-time-to-minutes start)))))
                 (start start)
                 (t nil)))))
        (let* ((windows (delq nil (mapcar 'extract-window list)))
               (org-agenda-time-grid
                (list (car org-agenda-time-grid)
                      (cadr org-agenda-time-grid)
                      (remove-if
                       (lambda (time)
                         (find-if (lambda (w)
                                    (if (numberp w)
                                        (equal w time)
                                      (and (>= time (car w))
                                           (< time (cdr w)))))
                                  windows))
                       (caddr org-agenda-time-grid)))))
          ad-do-it))
    ad-do-it))
(ad-activate 'org-agenda-add-time-grid-maybe)

Group task list by a property

This advice allows you to group a task list in Org-Mode. To use it, set the variable org-agenda-group-by-property to the name of a property in the option list for a TODO or TAGS search. The resulting agenda view will group tasks by that property prior to searching.

(defvar org-agenda-group-by-property nil
  "Set this in org-mode agenda views to group tasks by property")

(defun org-group-bucket-items (prop items)
  (let ((buckets ()))
    (dolist (item items)
      (let* ((marker (get-text-property 0 'org-marker item))
             (pvalue (org-entry-get marker prop t))
             (cell (assoc pvalue buckets)))
        (if cell
            (setcdr cell (cons item (cdr cell)))
          (setq buckets (cons (cons pvalue (list item))
                              buckets)))))
    (setq buckets (mapcar (lambda (bucket)
                            (cons (car bucket)
                                  (reverse (cdr bucket))))
                          buckets))
    (sort buckets (lambda (i1 i2)
                    (string< (car i1) (car i2))))))

(defadvice org-finalize-agenda-entries (around org-group-agenda-finalize
                                               (list &optional nosort))
  "Prepare bucketed agenda entry lists"
  (if org-agenda-group-by-property
      ;; bucketed, handle appropriately
      (let ((text ""))
        (dolist (bucket (org-group-bucket-items
                         org-agenda-group-by-property
                         list))
          (let ((header (concat "Property "
                                org-agenda-group-by-property
                                " is "
                                (or (car bucket) "<nil>") ":\n")))
            (add-text-properties 0 (1- (length header))
                                 (list 'face 'org-agenda-structure)
                                 header)
            (setq text
                  (concat text header
                          ;; recursively process
                          (let ((org-agenda-group-by-property nil))
                            (org-finalize-agenda-entries
                             (cdr bucket) nosort))
                          "\n\n"))))
        (setq ad-return-value text))
    ad-do-it))
(ad-activate 'org-finalize-agenda-entries)

Link to Gnus messages by Message-Id

In a recent thread on the Org-Mode mailing list, there was some discussion about linking to Gnus messages without encoding the folder name in the link. The following code hooks in to the store-link function in Gnus to capture links by Message-Id when in nnml folders, and then provides a link type "mid" which can open this link. The mde-org-gnus-open-message-link function uses the mde-mid-resolve-methods variable to determine what Gnus backends to scan. It will go through them, in order, asking each to locate the message and opening it from the first one that reports success.

It has only been tested with a single nnml backend, so there may be bugs lurking here and there.

The logic for finding the message was adapted from an Emacs Wiki article.

;; Support for saving Gnus messages by Message-ID
(defun mde-org-gnus-save-by-mid ()
  (when (memq major-mode '(gnus-summary-mode gnus-article-mode))
    (when (eq major-mode 'gnus-article-mode)
      (gnus-article-show-summary))
    (let* ((group gnus-newsgroup-name)
           (method (gnus-find-method-for-group group)))
      (when (eq 'nnml (car method))
        (let* ((article (gnus-summary-article-number))
               (header (gnus-summary-article-header article))
               (from (mail-header-from header))
               (message-id
                (save-match-data
                  (let ((mid (mail-header-id header)))
                    (if (string-match "<\\(.*\\)>" mid)
                        (match-string 1 mid)
                      (error "Malformed message ID header %s" mid)))))
               (date (mail-header-date header))
               (subject (gnus-summary-subject-string)))
          (org-store-link-props :type "mid" :from from :subject subject
                                :message-id message-id :group group
                                :link (org-make-link "mid:" message-id))
          (apply 'org-store-link-props
                 :description (org-email-link-description)
                 org-store-link-plist)
          t)))))

(defvar mde-mid-resolve-methods '()
  "List of methods to try when resolving message ID's.  For Gnus,
it is a cons of 'gnus and the select (type and name).")
(setq mde-mid-resolve-methods
      '((gnus nnml "")))

(defvar mde-org-gnus-open-level 1
  "Level at which Gnus is started when opening a link")
(defun mde-org-gnus-open-message-link (msgid)
  "Open a message link with Gnus"
  (require 'gnus)
  (require 'org-table)
  (catch 'method-found
    (message "[MID linker] Resolving %s" msgid)
    (dolist (method mde-mid-resolve-methods)
      (cond
       ((and (eq (car method) 'gnus)
             (eq (cadr method) 'nnml))
        (funcall (cdr (assq 'gnus org-link-frame-setup))
                 mde-org-gnus-open-level)
        (when gnus-other-frame-object
          (select-frame gnus-other-frame-object))
        (let* ((msg-info (nnml-find-group-number
                          (concat "<" msgid ">")
                          (cdr method)))
               (group (and msg-info (car msg-info)))
               (message (and msg-info (cdr msg-info)))
               (qname (and group
                           (if (gnus-methods-equal-p
                                (cdr method)
                                gnus-select-method)
                               group
                             (gnus-group-full-name group (cdr method))))))
          (when msg-info
            (gnus-summary-read-group qname nil t)
            (gnus-summary-goto-article message nil t))
          (throw 'method-found t)))
       (t (error "Unknown link type"))))))

(eval-after-load 'org-gnus
  '(progn
     (add-to-list 'org-store-link-functions 'mde-org-gnus-save-by-mid)
     (org-add-link-type "mid" 'mde-org-gnus-open-message-link)))