Storing Session History
A little while ago we were talking about writing a little emacs-based application to enable the users to help themselves. The beginning of this tool needs a light-weight dired using emacs buttons to use for navigating around the filesystem. Today we will look at adding functionality to remember which files and directories have been accessed previously.
First of all we need some variables to store the directories and files in.
(defvar file-editor-current-dir nil) (defvar file-editor-save-dirs nil) (defvar file-editor-save-dirs '(a b c)) (defvar file-editor-save-files nil)
Then we provide a customizable variable where the history will be saved between emacs sessions.
(defcustom file-editor-history-file "~/.file-editor-history" "File in which the file-editor history is saved between invocations. Variables stored are: `file-editor-save-dirs', `file-editor-save-files'." :type 'string :group 'file-editor)
We will frequently be adding the same file and directory into the lists and we don’t want to get dupes. I could use a data structure that helps avoid dupes or I could just sort the lists and remove adjacent dupes. Guess which option I chose.
(defun remove-dupes (list) (let (tmp-list head) (while list (setq head (pop list)) (unless (equal head (car list)) (push head tmp-list))) (reverse tmp-list))) (defun file-editor-sort-history () (setq file-editor-save-dirs (remove-dupes (sort file-editor-save-dirs #'string<))) (setq file-editor-save-files (remove-dupes (sort file-editor-save-files #'string<))))
ido has code that stores history between sessions. I’ve stolen most of it to save the file editor history.
(ido-pp ...) pretty prints the variable contents into the buffer, e.g. something like this.
;; ----- file-editor-save-dirs ----- ( "dir1" "dir2" "dir3" )
(require 'ido) (defun file-editor-save-history () "Save file-editor history between sessions." (let ((buf (get-buffer-create " *file-editor data*")) (version-control 'never)) (unwind-protect (with-current-buffer buf (erase-buffer) (file-editor-sort-history) (ido-pp 'file-editor-save-dirs) (ido-pp 'file-editor-save-files) (write-file file-editor-history-file nil)) (kill-buffer buf))))
When it comes time to load the history back,
(read (current-buffer)) loads it back into the variables. You can see the use of
condtion-case in the code below as I talked about in my emacs lisp error handling post.
(defun file-editor-load-history () (let ((file (expand-file-name file-editor-history-file)) buf) (when (file-readable-p file) (let ((buf (get-buffer-create " *file-editor data*"))) (unwind-protect (with-current-buffer buf (erase-buffer) (insert-file-contents file) (condition-case nil (setq file-editor-save-dirs (read (current-buffer)) file-editor-save-files (read (current-buffer))) (error nil))) (kill-buffer buf))))))
The obvious time to save the history is when we exit emacs.
(defun file-editor-kill-emacs-hook () (file-editor-save-history)) (add-hook 'kill-emacs-hook 'file-editor-kill-emacs-hook)
Modifications To The Original Code
The way we choose which files and directories will be remembered is each time a file is opened, the parent directory and the file including full path are added to the appropriate variable.
(defun file-editor-open-file-editor-file (button) (let ((parent (button-get button 'parent)) (file (button-get button 'file)) (file-complete (concat parent "/" file))) (push parent file-editor-save-dirs) (push file-complete file-editor-save-files) (find-file file-complete) (file-editor-mode) (longlines-mode 1)))
We need to extend the
file-editor-default-dirs function to display the previously stored directories and files.
(defun file-editor-default-dirs () (let ((buffer (get-buffer-create "*file-editor-dir-list*"))) (with-current-buffer buffer (let ((inhibit-read-only t)) (erase-buffer) (file-editor-sort-history) (insert "*** Default File List ***\n\n") (dolist (dir file-editor-default-dirs) (file-editor-insert-opendir-button "" dir)) (when file-editor-save-dirs (insert "\n") (dolist (dir file-editor-save-dirs) (file-editor-insert-opendir-button "" dir))) (when file-editor-save-files (insert "\n") (dolist (file file-editor-save-files) (insert-text-button file 'parent "" 'file file :type 'open-file-editor)))))))
And for some future functionality I am thinking about we also store the current directory that is being visited.
(defun file-editor-dir-list (parent) (let ((buffer (get-buffer-create "*file-editor-dir-list*"))) (with-current-buffer buffer (let ((inhibit-read-only t) files dirs) (setq parent (expand-file-name parent)) (setq file-editor-current-dir parent) (erase-buffer) ;; ... ))))