;;; d-recent.el --- log of recently edited files

;; Copyright (C) 2006-2011 Davin Pearson

;; Author/Maintainer: m4_davin_pearson
;; Keywords: recently edited files
;; Version: 1.0

;;; Commentary:

;; This file is not part of GNU Emacs.

;; When you quit Emacs a list of files that were edited is written to
;; disk in the file and folder ~/.emacs.d/recent-YYYYMMDD-HHMMSS.

;;; m4_limitation_of_warranty

;;; m4_install_instructions(d-recent)

;;; Known Bugs:

;; None so far!

;;; Code:

(require 'diagnose)
(require 'd-time)
(require 'd-make-faces)
(require 'd-groups)
(require 'd-find)

(defvar d-recent--pad-width 10)
(defvar d-recent--keymap (make-keymap))
(defvar d-recent--buf-name "*recent*")

(global-set-key [kp-enter] 'd-recent-switch-to-buffer)

;;(defvar d-recent--time-emacs-started-stamp (d-time--decode-time-readable (current-time)))
(defvar d-recent--persistent-file-name (car (last (directory-files "~/.emacs.d" t "^recent.*$"))))

(defun d-recent--get-nf-string (cons-cell)
  (assert (consp cons-cell))
  (assert (numberp (car cons-cell)))
  (assert (stringp (cdr cons-cell)))
  ;;(debug "Nail")
  (let ((number (format (concat "%" (format "%d" 10) "d") (car cons-cell)))
        (file   (cdr cons-cell)))
    (concat number " " file)))

(defun d-recent--insert-lines ()
  (progn
    (if (get-buffer d-recent--buf-name)
        (kill-buffer d-recent--buf-name))
    (generate-new-buffer d-recent--buf-name)
    (set-buffer d-recent--buf-name)
    (erase-buffer)
    (goto-char (point-min))
    (let ((ptr d-recent--list))
      (while ptr
        ;;(debug)
        (if (file-exists-p (cdar ptr))
            (insert (d-recent--get-nf-string (car ptr)) "\n"))
        (setq ptr (cdr ptr)))
      ))
  )

(defun d-recent--fontify-lines ()
  (assert (string= d-recent--buf-name (buffer-name)))
  (let ((array (make-vector 999 0)))
    (let ((i 0) (len (length array)))
      (while (< i len)
        (aset array i (read-str (eval (format "recent-line%04d" i))))
        (setq elt (aref array i))
        (make-face elt)
        (incf i)
        )
      (goto-char (point-min))
      (setq i 0)
      (while (and (< (point) (point-max)) (< i len))
        (let* ((s (d-current-line-as-string))
               (min (point-at-bol))
               (max (point-at-eol))
               (pair (d-groups-get-face (substring s (1+ d-recent--pad-width)))))
          (setq elt (aref array i))
          (set-face-background elt (nth 0 pair))
          (set-face-foreground elt (nth 1 pair))
          ;;(debug "Foo")
          (put-text-property min (1+ max) 'face elt)
          (forward-line 1)
          (incf i)
          )
        )
      )
    )
  )

(defun d-recent--pred (x y)
  (> (car x) (car y)))

(defun d-recent--generate-buffer ()
  (interactive)
  ;;(d-beeps "foo")
  (setq d-recent--list (sort d-recent--list 'd-recent--pred))
  (save-excursion
    (d-recent--insert-lines)
    (use-local-map d-recent--keymap)
    (local-set-key "\C-m" 'd-recent--find-file)
    (d-recent--fontify-lines)
    ;;(debug "Bing")
    )
  (progn
    (set-buffer d-recent--buf-name)
    (goto-char (point-min)))
  )

(defun d-recent-switch-to-buffer ()
  (interactive)
  ;;(when (or (not (get-buffer d-recent--buf-name)) (= 0 (buffer-size (get-buffer d-recent--buf-name))))
  ;;(d-foo)
  (if (get-buffer d-recent--buf-name)
      (kill-buffer d-recent--buf-name))
  ;;(if (not (get-buffer d-recent--buf-name))
  (d-recent--generate-buffer)
  (switch-to-buffer d-recent--buf-name)
  ;;(debug 123)
  )

(defun d-recent--find-file ()
  (interactive)
  (let ((s (substring (d-current-line-as-string) (1+ d-recent--pad-width))))
    ;;(message "s=%s" s)
    (assert (file-exists-p s))
    (d-find-file s)
    ;;(kill-buffer (get-buffer d-recent--buf-name))
    )
  )

;;;
;;; USED BY: d-find.el
;;;
(defun d-recent-find-file-hook (filename)

  (setq filename (safe-compress-file-name filename))

  ;;(d-beeps "Foo")

  (if (file-directory-p filename)
      (setq filename (concat filename "/")))

  (if (string= d-recent--persistent-file-name filename)
      nil
    (if t;;(not (file-directory-p filename))
        (let ((found (rassoc filename d-recent--list)))
          (if found
              (setcar found (1+ (car found)))
            (setq d-recent--list (cons (cons 1 filename) d-recent--list))))

      ;;(d-recent--generate-buffer)

      )
    )
  )

(defun d-recent--load-log-file ()
  (save-excursion
    (setq d-recent--list nil)
    ;;
    ;; NOTE: find-file rather than d-find-file is used here to prevent an infinite recursion
    ;;
    (find-file d-recent--persistent-file-name)
    (goto-char (point-min))
    (while (< (point) (point-max))
      (let* ((line   (d-current-line-as-string))
             (number (read (substring line 0 d-recent--pad-width)))
             (file   (substring line (1+ d-recent--pad-width))))
        (if (file-exists-p file)
            (setq d-recent--list (cons (cons number file) d-recent--list))))
      (forward-line 1))
    (kill-buffer nil)
    )
  )

;;(d-recent--load-log-file)

(add-hook 'kill-emacs-hook 'd-recent--save-log-file)
;;(add-hook 'after-save-hook 'd-recent--save-log-file)
;;(add-hook 'write-file-hooks 'd-recent--save-log-file)
;;(setq kill-emacs-hook nil)
;;(setq after-save-hook nil)
;;(setq write-file-hooks nil)

(defun d-recent--save-log-file ()
  (interactive)
  (progn
    ;;
    ;; NOTE: doesn't use find-file to prevent an infinite recursion
    ;;
    (find-file d-recent--persistent-file-name)
    (read-only-mode -1)
    (erase-buffer)
    (let ((ptr d-recent--list)
          line)
      (while ptr
        (when (file-exists-p (cdar ptr))
          (setq line (d-recent--get-nf-string (car ptr)))
          (insert line "\n"))
        (setq ptr (cdr ptr))))
    (save-buffer)
    (kill-buffer)
    (message "Saved log file %s" d-recent--persistent-file-name)
    (sit-for 1)
    )
  )

;;;
;;; (setq ptr (buffer-list))
;;; (setq ptr (cdr ptr))
;;;
(defadvice save-some-buffers (before d-recent activate)
  (let* ((list  (buffer-list))
         (ptr   list)
         (found nil))
    (setq d-recent--list (sort d-recent--list 'd-recent--pred))
    (while (and ptr (not found))
      (when (and (buffer-modified-p (car ptr)) (buffer-file-name (car ptr)))
        ;;(debug)
        (setq found t))
      (setq ptr (cdr ptr)))
    ;;(debug)
    (if found (d-recent--save-log-file))
    ;;(d-beeps "defadvice save-some-buffers")
  ))

(setq d-recent--list nil)

(provide 'd-recent)
;;; d-recent.el ends here
