(require 'org) (add-to-list 'auto-mode-alist '("\\.org" . org-mode)) ;(setq org-agenda-files '("~/notebook/personal/organizer.org" "~/notebook/writing/WickedCoolEmacs.org" "~/notebook/personal/books.org" "~/notebook/personal/101things.org")) (setq org-fast-tag-selection-single-key 'expert) (setq org-icalendar-include-todo t) (setq org-combined-agenda-icalendar-file "~/notebook/org/org.ics") (setq org-agenda-time-grid '((daily require-timed) "--------------------" (800 1000 1200 1400 1600 1800 2000 2200))) (setq org-agenda-skip-deadline-if-done t) (setq org-agenda-skip-scheduled-if-done t) (setq org-reverse-note-order t) ;; display projects and their next action (setq org-agenda-custom-commands '(("d" todo "DELEGATED" nil) ("p" tags "PROJECT-MAYBE-DONE" nil) ("P" todo "PROJECT-MAYBE-DONE" nil) ("m" tags "PROJECT&MAYBE" nil) ("c" todo "DONE|DEFERRED|CANCELLED" nil) ("w" todo "WAITING" nil) ("n" "Next actions for projects" ((sacha/org-project-get-summary))) ("a" "My agenda" ((org-agenda-list) (sacha/org-agenda-load))) ("W" agenda "" ((org-agenda-ndays 21))) ("A" agenda "" ((org-agenda-skip-function (lambda nil (org-agenda-skip-entry-if (quote notregexp) "\\=.*\\[#A\\]"))) (org-agenda-ndays 1) (org-agenda-overriding-header "Today's Priority #A tasks: "))) ("u" alltodo "" ((org-agenda-skip-function (lambda nil (org-agenda-skip-entry-if (quote scheduled) (quote deadline) (quote regexp) "<[^>\n]+>"))) (org-agenda-overriding-header "Unscheduled TODO entries: "))))) (setq org-cycle-include-plain-lists nil) (setq org-remember-templates (quote ((?t "* TODO %?\n%i\n %u" "~/notebook/personal/organizer.org" "Tasks") (?n "* %u %?" "~/notebook/personal/organizer.org" "Notes") (?b "** %^{Title} %^{Tags}\nSCHEDULED: %^t\n Author(s): %^{Author}\nISBN: %^{ISBN}\n\n%?\n\n%u" "~/notebook/personal/books.org" "Books")))) (defun sacha/remember-to-org () (interactive) (let ((org-directory "~/notebook/personal/") (org-default-notes-file "~/notebook/personal/organizer.org") (remember-annotation-functions '(org-remember-annotation)) (remember-mode-hook (cons 'org-remember-apply-template remember-mode-hook))) (remember) (set (make-variable-buffer-local 'remember-handler-functions) '(org-remember-handler)))) (setq org-publish-project-alist (list '("org" . (:base-directory "~/notebook/personal/" :base-extension "org" :publishing-directory "~/public_html/notebook/org" :with-section-numbers nil :table-of-contents nil :style "")))) (setq org-agenda-prefix-format '((agenda . " %-12:c%?-12t% s") ; (agenda . " %s %-12t ") (timeline . " ") (todo . " ") (tags . " "))) ;(remove-hook 'org-mode-hook (lambda () (longlines-mode-on))) (require 'twit) (defvar twit-active-p t) (defadvice org-store-log-note (before sacha activate) (when twit-active-p (let ((message (buffer-string))) (while (string-match "\\`#.*\n[ \t\n]*" message) (setq message (replace-match "" t t message))) (save-excursion (with-current-buffer (marker-buffer org-log-note-marker) (save-excursion (goto-char org-log-note-marker) (twit-post-function twit-update-url (concat (org-get-heading t) ": " message)))))))) (defun sacha/org-report-started () (when (and twit-active-p (string= state "STARTED") (not (string= last-state state))) (save-excursion (twit-post-function twit-update-url (org-get-heading t))))) (add-hook 'org-after-todo-state-change-hook 'sacha/org-report-started) (defun sacha/org-show-load () "Show my unscheduled time and free time for the day." (interactive) (let ((time (sacha/org-calculate-free-time ;; today (calendar-gregorian-from-absolute (time-to-days (current-time))) ;; now (let* ((now (decode-time)) (cur-hour (nth 2 now)) (cur-min (nth 1 now))) (+ (* cur-hour 60) cur-min)) ;; until the last time in my time grid (let ((last (car (last (elt org-agenda-time-grid 2))))) (+ (* (/ last 100) 60) (% last 100)))))) (message "%.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-agenda-load (&rest ignore) "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)))) (defun sacha/org-clock-in-if-starting () "Clock in when the task is marked STARTED." (when (and (string= state "STARTED") (not (string= last-state state))) (org-clock-in))) (add-hook 'org-after-todo-state-change-hook 'sacha/org-clock-in-if-starting) (defadvice org-clock-in (after sacha activate) "Set this task's status to 'STARTED'." (org-todo "STARTED")) (defun sacha/org-clock-out-if-waiting () "Clock in when the task is marked STARTED." (when (and (string= state "WAITING") (not (string= last-state state))) (org-clock-out))) (add-hook 'org-after-todo-state-change-hook 'sacha/org-clock-out-if-waiting) (provide 'org-config) (defun sacha/org-project-get-summary (&rest ignore) (interactive) (let ((matcher (cdr (org-make-tags-matcher "PROJECT-DONE-CANCELLED-WAITING"))) (re (concat "[\n\r]" outline-regexp " *\\(\\<\\(" (mapconcat 'regexp-quote org-todo-keywords-1 "\\|") (org-re "\\>\\)\\)? *\\(.*?\\)\\(:[[:alnum:]_@:]+:\\)?[ \t]*$"))) (case-fold-search nil) tags tags-list tags-alist (llast 0) rtn level category i txt todo marker entry priority project-name task date result rtn rtnall files file pos buffer) (setq files (org-agenda-files)) (while (setq file (pop files)) (catch 'nextfile (org-check-agenda-file file) (setq buffer (if (file-exists-p file) (org-get-agenda-file-buffer file) (error "No such file %s" file))) (if (not buffer) (setq rtn (list (format "ORG-AGENDA-ERROR: No such org-file %s" file)) rtnall (append rtnall rtn)) (with-current-buffer buffer (unless (org-mode-p) (error "Agenda file %s is not in `org-mode'" file)) (save-excursion (save-restriction (if org-agenda-restrict (narrow-to-region org-agenda-restrict-begin org-agenda-restrict-end) (widen)) (show-all) ;; Scan for tags (goto-char (point-min)) (setq rtn nil) (while (re-search-forward re nil t) (catch :skip (setq todo (if (match-end 1) (match-string 2)) tags (if (match-end 4) (match-string 4))) (goto-char (setq lspos (1+ (match-beginning 0)))) (setq level (org-reduced-level (funcall outline-level)) category (org-get-category)) (setq i llast llast level) ;; remove tag lists from same and sublevels (while (>= i level) (when (setq entry (assoc i tags-alist)) (setq tags-alist (delete entry tags-alist))) (setq i (1- i))) ;; add the nex tags (when tags (setq tags (mapcar 'downcase (org-split-string tags ":")) tags-alist (cons (cons level tags) tags-alist))) ;; compile tags for current headline (setq tags-list (if org-use-tag-inheritance (apply 'append (mapcar 'cdr tags-alist)) tags)) (when (and (eval matcher) (or (not org-agenda-skip-archived-trees) (not (member org-archive-tag tags-list)))) ;; Okay, I am at the right project tree. ;; I now need to find the next action in this tree. (setq project (org-get-heading) task nil date nil) (if (string-match (org-re "[ \t]+:[[:alnum:]_@:]+:[ \t]*$") project) (setq project (replace-match "" t t project))) (save-excursion (save-restriction (org-narrow-to-subtree) ;; Find all the actions (while (re-search-forward (concat "^" outline-regexp " *" org-not-done-regexp) nil t) (let ((properties (org-entry-properties (point)))) (when (or (null task) (and (assoc "SCHEDULED" properties) (or (null date) (string< (cdr (assoc "SCHEDULED" properties)) date)))) (setq task (org-get-heading) date (cdr (assoc "SCHEDULED" properties))) (when (string-match (concat "^\\(" (mapconcat 'regexp-quote org-todo-keywords-1 "\\|") "\\) *") task) (setq task (replace-match "" t t task)))))))) (goto-char (org-end-of-subtree)) (setq rtn (cons (format "| %s | %s | %s |" project (or task "") (or date "")) rtn))))) (setq rtnall (append rtnall (nreverse rtn))))))))) (if org-agenda-overriding-header (insert (org-add-props (copy-sequence org-agenda-overriding-header) nil 'face 'org-agenda-structure) "\n") (insert "* Project next actions:\n") (add-text-properties (point-min) (1- (point)) (list 'face 'org-agenda-structure))) (when rtnall (insert (org-finalize-agenda-entries rtnall) "\n")) (goto-char (point-min)) (org-fit-agenda-window) (add-text-properties (point-min) (point-max) '(org-agenda-type tags)) (org-finalize-agenda) (setq buffer-read-only t))) ;;; (defun sacha/org-get-projects-and-next-actions () ;;; "Return a list of projects, next actions, and scheduled dates." ;;; ; Search for projects ;;; (goto-char (point-min)) ;;; (let (results ;;; proj-name ;;; next-action ;;; scheduled-date) ;;; (while (re-search-forward "^\\*+ \\(.+\\)[ ]*:PROJECT:" nil t) ;;; (save-match-data ;;; (setq proj-name (match-string 1)) ;;; (save-restriction ;;; (org-narrow-to-subtree) (defvar sacha/org-publish-agenda-directory "~/notebook/org/" "*Directory to save the published agenda to.") (org-defkey org-agenda-mode-map "p" 'sacha/org-publish-agenda) (defun sacha/org-publish-agenda () "Copy the agenda buffer to a file in `sacha/org-publish-agenda-directory'." (interactive) ;; Take the entire contents of the agenda and dump it into a text file labeled with the date. (let ((agenda (with-current-buffer org-agenda-buffer-name (unless org-agenda-show-log (org-agenda-log-mode)) (buffer-string))) (filename (format-time-string "%Y-%m-%d.txt" (if org-starting-day (calendar-time-from-absolute (1+ org-starting-day) 0) (current-time))))) (with-temp-buffer (insert agenda) (write-file (expand-file-name filename sacha/org-publish-agenda-directory))))) (defun sacha/org-publish-agenda-today (interactive) "Publish today's agenda. Suitable for ~/.emacs, we hope." (let ((entry (assoc "a" org-agenda-custom-commands))) (if entry (org-run-agenda-series (nth 1 entry) (cddr entry)) (call-interactively 'org-agenda-list)) (sacha/org-publish-agenda))) (setq org-agenda-include-diary t)