;;; -*- Emacs-Lisp -*- ;;; planner-browser.el -- major mode for browsing planner file ;;; Author : Quasihiko Tsuruse ;;; http://www003.upp.so-net.ne.jp/quasi/ ;;; Created: 2001/10/13 ;;; $Id: planner-browser.el,v 1.117 2003/10/13 01:04:53 tsuruse Exp $ ;;; Note: ;;; This package works with planner.el by John Wiegley . ;;; planner.el can publish html files and its indexes. ;;; I just want to view many planner daily task list files ;;; at the same time with indexes. ;;; Usage: ;;; M-x planner-browser-directory to show planner-directory. ;;; n, p next/previous-line ;;; P Publish ;;; w Publish and browse file. ;;; t toggle index ;;; T sort ;;; v change project ;;; add below to your .emacs ;;; (load "emacs-wiki") ;;; (load "planner") ;;; (load "planner-browser") (require 'emacs-wiki) (require 'planner) (require 'calendar) (require 'diary-lib) (defvar planner-browser-index-buffer " *planner index*" "Temporally") (defvar planner-browser-index-window nil) (defvar planner-browser-browsing-window nil) (defvar planner-browser-calendar-window nil) (defvar planner-browser-calendar-flag t "If non-nil, show calendar." ) (defvar planner-browser-move-and-show nil "If non-nil, show current line file after `planner-browser-next-line'.") (defvar planner-browser-initial-project "WikiPlanner" "If non-nil, use it as initial project. WikiPlanner is the project for planner.el") (defvar planner-browser-save-window-configuration t "If non-nil, restore window-configuration") (defvar planner-browser-window-configuration nil) (defvar planner-browser-mode-map nil "Keymap for planner-browser.") (if planner-browser-mode-map () (setq planner-browser-mode-map (make-keymap)) (suppress-keymap planner-browser-mode-map) (define-key planner-browser-mode-map "?" 'describe-mode) (define-key planner-browser-mode-map "h" 'describe-mode) (define-key planner-browser-mode-map "n" 'planner-browser-next-line) (define-key planner-browser-mode-map "p" 'planner-browser-previous-line) (define-key planner-browser-mode-map "q" 'planner-browser-exit) (define-key planner-browser-mode-map "o" 'planner-browser-show-other-window) (define-key planner-browser-mode-map "w" 'planner-browser-w3m-browse) (define-key planner-browser-mode-map "." 'planner-browser-show-today) (define-key planner-browser-mode-map "\r" 'planner-browser-show-current-line) (define-key planner-browser-mode-map "\t" 'planner-browser-next-line) (define-key planner-browser-mode-map "P" 'planner-browser-publish-this-line) (define-key planner-browser-mode-map "t" 'planner-browser-rotate-indexes) (define-key planner-browser-mode-map "T" 'planner-browser-insert-indexes-2-rotate-sort) (define-key planner-browser-mode-map "s" 'planner-browser-project-info) (define-key planner-browser-mode-map "v" 'planner-browser-change-project-and-update) (define-key planner-browser-mode-map "c" 'planner-browser-calendar-toggle) (define-key planner-browser-mode-map "g" 'planner-browser-update-index) (define-key planner-browser-mode-map "D" 'planner-browser-show-debug) (define-key planner-browser-mode-map [mouse-2] 'planner-browser-mouse-show) ) (defun planner-browser-w3m-browse () "Browse published planner file by w3m. If AFILE is nil, use `planner-browser-current-line'." (interactive) (require 'w3m) (let ((sw (selected-window)) (file (emacs-wiki-published-file (planner-browser-current-line)))) (planner-browser-publish-this-line) (save-excursion (select-window (next-window)) (w3m-find-file file)) (select-window sw))) (defun planner-browser-publish-this-line () "Force publication of the current line planner." (interactive) (emacs-wiki-publish-files (list (planner-browser-absolute-file-name (planner-browser-index-elt (planner-browser-current-line)))) nil)) (defun planner-browser-exit () "Exit planner-browser-mode." (interactive) (if (and planner-browser-save-window-configuration planner-browser-window-configuration) (set-window-configuration planner-browser-window-configuration)) (kill-buffer planner-browser-index-buffer) (remove-hook 'calendar-move-hook 'planner-browser-calendar-goto) (message nil)) (defun planner-browser-init-window () "Split window to browse." (interactive) (if planner-browser-save-window-configuration (setq planner-browser-window-configuration (current-window-configuration))) (setq planner-browser-browsing-window (selected-window)) (delete-other-windows) (setq planner-browser-index-window (split-window planner-browser-browsing-window (round (* (nth 2 (window-edges)) 0.7)) t)) (set-window-buffer planner-browser-index-window planner-browser-index-buffer) (if planner-browser-calendar-flag (progn (planner-browser-calendar) (planner-browser-calendar-split) (bury-buffer calendar-buffer)))) (defun planner-browser-init () "Initialize planner-browser." (interactive) (let ((sw (selected-window))) (setq planner-browser-browsing-window sw) (setq planner-browser-index-window (split-window planner-browser-browsing-window (round (* (nth 2 (window-edges)) 0.7)) t)) (set-window-buffer planner-browser-index-window planner-browser-index-buffer) (if planner-browser-calendar-flag (progn (planner-browser-calendar) (planner-browser-calendar-split) (bury-buffer calendar-buffer))))) (defun planner-browser-calendar-split () "Split window to show calendar." (setq planner-browser-calendar-window (split-window planner-browser-browsing-window (round (* (nth 3 (window-edges)) 0.7)))) (set-window-buffer planner-browser-calendar-window calendar-buffer) (fit-window-to-buffer planner-browser-calendar-window)) (defun planner-browser-calendar-toggle () (interactive) "Show/Hide Calendar." (if (window-live-p planner-browser-calendar-window) (delete-window planner-browser-calendar-window) (planner-browser-calendar-split))) (defun planner-browser-calendar (&optional arg) "Copy of `calendar-basic-setup'. But use just `set-buffer' instead of `pop-to-buffer' to control buffer." (interactive "P") (set-buffer (get-buffer-create calendar-buffer)) (calendar-mode) (let* ((split-height-threshold 1000) (date (if arg (calendar-read-date t) (calendar-current-date))) (month (extract-calendar-month date)) (year (extract-calendar-year date))) (set-buffer calendar-buffer) (increment-calendar-month month year (- calendar-offset)) (generate-calendar-window month year) (if (and view-diary-entries-initially (calendar-date-is-visible-p date)) (view-diary-entries (if (vectorp number-of-diary-entries) (aref number-of-diary-entries (calendar-day-of-week date)) number-of-diary-entries)))) (let* ((diary-buffer (get-file-buffer diary-file)) (diary-window (if diary-buffer (get-buffer-window diary-buffer))) (split-height-threshold (if diary-window 2 1000))) (if view-calendar-holidays-initially (list-calendar-holidays))) (run-hooks 'initial-calendar-window-hook)) (defun planner-browser-mark-planning-entries () "Mark calendar if daily planning file exists." (interactive) (setq mark-diary-entries-in-calendar t) (mapcar '(lambda (f) (planner-browser-mark-calendar (planner-browser-file-name f))) (planner-browser-file-alist '(planner-browser-file-daily-p)))) (defun planner-browser-mark-calendar (file) "Mark calendar. file is YYYY.MM.DD." (if (= (length file) 10) (mark-calendar-date-pattern ;; MONTH DAY YEAR (string-to-number (substring file 5 7)) (string-to-number (substring file 8 10)) (string-to-number (substring file 0 4))))) (defun planner-browser-next-line (arg) "Go next-line with ARG." (interactive "p") (forward-line arg) (if planner-browser-move-and-show (planner-browser-show-current-line))) (defun planner-browser-previous-line (arg) "Go previous-line with ARG." (interactive "p") (planner-browser-next-line (- arg))) (defun planner-browser-show-current-line () "Show current line contents." (interactive) (planner-browser-show (planner-browser-current-line))) (defun planner-browser-project-directory (&optional project) "Return one directory related to project. use emacs-wiki-current-project when project is nil." (cadr (assq 'emacs-wiki-directories (cdr (assoc (or project emacs-wiki-current-project) emacs-wiki-projects))))) (defun planner-browser-show (file &optional SUCCESS FAIL) "Show file in 'emacs-wiki-directories in 'emacs-wiki-current-project at current line to other window. If file exists, call SUCCESS, or show SUCCESS(if stringp). If file not exists, call FAIL." (let ((sw (selected-window))) (cond ((planner-browser-file-exists-p file) (set-window-buffer planner-browser-browsing-window (find-file-noselect (planner-browser-absolute-file-name (planner-browser-index-elt file)))) (cond ((functionp SUCCESS) (funcall SUCCESS)) ((stringp SUCCESS) (message SUCCESS)))) (t (cond ((functionp FAIL) (funcall FAIL)) ((stringp FAIL) (message FAIL))))) (select-window sw))) (defun planner-browser-file-exists-p (file) "Return t if file FILENAME exists." (let ((full (planner-browser-absolute-file-name (planner-browser-index-elt file)))) (and full (file-exists-p full)))) (defun planner-browser-show-other-window () "Show wiki file at current line to other window and point the window." (interactive) (planner-browser-show (planner-browser-current-line) nil nil) (select-window planner-browser-browsing-window)) (defun planner-browser-mouse-show (event) "Show wiki file at current mouse position to other window." (interactive "e") (let ((afile)) (save-excursion (set-buffer (window-buffer (posn-window (event-end event)))) (save-excursion (goto-char (posn-point (event-end event))) (setq afile (planner-browser-current-line)))) (select-window (posn-window (event-end event))) (planner-browser-show afile))) (defun planner-browser-show-today () "Goto today planner. if not exists, create it." (interactive) (let* ((filename (planner-today)) (file-exists (planner-browser-file-exists-p filename)) (index-exists (planner-browser-index-elt filename)) (move-pos ; inner-func to move cursor (function (lambda () (goto-char (point-min)) (search-forward filename) (beginning-of-line))))) (cond ((and index-exists file-exists) (funcall move-pos) (planner-browser-show filename)) ((and (not index-exists) file-exists) (message "%s exists. Change rule to show it." filename)) (t (set-window-buffer planner-browser-browsing-window (find-file-noselect (expand-file-name filename (planner-browser-project-directory)))) (select-window planner-browser-browsing-window) (planner-seek-to-first))))) (defun planner-browser-update-index() "Update index." (interactive) (planner-browser-rotate-indexes t)) (defun planner-browser-show-monthly-index () "Show monthly index." (interactive) (let ((afile (concat (format-time-string "%Y") "." (format-time-string "%m"))) (contentbuf)) (save-excursion (setq contentbuf (find-file-noselect afile)) (pop-to-buffer contentbuf)))) (defun planner-browser-monthly-index-alist (&optional year month) "File alist of month." (interactive) (let ((filealist (emacs-wiki-file-alist t)) (result)) (while filealist (if (planner-browser-file-display-p '((lambda (x) (planner-browser-file-year-month-p x year month))) (planner-browser-file-name (car filealist))) (setq result (cons (car filealist) result))) (setq filealist (cdr filealist))) (setq result (sort result 'planner-browser-sort-alist)))) (defun planner-browser-file-name (alist) "Get filename from element of `emacs-wiki-file-alist'. alist is '(file-name absolute-file-name)" (car alist)) (defun planner-browser-absolute-file-name (alist) "Return absolute file name. alist is '(file-name . absolute-file-name)" (cdr alist)) (defun planner-browser-current-line () "Return planner file-name in browsing buffer." (save-excursion (beginning-of-line) (buffer-substring (point) (progn (skip-chars-forward "^ \t\n") (point))))) (defun planner-browser-project-info () "Show project-specific emacs-wiki variable settings." (interactive) (message "emacs-wiki-current-project: %s emacs-wiki-directories: %s" (or emacs-wiki-current-project "nil") (mapconcat 'concat emacs-wiki-directories ","))) (defun planner-browser-file-info (file) "Show file information." (let* ((file-name (planner-browser-absolute-file-name (assoc file (planner-browser-file-alist '(planner-browser-file-allways-p)))))) (if file-name (let* ((attr (file-attributes file-name)) (size-in-bytes (nth 7 attr)) (last-modification-time (format-time-string "%Y.%m.%d %H:%M:%S" (nth 5 attr)))) (message "%s: size: %d last-modified: %d" file-name size-in-bytes last-modification-time)) (message "File Not Exists: %s" file)))) (defun planner-browser-rotate-indexes (&optional keeprule) "Show wiki file which matches with current rule. if keeprule, just redraw without rotate rule." (interactive) (or keeprule (planner-browser-rotate-rule)) (let ((index (planner-browser-index))) (message "%s %s" (planner-browser-rule-message) (if index "" "[No match]")) (planner-browser-insert-indexes index))) (defun planner-browser-insert-indexes-2-rotate-sort () "(re)Show wiki file which matches with current rule." (interactive) (planner-browser-rotate-sort) (planner-browser-rotate-indexes t)) (defun planner-browser-insert-indexes (index) "Insert wiki file names. index is (part of) emacs-wiki-file-alist." (save-excursion (set-buffer planner-browser-index-buffer) (setq buffer-read-only nil) (erase-buffer) (insert (mapconcat '(lambda (x) (planner-browser-decorate-wiki-file (concat x))) index "\n")) (goto-char (point-min)) (setq buffer-read-only t)) (run-hooks 'planner-browser-insert-indexes-hook)) ;;divide data structure and presentation. (defun planner-browser-index () "Get file name list." (mapcar 'planner-browser-file-name (planner-browser-file-alist (planner-browser-rule)))) (defun planner-browser-index-elt (file) "Return (file . absolute-file) alist if current index contains file" (assoc file (planner-browser-file-alist (planner-browser-rule)))) (defvar planner-browser-insert-indexes-hook (if planner-browser-calendar-flag '(planner-browser-mark-planning-entries)) "*List of functions called whenever `planner-browser-insert-indexes' is called.") (defun planner-browser-decorate-wiki-file (afile) "Decorate special file name. TODO: Add more text-properties." (if (string= afile (planner-today)) (concat afile " " "*") afile)) (defun planner-browser-file-alist (&optional rules) "Return file alist like `emacs-wiki-file-alist' therefore ((filename . fullpath) ...). If rules non-nil, call it with a file in turn, and add it to result only when the rule returns t." (let ((file-alist) (result nil) (file-alist-iter (function (lambda (filename) (planner-browser-file-display-p (or rules (list 'planner-browser-file-allways-p)) filename))))) (setq file-alist (sort (copy-sequence (emacs-wiki-file-alist nil)) (planner-browser-get-sort))) (while file-alist (if (funcall file-alist-iter (planner-browser-file-name (car file-alist))) (setq result (cons (car file-alist) result))) (setq file-alist (cdr file-alist))) result)) (defun planner-browser-change-project (&optional project) "Manually change the project. see 'emacs-wiki-change-project." (interactive) (or project (setq project (completing-read "Switch to project: " emacs-wiki-projects nil t nil))) (if (null (assoc project emacs-wiki-projects)) (error "No such projects: %s. Please check 'emacs-wiki-projects and 'planner-browser-initial-project." projects)) (let ((projsyms (cdr (assoc project emacs-wiki-projects))) sym) (while projsyms (setq sym (caar projsyms)) (unless (memq sym '(emacs-wiki-predicate emacs-wiki-major-mode)) (let ((custom-set (or (get sym 'custom-set) 'set)) (var (if (eq (get sym 'custom-type) 'hook) (make-local-hook sym) (make-local-variable sym)))) (if custom-set (funcall custom-set var (cdar projsyms))))) (setq projsyms (cdr projsyms)))) (setq emacs-wiki-current-project project) ) (defun planner-browser-change-project-and-update (&optional project) "Change project and insert indexes." (interactive) (setq project (planner-browser-change-project project)) (let ((index (planner-browser-index))) (message "Welcome to %s: %s %s" project (planner-browser-rule-message) (if index "" "[No match]")) (planner-browser-insert-indexes index)) (setq mode-line-buffer-identification `(,emacs-wiki-current-project))) (defun planner-browser-file-display-p (rules afile) "Return afile when funcall rule returns t." (cond ((null rules) nil) ((funcall (car rules) afile)) (t (planner-browser-file-display-p (cdr rules) afile)))) (defun planner-browser-file-match (regexp file) (let ((case-fold-search nil)) (if (string-match regexp file) file))) (defun planner-browser-file-daily-p (file) "If file is daily planning file, return non-nil." (planner-browser-file-match "\\([0-9][0-9][0-9][0-9]\\)\\.\\([0-1][0-9]\\)\\.\\([0-3][0-9]\\)" file)) (defun planner-browser-file-thismonth-p (file) "If file is daily planning file in this month, return non-nil." (planner-browser-file-year-month-p file)) (defun planner-browser-file-month-summary-p (file) "If file is planning summary file, return non-nil." (planner-browser-file-match "\\([0-9][0-9][0-9][0-9]\\)\\.\\([0-1][0-9]\\)$" file)) (defun planner-browser-file-year-month-p (file &optional year month) "If file is daily planning file in year month, return non-nil. match only year.month.** file. don't match year.month file. " (planner-browser-file-match (concat (or year (format-time-string "%Y")) "\\." (if year (if month (concat month "\\.[0-3][0-9]") "[0-1][0-9]\\.[0-3][0-9]") (concat (format-time-string "%m") "\\.[0-9][0-9]"))) file)) (defun planner-browser-file-wiki-p (file) "Return non-nil when file is emacs-wiki-name." ;; emacs-wiki-name-regexp is modified when WikiPlanner loaded. (planner-browser-file-match "\\<[A-Z][a-z]+\\([A-Z][a-z]+\\)+\\(#[A-Za-z0-9_%]+\\)?" file)) (defun planner-browser-file-allways-p (file) file) (defun planner-browser-file-franklin-p (file) "FranlinPlanner(TM) like file name. See http://www.franklincovey.co.jp/" (planner-browser-file-match "\\(GoalPlanning\\)\\|\\(MyValues\\)\\|\\(MissionStatement\\)\\|\\(KeyInformation\\)" file)) ;; set rotation list. ;; don't eval length planner-browser-file-display-rule-ring, ;; cause infinite-loop. use safe-length. (defvar planner-browser-file-display-rule-ring nil "List of rules to display. ((MESSAGE . (RULE1 RULE2 ...)) ...) element (MESSAGE . (RULE1 RULE2 ...)) is a set of rules.") (defun planner-browser-make-ring (list) "make ring list. destructive. don't eval length of ring." (cond ((null list) nil) ((listp list) (setcdr (nthcdr (1- (length list)) list) list)))) (defun planner-browser-unbind-ring (ring) "make list from ring." (let ((length (planner-browser-ring-length ring))) (setcdr (nthcdr (1- length) ring) nil) ring)) (defun planner-browser-rotate-ring (ring) "rotate ring list." (cdr ring)) (defun planner-browser-ring-add (ring elt) "add elt to ring. destructive." (let ((ring-length (planner-browser-ring-length ring))) (cond ((null ring) (setq ring (planner-browser-make-ring (list elt)))) ((null ring-length) nil) (t (planner-browser-unbind-ring ring) (setq ring (cons elt ring)) (planner-browser-make-ring ring))))) (defun planner-browser-ring-length (ring) "length of ring(iterative process). return nil if not ring." (let ((length-iter ;; inner function '(lambda (part n) (cond ((null part) nil) ((and (eq part ring) (> n 0)) n) (t (funcall length-iter (cdr part) (1+ n))))))) (funcall length-iter ring 0))) ;; (setq my-year-month '(1 2 3 4 5 6 7 8 9 10 11 12)) => (1 2 3 4 5 6 7 8 9 10 11 12) ;; (planner-browser-make-ring my-year-month) => (1 2 3 4 5 6 7 8 9 10 11 12 ...) ;; (nth 0 my-year-month) => 1 ;; (nth 12 my-year-month) => 1 ;; (nth 120 my-year-month) => 1 ;; (safe-length my-year-month) (defun planner-browser-make-rule (desc func) (cond ((listp func) (cons desc func)) (t (cons desc (list func))))) (or planner-browser-file-display-rule-ring ;; default planner-browser-file-display-rule-ring. (progn (setq planner-browser-file-display-rule-ring (mapcar '(lambda (x) (planner-browser-make-rule (car x) (cdr x))) '( ("Franklin Planner files and daily files in this month." planner-browser-file-franklin-p planner-browser-file-thismonth-p planner-browser-file-month-summary-p) ("Wiki and daily files in this month." planner-browser-file-wiki-p planner-browser-file-thismonth-p) ("All files." planner-browser-file-allways-p) ("All Daily files." planner-browser-file-daily-p) ("Wiki files." planner-browser-file-wiki-p) ))) ;; get rotation list (planner-browser-make-ring planner-browser-file-display-rule-ring))) (defun planner-browser-rotate-rule () "Rotate planner-browser-file-display-rule-ring." (setq planner-browser-file-display-rule-ring (planner-browser-rotate-ring planner-browser-file-display-rule-ring))) (defun planner-browser-rule () "Get display file rule list." (cdr (car planner-browser-file-display-rule-ring))) (defun planner-browser-rule-message () "Get display file rule message." (car (car planner-browser-file-display-rule-ring))) (defun planner-browser-get-sort () "Get sort func. " (car planner-browser-sort-func)) (defun planner-browser-rotate-sort () "Rotate sort func. " (setq planner-browser-sort-func (cdr planner-browser-sort-func))) (setq planner-browser-sort-func '(planner-browser-sort-alist planner-browser-sort-alist-reverse planner-browser-sort-access-time planner-browser-sort-access-time-reverse )) (planner-browser-make-ring planner-browser-sort-func) (defun planner-browser-sort-alist (l r) "Function for sorting." (string-lessp (car l) (car r))) (defun planner-browser-sort-alist-reverse (l r) (planner-browser-sort-alist r l)) (defun planner-browser-sort-access-time (l r) (let ((la (file-attributes (cdr l))) (ra (file-attributes (cdr r)))) (time-less-p (nth 4 la) (nth 4 ra)))) (defun planner-browser-sort-access-time-reverse (l r) (planner-browser-sort-access-time r l)) (defun planner-browser-calendar-goto () "Goto the planning file corresponding to the calendar date. Correspond to `planner-calendar-goto'." (interactive) (let* ((cdate (calendar-cursor-to-date)) (file (format "%04d.%02d.%02d" (nth 2 cdate) (nth 0 cdate) (nth 1 cdate)))) (planner-browser-show file nil (format "File not exists: %s " file)))) (defun planner-browser-show-debug () "show debug info" (interactive) (let ((prj emacs-wiki-current-project) (dir emacs-wiki-directories) (prjs emacs-wiki-projects) (alist emacs-wiki-file-alist)) (with-output-to-temp-buffer "*planner-browser-debug*" (print prj) (print dir) (print prjs) (print alist) nil))) (defvar planner-browser-mode-hook nil "*List of functions called whenever `planner-browser-directory' is called.") (defun planner-browser-directory () "Browse emacs-wiki-directories. Special commands: \\[planner-browser-next-line] move to next planner file. \\[planner-browser-previous-line] move to previous planner file. \\[planner-browser-exit] exit planner-browser. \\[planner-browser-show-current-line] Show current Planner file. \\[planner-browser-show-today] Goto today Planner file. \\[planner-browser-publish-this-line] Publish current Planner file. \\[planner-browser-rotate-indexes] Rotate content. \\[planner-browser-calendar-toggle] Show/Hide Calendar. \\[planner-browser-project-info] Show project info. \\[planner-browser-change-project-and-update] Change current project. \\[describe-mode] Show this message. " (interactive) (save-excursion (get-buffer-create planner-browser-index-buffer) (planner-browser-init-window) (set-buffer planner-browser-index-buffer) (setq truncate-lines t) ;; automarically buffer-local variable (planner-update-wiki-project) ;; add WikiPlanner (make-local-variable 'emacs-wiki-current-project) (make-local-variable 'emacs-wiki-file-alist) (planner-browser-change-project-and-update planner-browser-initial-project) (select-window planner-browser-index-window) (setq major-mode 'planner-browser-directory mode-name "planner-browser-mode") (setq mode-line-buffer-identification `(,emacs-wiki-current-project)) (setq mode-line-format '(" " mode-line-buffer-identification ":")) (add-hook 'calendar-move-hook 'planner-browser-calendar-goto nil nil) (use-local-map planner-browser-mode-map) (run-hooks 'planner-browser-mode-hook)))