python emacs / dir-tree.el

;;; dir-tree.el --- Sophisticated example of `tree-widget' usage

;; Copyright (C) 2001, 2003 by David Ponce

;; Author: David Ponce <>
;; Maintainer: David Ponce <>
;; Created: 16 Feb 2001
;; Keywords: extensions
;; Revision: $Id: dir-tree.el,v 1.4 2003/09/30 12:01:33 ponced Exp $

(defconst dir-tree-version "1.1")

;; This file is not part of Emacs

;; This program is free software; you can redistribute it and/or
;; modify it under the terms of the GNU General Public License as
;; published by the Free Software Foundation; either version 2, or (at
;; your option) any later version.

;; This program is distributed in the hope that it will be useful, but
;; WITHOUT ANY WARRANTY; without even the implied warranty of
;; General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program; see the file COPYING.  If not, write to
;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.

;;; Commentary:
;; This library is a sophisticated example of usage of the
;; `tree-widget' library.
;; It defines the command `dir-tree' which displays a directory tree.
;; A directory content is read when unfolding the corresponding node
;; and reused later.  To refresh a directory content just click on the
;; corresponding node name.  Also, you can change the select/unselect
;; state of a file entry by clicking on it.
;; To install and use, put this file on your Emacs-Lisp load path and
;; add the following into your ~/.emacs startup file:
;; (require 'dir-tree)

;;; History:

;;; Code:
(require 'tree-widget)

;;; Compatibility
(if (fboundp 'overlay-lists)
    (defalias 'dir-tree-overlay-lists
  (defalias 'dir-tree-overlay-lists
    '(lambda () (list (extent-list)))))

(if (fboundp 'delete-overlay)
    (defalias 'dir-tree-delete-overlay
  (defalias 'dir-tree-delete-overlay
;;; Widgets
(define-widget 'dir-tree-dir-widget 'tree-widget
  "Directory Tree widget."
  :dynargs        'dir-tree-expand-dir
  :has-children   t)

(define-widget 'dir-tree-file-widget 'push-button
  "File widget."
  :doc            "*"
  ;; When unfolding the parent directory tree keep :doc value which
  ;; could be changed by `dir-tree-toggle-selection'
  :keep           '(:doc)
  :format         "%[%t%]%d"
  :button-face    'default
  :notify         'dir-tree-toggle-selection)

;;; Callbacks
(defun dir-tree-toggle-selection (widget &rest ignore)
  "Change the selected state of WIDGET.
IGNORE other arguments."
  (let ((f (widget-get widget :tag ))
        (p (widget-get widget :path))
        (s (string= (widget-get widget :doc) "*")))
    (if s
        (widget-put widget :doc "-")
      (widget-put widget :doc "*"))
    ;; Redraw the tree node.
    (widget-value-set widget (widget-value widget))
    (message "File %s %s" p (if s "unselected" "selected"))))

(defun dir-tree-refresh-dir (widget &rest ignore)
  "Refresh WIDGET parent tree children.
IGNORE other arguments."
  (let ((tree (widget-get widget :parent)))
    ;; Clear the tree children cache.
    (widget-put tree :args nil)
    ;; Redraw the tree node.
    (widget-value-set tree (widget-value tree))))

(defun dir-tree-widget (e)
  "Return a widget to display file or directory E."
  (if (file-directory-p e)
        :node (push-button
               :tag ,(file-name-as-directory (file-name-nondirectory e))
               :format "%[%t%]\n"
               :notify dir-tree-refresh-dir)
        :path ,e)
      :path ,e
      :tag  ,(file-name-nondirectory e))

(defun dir-tree-list (dir)
  "Return the list of entries in DIR.
Place directories first."
  (let ((entries (directory-files dir 'full))
        files dirs entry)
    (while entries
      (setq entry   (car entries))
      (setq entries (cdr entries))
      (if (not (string= (substring entry -1) "."))
          (if (file-directory-p entry)
              (setq dirs (cons entry dirs))
            (setq files (cons entry files)))))
    (nreverse (nconc files dirs))))

(defun dir-tree-expand-dir (tree)
  "Return TREE widget children.
Reuse :args cache if exists."
  (or (widget-get tree :args)
      (let ((dir (widget-get tree :path)))
        (message "Reading directory '%s'..." dir)
        (condition-case err
                (mapcar 'dir-tree-widget (dir-tree-list dir))
              (message "Reading directory '%s'...done" dir))
           (message "%s" (error-message-string err))
;;; Command
(defun dir-tree (root)
  "Display a tree of entries in ROOT directory."
  (interactive "DRoot: ")
  (switch-to-buffer (format "* %s directory tree*" root))
  (let ((inhibit-read-only t))
  (let ((all (dir-tree-overlay-lists)))
    (mapcar 'dir-tree-delete-overlay (car all))
    (mapcar 'dir-tree-delete-overlay (cdr all)))
  (tree-widget-set-theme "folder")
  (widget-insert (format "%s directory tree. \n\n" root))
  (let ((default-directory root))
    (widget-create (dir-tree-widget root)))
  (use-local-map widget-keymap)
  (goto-char (point-min)))

(provide 'dir-tree)

;;; dir-tree.el ends here