Feeds:
Posts
Comments

Archive for the ‘Emacs’ Category

Hang on, that should be Vim vs Emacs right? No.

I have been blogging for the Perl community I have been writing Perl blog posts for around 6 weeks now. Prior to that, I wrote mostly Emacs related blog posts for around 18 months. My vast experience puts me in a great position to compare them.

Emacs blogging

Back when I wrote about Emacs I averaged a couple of posts a week and around 200 visitors a day. Better still, I got several comments for each post, pointing out things I had got wrong, things I could improve or alternative techniques.

200 visitors isn’t many, but it was plenty for me. I was paid for my blogging effort in comment currency.

I suspect the reason I got the comments was that I never had much competition in the emacs hints and tips space. The main ones were:

Of these, only emacs-fu kept grinding out the handy tips, week in week out.

Interestingly, emacsblog reports 3379 readers by feedburner which indicates a decent level of interest in emacs hints and tips.

There was only one real place to pick up emacs news – Planet Emacsen. My posts would hang around for a week or more and I would pick up pretty much all the readers who were interested as well as probably quite a few who weren’t.

Perl blogging

With my Perl blog posts, I struggle to keep 100 visitors a day and I need to post every other day to get that many. Why the difference? I suspect it is a combination of things.

  • If you want to read about perl, you can read the gurus in the community – the Miyagawas, the Tim Bunces, the Curtis Jewels. And there’s hundreds more perl gurus blogging. Okay, maybe 10 more.
  • There are multiple perl news sources. I listed a few here.
  • If you want to improve your perl-fu there is IRC or Perl Monger groups. Emacs in contrast had fewer options.
  • There are more perl bloggers, so your post disappears off the Ironman aggregation pretty quickly.
  • Six weeks may not be enough to build up a following.
  • There are more areas of perl to be interested in. Maybe no-one else wants to know about writing AnyEvent TCP servers.

It could also be that the quality of my writing is poor. But one of you guys would tell me, right?

Why does this matter?

Blogging has to have some value to me, otherwise I might as well watch TV. I do get something out of it even if there are no readers – I’m able to find my fantastic code snippets as long as I have access to the internet. But the lack of input means this doesn’t really offset the effort to write a story around each post. And these days with github et al there are easier and better ways to get your code out there.

Okay, no worries, you’re thinking, if Jared stops blogging about perl (not that I’m thinking of doing so) no-one loses anything. But maybe my experiences is why the level of blogging activity is low compared to the relative size of the community.

And maybe it just doesn’t matter.

Read Full Post »

I am a student of the design your data structures first school of programming1. My first proper language was C and in that language pretty much all you had was typedef struct and a handful of primitive types.

Despite the fact that I now spend most of my time programming with dynamic languages (emacs lisp and perl), I still believe it is important to think about your types before you start programming. Eh, but what’s that? Dynamic languages don’t have types? Au contraire mon ami. Not having static type-checking is not the same as not having types.

So, enough handwaving, what about a real example?

Extensible Vectors

In C++, one of the data structures I use most is the STL vector. But in emacs lisp, the vector is a very different animal as it doesn’t automatically extend. If I want an extensible vector type (an evector), I have to implement it myself (or find a library containing one, but that wouldn’t make a good example).

So, first of all, I need to know what pieces of information each evector will need. There will be the vector itself, plus the current end of the vector and the current size before we need to resize it. If we want to use fibonacci to get the next size of vector we need another slot for the fibonacci counter. So each evector will look like this:

['evector [...] <end position> <fib counter>]

The constructor is generally called make-<typename> so the implementation (assuming we want a vector that starts with 8 slots) is:

(defun make-evector ()
  (vector 'evector (make-vector 8 nil) 0 5))

We also need a predicate for checking if a variable is of type evector.

(defun evectorp (object)
  (eq (aref object 0) 'evector))

What does the push-back operation look like?

(defun evector-push-back (object elem)
  (let* ((vec (aref object 1))
         (pos (aref object 2))
         (len (length vec)))
    ;; increase the size of the vector if necessary
    (when (<= len pos)
      (let* ((new-size (+ len (aref object 3)))
             (new-vec (make-vector new-size nil))
             (i 0))
        ;; copy the original vector into the new one
        (while (< i len)
          (aset new-vec i (aref vec i))
          (incf i))
        (setq vec new-vec)
        (setf (aref object 1) vec)
        (setf (aref object 3) len)))
    ;; set the vector element and update end position
    (aset vec pos elem)
    (incf (aref object 2))))

Hmmm… referring to the member variables by position looks error prone and inflexible. What can we do about that?

If you’re thinking accessors, you’re on the right lines. Fortunately, because of setf, you only need a getter.

(edit: fixed, thanks Jisang)

(defsubst evector-data (object) (aref object 1))
(defsubst evector-end-position (object aref object 2))
(defsubst evector-fib-var (object aref object 3))

defstruct

But even better, there is a macro that does defines your constructor, predicate and accessors for you, defstruct.

(defstruct evector
  (data (make-vector 8 nil))
  (end-pos 0)
  (fib-var 5))

Yet another advantage of using defstruct, is that the error messages are clearer than with my basic accessors.

(evector-fib-var 'x)

[basic accessor]
Debugger entered--Lisp error: (wrong-number-of-arguments (lambda (object aref object 3)) 1)

[defstruct accessor]
Debugger entered--Lisp error: (error "evector-fib-var accessing a non-evector")
  signal(error ("evector-fib-var accessing a non-evector"))
  error("evector-fib-var accessing a non-evector")
...

edit: apologies to those who arrived from ironman perl. This isn’t a perl related post so I hadn’t tagged it as such. I didn’t realise that posts that mention perl anywhere get picked up.


1. Although it might not come across in my examples :)

Read Full Post »

Directory Aliases is one of my favourite emacs packages, well, out of the ones I have written at least. It provides a way to go from an alias to a directory location using ido and dired respectively. On Linux, I can use shell aliases and the emacs daemon but for my sins, a lot of my time is spent developing on Windows and there it is invaluable. As I find the various Windows shells are lacking it is nice to an alternative interface to the OS – emacs.

alias realias='$EDITOR ~/.aliases; source ~/.aliases'

Inspired by chromatic’s post mentioning realias (attributed to Damian Conway) I added a couple of features to directory aliases. The first is the ability to open a regular file in addition to the directory. The second is the ability to reload the aliases so you have access to them immediately after adding them.

Future plans include a facility to add an alias with a keystroke while visiting a file (maybe I should be using bookmarks instead… but I like ido too much).

(require 'ido)
(require 'dired)

(defconst *home* "c:/home/jared")
(defconst *packages* "c:/packages")

(defconst *file:dir-aliases* (concat *elisp-dir* "dir-aliases.el"))

(defsubst home (path) (concat *home* path))
(defsubst packages (path) (concat *packages* path))

The preamble has some convenience functions for referring to commonly used areas.

(defconst *file-aliases*
  (list (cons "dir-aliases" *file:dir-aliases*)
        ...))

(defconst *dired-aliases*
  (list (cons "project" (home "/project"))
        (cons "curious" (home "/websites/curious"))
        (cons "plack" (home "/plack-tests"))
        (cons "site-lisp" (packages "/emacs/site-lisp"))
        ...))

I main separate alists for file aliases and dired aliases.

(defun my-open-alias (aliases fn &optional alias)
  (interactive)
  (unless alias
    (setq alias
          (ido-completing-read "Alias: "
                               (mapcar (lambda (e) (car e)) aliases)
                               nil t)))
  (if (and (stringp alias) (> (length alias) 0 ))
      (let ((pair (assoc alias aliases)))
        (if pair
            (funcall fn (cdr pair))
          (error "Invalid alias %s" alias)))
    (error "Invalid alias %s" alias)))

The main my-open-alias function reads in the alias with ido and then applies the passed in function.

(defun file-open-alias (&optional alias)
  (interactive)
  (my-open-alias *file-aliases* #'find-file alias))

(defun dired-open-alias (&optional alias)
  (interactive)
  (my-open-alias *dired-aliases* #'dired alias))

(defun reload-aliases ()
  (interactive)
  (load *file:dir-aliases*))

More convenience functions using #'find-file and #'dired and the alias reloader I mentioned earlier.

(defvar f2-prefix-map nil)
(setq f2-prefix-map (make-sparse-keymap))

(global-set-key [f2] f2-prefix-map)
(global-set-key (kbd " d") 'dired-open-alias)
(global-set-key (kbd " f") 'file-open-alias)

(provide 'dir-aliases)

Maybe dir-aliases isn’t the best name anymore.

Read Full Post »

Sorting Records With Emacs

I use sort-lines fairly frequently to keep my #includes, uses or variable names in order. Occasionally though, I need to sort something slightly more complicated and I end up writing a little perl script. I found myself thinking it must be equally easy in emacs.

For example, I say I have the following data which I want to sort by the second field which is in position 12 and 13.

Edward Wood 9   01/07/2005
Ed Smith    25  06/12/2004
James Brown 18  01/07/2005
Jon James   13  05/15/2007

Now, emacs does have a sort-columns function which does all sorts of fancy things including checking if we are using Unix and shelling out to sort if possible, but it doesn’t seem to be able to do a numerical comparison. However, in sort.el it does have a general purpose sorting function called sort-subr.

(defun sort-subr (reverse nextrecfun endrecfun
                          &optional startkeyfun endkeyfun predicate)

sort-lines itself is a good start for my custom function as it has all the code for handling regions, reversal and skipping between records (lines) that we need.

(defun sort-lines (reverse beg end)
  (interactive "P\nr")
  (save-excursion
    (save-restriction
      (narrow-to-region beg end)
      (goto-char (point-min))
      (let ((inhibit-field-text-motion t))
        (sort-subr reverse 'forward-line 'end-of-line)))))

Getting the startkeyfun and endkeyfun correct was fairly straight forward but I wasn’t entirely sure what information was passed to the predicate.

(defun sort-on-field (reverse beg end)
  (interactive "P\nr")
  (save-excursion
    (save-restriction
      (narrow-to-region beg end)
      (goto-char (point-min))
      (let ((inhibit-field-text-motion t))
        (sort-subr reverse 'forward-line 'end-of-line
                   (lambda () (forward-char 12))
                   (lambda () (forward-char 2))
                   (lambda (a b)
                     (message (format "[%s] [%s]" a b))))))))

[(703 . 705)] [(676 . 678)]
[(757 . 759)] [(730 . 732)]
[(757 . 759)] [(703 . 705)]
[(730 . 732)] [(703 . 705)]

The final working version:

(defun sort-on-field (reverse beg end)
  (interactive "P\nr")
  (save-excursion
    (save-restriction
      (narrow-to-region beg end)
      (goto-char (point-min))
      (let ((inhibit-field-text-motion t))
        (sort-subr reverse 'forward-line 'end-of-line
                   (lambda () (forward-char 12))
                   (lambda () (forward-char 2))
                   (lambda (a b)
                     (let ((s1 (buffer-substring (car a) (cdr a)))
                           (s2 (buffer-substring (car b) (cdr b))))
                       (< (string-to-number s1) (string-to-number s2)))))))))

Providing a nice way of asking for the start and end location of the sorting field is left as an exercise for the reader.

Read Full Post »

I don’t create many lisp macros but it is still a huge advantage having them. I’m a big user of macros that other people have created. For example, by default emacs lisp does not support keyword parameters. Thanks to the magic of macros they have been added in the cl.el common lisp package.

(require 'cl)

(defun* example (&rest args
                 &key (a 1) (b 2))
  (format "%s %s %s" args a b))

(example) works as expected and returns "nil 1 2"

(example :a 10) is pretty good too although you may be surprised that keywords are not removed from the &rest args "(:a 10) 10 2"

However, (example 1) returns the following surprise (error "Keyword argument 1 not one of (:a :b)").

If you are using &rest and &key together, you can fix this with &allow-other-keys.

(defun* example (&rest args
                 &key (a 1) (b 2)
                 &allow-other-keys)
  (format "%s %s %s" args a b))

Okay, so much for pithy examples. How about something practical?

(defun* remote-path (&rest path
                     &key (user "jared") (server "someserver")
                     &allow-other-keys)
  (concat "ftp:" user "@" server ":"
          (mapconcat #'identity path "/")))

(remote-path :user "uat-user" "/tmp" "test-dir") throws this error (wrong-type-argument sequencep :user). This is because if you use &rest arguments and &key arguments together, the keyword arguments are also passed through into &rest.

I couldn’t find an obvious function to remove keyword parameters so I’ve written my own remove-keyword-params.

(defun remove-keyword-params (seq)
  (if (null seq) nil
    (let ((head (car seq))
          (tail (cdr seq)))
      (if (keywordp head) (remove-keyword-params (cdr tail))
        (cons head (remove-keyword-params tail))))))

(defun* remote-path (&rest path
                     &key (user "jared") (server "someserver")
                     &allow-other-keys)
  (concat "ftp:" user "@" server ":"
          (mapconcat #'identity (remove-keyword-params path) "/")))

Now it works correctly.

(remote-path :user "uat-user" "/tmp" "test-dir")
;; "ftp:uat-user@someserver:/tmp/test-dir"

So what do I want this for? For my directory aliases of course.

(defconst *dired-dirs*
  (list (cons "dev" (remote-path "~/dev"))
        (cons "dev:system"
              (remote-path :user "dev-user" "~/system"))
        (cons "uat:system"
              (remote-path :user "dev-user" "~/system"))))

And surprise surprise, because emacs is architected so well, it all works remotely through tramp completely transparently.

Read Full Post »

Does anyone else use tramp? It is a mode that allows emacs to transparently edit remote files. For example, you have a file called afile.txt on a server called someserver.

/ftp:jared@someserver:~/afile.txt

Then behind the scenes emacs will ftp the the file to and from the server every time you save.

What Is Happening In Ange-ftp?

Recently tramp has been letting me down. When I try and access a remote-file it fails to login to the ftp process. Sad.

The ftp login as handled by ange-ftp. I finally tracked the problem down to this section.

(defun ange-ftp-get-process (host user)
      ;; ...
      (let ((pass (ange-ftp-quote-string
                   (ange-ftp-get-passwd host user)))

ange-ftp-get-process calls (ange-ftp-quote-string ...). Normally, this isn’t a problem. For example, on the Windows PC I am using at this precise moment (ange-ftp-quote-string "!\"£$%^&*()") expands to "!\"£$%^&*()". However, at work, I get:

"\\!\"£\\$\\%^\\&\\*()"

Could this be due to the customize EmacsW32 option? I’m not sure. Anyway, absent the underlying cause, my work-around is to (require 'ange-ftp) and then redefine the ange-ftp-get-process without the quoting of the password.

(require 'ange-ftp)

(defun ange-ftp-get-process (host user)
      ;; ...
      (let ((pass (ange-ftp-get-passwd host user))

Read Full Post »

I have a number of utility functions in a file called my-utils.el. The first one I wrote was add-path.

add-path

(defun add-path (&rest path)
  (let ((full-path ""))
    (dolist (var path)
      (setq full-path (concat full-path var)))
    (add-to-list 'load-path (expand-file-name full-path))))

This means that when I add a new third-party library (in its own directory), I can just write:

(add-path *elisp-dir* "muse-3.12")

I have a function called identity that returns whatever was passed to it.

(defun identity (e) e)

This is particularly useful in functions like mapconcat.

(defun add-path (&rest path)
  (add-to-list 'load-path (mapconcat #'identity path "")))

I like this function that I found on the internet a while ago (can’t find it again for attribution unfortunately).

insert-YYMMDD

(defun insert-YYYMMDD ()
  (interactive)
  (insert (format-time-string "%Y%m%d")))

remove-dupes

remove-dupes removes duplicate items that are next to each other in a list in the same way that uniq does. (I mentioned this previously in one of the enabling your users posts.)

(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)))

assoc-replace

Sometimes you will have an assocation list where you just want to replace one of the values.

(defun assoc-replace (seq values)
  "Replace an element within an association list where the cars match."
  (mapcar (lambda (elem)
            (let* ((key (car elem))
                   (val (assoc key values)))
              (if val (cadr val) elem))) seq))

And often, only the cdr needs to change so kv is a shortcut for that case. (kv 'a 'b) returns (a (a . b)).

(defsubst kv (k v)
  `(,k (,k . ,v)))

(These functions were mentioned in emacs association lists)

ext-mode-map

ext-mode-map allows me to easily setup a major mode for extensions that are inconsistently capitalized.

(defun file-extensions (l)
  (concat "\\.\\("
          (mapconcat
           (lambda (s)
             (mapconcat (lambda (c)
                          (let ((c (upcase (char-to-string c))))
                            (concat "[" c (downcase c) "]")))
                        (symbol-name s) ""))
           l "\\|")
          "\\)\\'"))

(defun ext-mode-map (extensions mode)
  (cons (file-extensions extensions) mode))

Then, for example, I can setup my perl extensions like this:

(add-to-list 'auto-mode-alist (ext-mode-map '(pl perl pm) 'cperl-mode))

(These functions were previously mentioned in autoloading an emacs major mode)

duplicate-current-line

I’m still using this function to duplicate the current line. It works unless you are on the final line which is very rare and you can work around that by opening a line below.

(defun duplicate-current-line ()
  (interactive)
  (beginning-of-line nil)
  (let ((b (point)))
    (end-of-line nil)
    (copy-region-as-kill b (point)))
  (beginning-of-line 2)
  (open-line 1)
  (yank)
  (back-to-indentation))

(This function was mentioned previously in A Simple Emacs Shortcut – Duplicate Line)

set-longlines-mode

I often copy text into buffer and want to read it nicely wrapped around. I’ll change this function to use visual-line-mode when all my emacs versions are upgraded to 23.

(defun set-longlines-mode ()
  (interactive)
  (text-mode)
  (longlines-mode 1))

count-words

Other people have talked about their own count-words functions in the past. I have my own ideas about what constitutes a word.

(defun count-words ()
  (interactive)
  (let ((words (count-matches "[-A-Za-z0-9][-A-Za-z0-9.]*"
                              (point-min) (point-max))))
    (message (format "There are %d words" words))))

regex-replace and string-repeat

I sometimes have a text transform that I want to apply globally to a buffer. Exercise for the reader, fix regex-replace to only apply to a region if one is selected.

(defun regex-replace (regex string)
  (goto-char (point-min))
  (while (re-search-forward regex nil t)
    (replace-match string)))

(defun string-repeat (str n)
  (let ((retval ""))
    (dotimes (i n)
      (setq retval (concat retval str)))
    retval))

Read Full Post »

Now that I have customised my emacs extensively, the default configuration is quite uncomfortable for me to use. I have a file called my-defaults.el which is the bare minimum I need to make using emacs a pleasant experience. If I have to sit down at your emacs session, I will probably need to cut and paste these into a temp buffer and call M-x eval-region.

I’ve mentioned some of these modifications before.

I always always use ido and uniquify. Ido makes it so nice for finding files and switching buffers, I now find the default behaviour surprising and sometimes even catch myself waiting for the options to appear.

(require 'ido)
(require 'uniquify)

flex-matching is a given of course, and I don’t like being prompted unnecessarily for new buffers – I’m always creating them.

(ido-mode t)
(setq ido-enable-flex-matching t)

(setq ido-create-new-buffer 'always)

The way emacs deals with identically named files by default is poor, but it is great that it is so easy to fix.

(setq uniquify-buffer-name-style 'reverse)
(setq uniquify-separator "|")
(setq uniquify-after-kill-buffer-p t)
(setq uniquify-ignore-buffers-re "^\\*")

Since emacs23, fonts now look great. This is from my windows config. Other folks have written about beautifying emacs for other OSes. I summarised those posts here.

(set-default-font
 "-outline-Consolas-normal-r-normal-normal-14-97-96-96-c-*-iso8859-1")

It should be obvious what most of these do. The most important ones are setting yes-or-no-p to accept y or n rather than forcing me to type yes<RETURN> and removing the toolbar. Actually no, scratch that, these are all important. (Emacs23 has some of these set by default).

(global-font-lock-mode 1)
(setq inhibit-splash-screen t)
(setq font-lock-maximum-decoration 3)
(setq use-file-dialog nil)
(setq use-dialog-box nil)
(fset 'yes-or-no-p 'y-or-n-p)
(tool-bar-mode -1)
(show-paren-mode 1)
(transient-mark-mode t)
(setq case-fold-search t)
(blink-cursor-mode 0)

It took me ages to figure out how to prevent emacs converting a bunch of spaces into tabs. And of course, the scrollbar should always be on the right.

(custom-set-variables
 '(scroll-bar-mode 'right)
 '(indent-tabs-mode nil))

Where am I?

(line-number-mode 1)
(column-number-mode 1)

Keep emacs backup files in one place. This is from my windows config again.

(push '("." . "c:/home/jared/.emacs-backups") backup-directory-alist)

And make it so that when I copy a region, that gets sent to the OS clipboard.

(setq x-select-enable-clipboard t)
(setq interprogram-paste-function 'x-cut-buffer-or-selection-value)

Read Full Post »

Looking back over my old posts, I found I didn’t cover basic muse setup at the time when I did my original muse series.

part 1 part 2 part 3 part 4

As I’ve mentioned previously, I generate the html (including the funky syntax highlighting) for my posts and squidoo lenses using muse with htmlize.

You can download muse here and if you are using emacs23 then you need the patched version of htmlize, available here.


The first thing I do with most third party modules, is I add the require lines and the paths I need.

(load "my-vars.el")

(defconst *elisp-muse* (concat *elisp-3rd* "/muse-3.12/lisp"))

(add-to-list 'load-path *elisp-muse*)

(require 'htmlize)

(require 'muse-mode)
(require 'muse-publish)
(require 'muse-html)

my-var.el contains constants pointing to the top-level directories (I still haven’t got around to putting everything under emacs.d)

(defconst *elisp-dir* (expand-file-name "~/emacs-files"))
(defconst *elisp-3rd* (concat *elisp-dir* "/third-party"))

(provide 'my-vars)

I use a couple of minor modes together with muse which are added to muse-mode-hook. (Note, I’m still using longlines-mode as at work I’m unable to use emacs23 where visual-line-mode was introduced)

(defun muse-minor-modes ()
  (longlines-mode 1)
  (flyspell-mode 1)
  (font-lock-mode 0))

(add-hook 'muse-mode-hook 'muse-minor-modes)

And I like publishing to be activated by a single key.

(defun my-muse-publish ()
  (interactive)
  (muse-publish-file (buffer-file-name) "html"))

(define-key muse-mode-map [f7] 'my-muse-publish)

Read Full Post »

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 unwind-protect and 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)
        ;; ...
))))

Read Full Post »

« Newer Posts - Older Posts »

Follow

Get every new post delivered to your Inbox.