--- /dev/null
+ztree
+=====
+
+Ztree is a project dedicated to implementation of several text-tree applications inside Emacs. It consists of 2 subprojects: **ztree-diff** and **ztree-dir**(the basis of **ztree-diff**). Available in **GNU ELPA** and **MELPA**.
+
+ztree-diff
+==========
+**ztree-diff** is a directory-diff tool for Emacs inspired by commercial tools like Beyond Compare or Araxis Merge. It supports showing the difference between two directories; calling **Ediff** for not matching files, copying between directories, deleting file/directories, hiding/showing equal files/directories.
+
+The comparison itself performed with the external **GNU diff** tool, so make sure to have one in the executable path. Verified on OSX and Linux.
+
+If one wants to have a stand-alone application, consider the (WIP)[zdircmp](https://github.com/fourier/zdircmp) project based on **ztree-diff**.
+
+Add the following to your .emacs file:
+
+```scheme
+(push (substitute-in-file-name "path-to-ztree-directory") load-path)
+(require 'ztree-diff)
+```
+
+Call the `ztree-diff` interactive function:
+
+```
+M-x ztree-diff
+```
+Then you need to specify the left and right directories to compare.
+
+###Hotkeys supported
+The basic hotkeys are the same as in the **ztree-dir**. Additionally:
+ * `RET` on different files starts the **Ediff** (or open file if one absent or the same)
+ * `Space` show the simple diff window for the current file instead of **Ediff** (or view file if one absent or the same)
+ * `TAB` to fast switch between panels
+ * `h` key to toggle show/hide identical files/directories
+ * `C` key to copy current file or directory to the left or right panel
+ * `D` key to delete current file or directory
+ * `v` key to quick view the current file
+ * `r` initiates the rescan/refresh of current file or subdirectory
+ * `F5` forces the full rescan.
+
+Screenshots:
+
+![ztreediff emacsx11](https://github.com/fourier/ztree/raw/screenshots/screenshots/emacs_diff_xterm.png "Emacs in xterm with ztree-diff")
+
+![ztreediff-diff emacsx11](https://github.com/fourier/ztree/raw/screenshots/screenshots/emacs_diff_simplediff_xterm.png "Emacs in xterm with ztree-diff and simple diff")
+
+
+ztree-dir
+---------
+**ztree-dir** is a simple text-mode directory tree for Emacs. See screenshots below for the GUI and the terminal versions of the **ztree-dir**.
+
+As above Add the following to your .emacs file:
+
+```scheme
+(push (substitute-in-file-name "path-to-ztree-directory") load-path)
+(require 'ztree-dir)
+```
+
+Call the `ztree-dir` interactive function:
+
+```
+M-x ztree-dir
+```
+
+* Open/close directories with double-click, `RET` or `Space` keys.
+* To jump to the parent directory, hit the `Backspace` key.
+* To toggle open/closed state of the subtree of the current directory, hit the `x` key.
+
+
+![ztree emacsapp](https://github.com/fourier/ztree/raw/screenshots/screenshots/emacs_app.png "Emacs App with ztree-dir")
+
+![ztree emacsx11](https://github.com/fourier/ztree/raw/screenshots/screenshots/emacs_xterm.png "Emacs in xterm with ztree-dir")
+
--- /dev/null
+;;; ztree-diff-model.el --- diff model for directory trees
+
+;; Copyright (C) 2013-2015 Free Software Foundation, Inc.
+;;
+;; Author: Alexey Veretennikov <alexey dot veretennikov at gmail dot com>
+;;
+;; Created: 2013-11-1l
+;;
+;; Keywords: files tools
+;; URL: https://github.com/fourier/ztree
+;; Compatibility: GNU Emacs GNU Emacs 24.x
+;;
+;; This file is part of GNU Emacs.
+;;
+;; GNU Emacs 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 3 of the License, or
+;; (at your option) any later version.
+;;
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
+;;
+;;; Commentary:
+
+;; Diff model
+
+;;; Code:
+(require 'ztree-util)
+
+(defvar ztree-diff-model-wait-message nil
+ "Message showing while constructing the diff tree.")
+(make-variable-buffer-local 'ztree-diff-model-wait-message)
+
+
+(defun ztree-diff-model-update-wait-message ()
+ "Update the wait mesage with one more '.' progress indication."
+ (when ztree-diff-model-wait-message
+ (setq ztree-diff-model-wait-message (concat ztree-diff-model-wait-message "."))
+ (message ztree-diff-model-wait-message)))
+
+
+
+;; Create a record ztree-diff-node with defined fielsd and getters/setters
+;; here:
+;; parent - parent node
+;; left-path is the full path on the left side of the diff window,
+;; right-path is the full path of the right side,
+;; short-name - is the file or directory name
+;; children - list of nodes - files or directories if the node is a directory
+;; different = {nil, 'new, 'diff} - means comparison status
+(defrecord ztree-diff-node (parent left-path right-path short-name right-short-name children different))
+
+(defun ztree-diff-node-to-string (node)
+ "Construct the string with contents of the NODE given."
+ (let* ((string-or-nil #'(lambda (x) (if x
+ (cond ((stringp x) x)
+ ((eq x 'new) "new")
+ ((eq x 'diff) "different")
+ (t (ztree-diff-node-short-name x)))
+ "(empty)")))
+ (children (ztree-diff-node-children node))
+ (ch-str ""))
+ (dolist (x children)
+ (setq ch-str (concat ch-str "\n * " (ztree-diff-node-short-name x))))
+ (concat "Node: " (ztree-diff-node-short-name node)
+ "\n"
+ ;; " * Parent: " (let ((parent (ztree-diff-node-parent node)))
+ ;; (if parent (ztree-diff-node-short-name parent) "nil"))
+ " * Parent: " (funcall string-or-nil (ztree-diff-node-parent node))
+ "\n"
+ " * Left path: " (funcall string-or-nil (ztree-diff-node-left-path node))
+ "\n"
+ " * Right path: " (funcall string-or-nil (ztree-diff-node-right-path node))
+ "\n"
+ " * Children: " ch-str
+ "\n")))
+
+
+(defun ztree-diff-node-short-name-wrapper (node &optional right-side)
+ "Return the short name of the NODE given.
+If the RIGHT-SIDE is true, take the right leaf"
+ (if (not right-side)
+ (ztree-diff-node-short-name node)
+ (ztree-diff-node-right-short-name node)))
+
+
+(defun ztree-diff-node-is-directory (node)
+ "Determines if the NODE is a directory."
+ (let ((left (ztree-diff-node-left-path node))
+ (right (ztree-diff-node-right-path node)))
+ (if left
+ (file-directory-p left)
+ (file-directory-p right))))
+
+(defun ztree-diff-node-side (node)
+ "Determine the side there the file is present for NODE.
+Return BOTH if the file present on both sides;
+LEFT if only on the left side and
+RIGHT if only on the right side."
+ (let ((left (ztree-diff-node-left-path node))
+ (right (ztree-diff-node-right-path node)))
+ (if (and left right) 'both
+ (if left 'left 'right))))
+
+(defun ztree-diff-node-equal (node1 node2)
+ "Determines if NODE1 and NODE2 are equal."
+ (and (string-equal (ztree-diff-node-short-name node1)
+ (ztree-diff-node-short-name node2))
+ (string-equal (ztree-diff-node-left-path node1)
+ (ztree-diff-node-left-path node2))
+ (string-equal (ztree-diff-node-right-path node1)
+ (ztree-diff-node-right-path node1))))
+
+(defun ztree-diff-untrampify-filename (file)
+ "Return FILE as the local file name."
+ (require 'tramp)
+ (if (not (tramp-tramp-file-p file))
+ file
+ (tramp-file-name-localname (tramp-dissect-file-name file))))
+
+(defun ztree-diff-modef-quotify-string (x)
+ "Surround string X with quotes."
+ (concat "\"" x "\""))
+
+(defun ztree-diff-model-files-equal (file1 file2)
+ "Compare files FILE1 and FILE2 using external diff.
+Returns t if equal."
+ (let* ((file1-untrampified (ztree-diff-untrampify-filename (ztree-diff-modef-quotify-string file1)))
+ (file2-untrampified (ztree-diff-untrampify-filename (ztree-diff-modef-quotify-string file2)))
+ (diff-command (concat "diff -q" " " file1-untrampified " " file2-untrampified))
+ (diff-output (shell-command-to-string diff-command)))
+ (not (> (length diff-output) 2))))
+
+(defun ztree-directory-files (dir)
+ "Return the list of full paths of files in a directory DIR.
+Filters out . and .."
+ (ztree-filter #'(lambda (file) (let ((simple-name (file-short-name file)))
+ (not (or (string-equal simple-name ".")
+ (string-equal simple-name "..")))))
+ (directory-files dir 'full)))
+
+(defun ztree-diff-model-partial-rescan (node)
+ "Rescan the NODE."
+ ;; assuming what parent is always exists
+ ;; otherwise the UI shall force the full rescan
+ (let ((parent (ztree-diff-node-parent node))
+ (isdir (ztree-diff-node-is-directory node))
+ (left (ztree-diff-node-left-path node))
+ (right (ztree-diff-node-right-path node)))
+ ;; if node is a directory - traverse
+ (when (and left right
+ (file-exists-p left)
+ (file-exists-p right))
+ (if isdir
+ (let ((traverse (ztree-diff-node-traverse
+ node
+ left
+ right)))
+ (ztree-diff-node-set-different node (car traverse))
+ (ztree-diff-node-set-children node (cdr traverse)))
+ ;; node is a file
+ (ztree-diff-node-set-different
+ node
+ (if (ztree-diff-model-files-equal left right)
+ nil
+ 'diff))))))
+
+(defun ztree-diff-model-subtree (parent path side)
+ "Create a subtree with given PARENT for the given PATH.
+Argument SIDE either 'left or 'right side."
+ (let ((files (ztree-directory-files path))
+ (result nil))
+ (dolist (file files)
+ (if (file-directory-p file)
+ (let* ((node (ztree-diff-node-create
+ parent
+ (when (eq side 'left) file)
+ (when (eq side 'right) file)
+ (file-short-name file)
+ (file-short-name file)
+ nil
+ 'new))
+ (children (ztree-diff-model-subtree node file side)))
+ (ztree-diff-node-set-children node children)
+ (push node result))
+ (push (ztree-diff-node-create
+ parent
+ (when (eq side 'left) file)
+ (when (eq side 'right) file)
+ (file-short-name file)
+ (file-short-name file)
+ nil
+ 'new)
+ result)))
+ result))
+
+(defun ztree-diff-node-update-diff-from-children (node)
+ "Set the diff status for the NODE based on its children."
+ (let ((children (ztree-diff-node-children node))
+ (diff nil))
+ (dolist (child children)
+ (setq diff
+ (ztree-diff-model-update-diff
+ diff
+ (ztree-diff-node-different child))))
+ (ztree-diff-node-set-different node diff)))
+
+(defun ztree-diff-node-update-all-parents-diff (node)
+ "Recursively update all parents diff status for the NODE."
+ (let ((parent node))
+ (while (setq parent (ztree-diff-node-parent parent))
+ (ztree-diff-node-update-diff-from-children parent))))
+
+
+(defun ztree-diff-model-update-diff (old new)
+ "Get the diff status depending if OLD or NEW is not nil."
+ (if new
+ (if (or (not old)
+ (eq old 'new))
+ new
+ old)
+ old))
+
+(defun ztree-diff-node-traverse (parent path1 path2)
+ "Traverse 2 paths creating the list nodes with PARENT defined and diff status.
+Function traversing 2 paths PATH1 and PATH2 returning the list where the
+first element is the difference status (nil, 'diff, 'new') and
+the rest is the combined list of nodes."
+ (let ((list1 (ztree-directory-files path1))
+ (list2 (ztree-directory-files path2))
+ (different-dir nil)
+ (result nil))
+ (ztree-diff-model-update-wait-message)
+ ;; first - adding all entries from left directory
+ (dolist (file1 list1)
+ ;; for every entry in the first directory
+ ;; we are creating the node
+ (let* ((simple-name (file-short-name file1))
+ (isdir (file-directory-p file1))
+ (children nil)
+ (different nil)
+ ;; create the current node to be set as parent to
+ ;; subdirectories
+ (node (ztree-diff-node-create parent file1 nil simple-name simple-name nil nil))
+ ;; 1. find if the file is in the second directory and the type
+ ;; is the same - i.e. both are directories or both are files
+ (file2 (ztree-find list2
+ #'(lambda (x) (and (string-equal (file-short-name x)
+ simple-name)
+ (eq isdir (file-directory-p x)))))))
+ ;; 2. if it is not in the second directory, add it as a node
+ (if (not file2)
+ (progn
+ ;; 2.1 if it is a directory, add the whole subtree
+ (when (file-directory-p file1)
+ (setq children (ztree-diff-model-subtree node file1 'left)))
+ ;; 2.2 update the difference status for this entry
+ (setq different 'new))
+ ;; 3. if it is found in second directory and of the same type
+ ;; 3.1 if it is a file
+ (if (not (file-directory-p file1))
+ ;; 3.1.1 set difference status to this entry
+ (setq different (if (ztree-diff-model-files-equal file1 file2) nil 'diff))
+ ;; 3.2 if it is the directory
+ ;; 3.2.1 get the result of the directories comparison together with status
+ (let ((traverse (ztree-diff-node-traverse node file1 file2)))
+ ;; 3.2.2 update the difference status for whole comparison from
+ ;; difference result from the 2 subdirectories comparison
+ (setq different (car traverse))
+ ;; 3.2.3 set the children list from the 2 subdirectories comparison
+ (setq children (cdr traverse)))))
+ ;; 2.3 update difference status for the whole comparison
+ (setq different-dir (ztree-diff-model-update-diff different-dir different))
+ ;; update calculated parameters of the node
+ (ztree-diff-node-set-right-path node file2)
+ (ztree-diff-node-set-children node children)
+ (ztree-diff-node-set-different node different)
+ ;; push the created node to the result list
+ (push node result)))
+ ;; second - adding entries from the right directory which are not present
+ ;; in the left directory
+ (dolist (file2 list2)
+ ;; for every entry in the second directory
+ ;; we are creating the node
+ (let* ((simple-name (file-short-name file2))
+ (isdir (file-directory-p file2))
+ (children nil)
+ ;; create the node to be added to the results list
+ (node (ztree-diff-node-create parent nil file2 simple-name simple-name nil 'new))
+ ;; 1. find if the file is in the first directory and the type
+ ;; is the same - i.e. both are directories or both are files
+ (file1 (ztree-find list1
+ #'(lambda (x) (and (string-equal (file-short-name x)
+ simple-name)
+ (eq isdir (file-directory-p x)))))))
+ ;; if it is not in the first directory, add it as a node
+ (when (not file1)
+ ;; if it is a directory, set the whole subtree to children
+ (when (file-directory-p file2)
+ (setq children (ztree-diff-model-subtree node file2 'right)))
+ ;; update the different status for the whole comparison
+ (setq different-dir (ztree-diff-model-update-diff different-dir 'new))
+ ;; set calculated children to the node
+ (ztree-diff-node-set-children node children)
+ ;; push the created node to the result list
+ (push node result))))
+ ;; result is a pair: difference status and nodes list
+ (cons different-dir result)))
+
+(defun ztree-diff-model-create (dir1 dir2)
+ "Create a node based on DIR1 and DIR2."
+ (when (not (file-directory-p dir1))
+ (error "Path %s is not a directory" dir1))
+ (when (not (file-directory-p dir2))
+ (error "Path %s is not a directory" dir2))
+ (setq ztree-diff-model-wait-message (concat "Comparing " dir1 " and " dir2 " ..."))
+ (let* ((model
+ (ztree-diff-node-create nil dir1 dir2
+ (file-short-name dir1)
+ (file-short-name dir2)
+ nil
+ nil))
+ (traverse (ztree-diff-node-traverse model dir1 dir2)))
+ (ztree-diff-node-set-children model (cdr traverse))
+ (ztree-diff-node-set-different model (car traverse))
+ (message "Done.")
+ model))
+
+(defun ztree-diff-model-update-node (node)
+ "Refresh the NODE."
+ (setq ztree-diff-model-wait-message
+ (concat "Updating " (ztree-diff-node-short-name node) " ..."))
+ (let ((traverse (ztree-diff-node-traverse node
+ (ztree-diff-node-left-path node)
+ (ztree-diff-node-right-path node))))
+ (ztree-diff-node-set-children node (cdr traverse))
+ (ztree-diff-node-set-different node (car traverse))
+ (message "Done.")))
+
+
+
+(provide 'ztree-diff-model)
+
+;;; ztree-diff-model.el ends here
--- /dev/null
+;;; ztree-diff.el --- Text mode diff for directory trees
+
+;; Copyright (C) 2013-2015 Free Software Foundation, Inc.
+;;
+;; Author: Alexey Veretennikov <alexey dot veretennikov at gmail dot com>
+;;
+;; Created: 2013-11-1l
+;;
+;; Keywords: files tools
+;; URL: https://github.com/fourier/ztree
+;; Compatibility: GNU Emacs GNU Emacs 24.x
+;;
+;; This file is part of GNU Emacs.
+;;
+;; GNU Emacs 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 3 of the License, or
+;; (at your option) any later version.
+;;
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
+;;
+;;; Commentary:
+
+;;; Code:
+(require 'ztree-view)
+(require 'ztree-diff-model)
+
+(defconst ztree-diff-hidden-files-regexp "^\\."
+ "Hidden files regexp.
+By default all filest starting with dot '.', including . and ..")
+
+(defface ztreep-diff-header-face
+ '((((type tty pc) (class color)) :foreground "lightblue" :weight bold)
+ (((background dark)) (:height 1.2 :foreground "lightblue" :weight bold))
+ (t :height 1.2 :foreground "darkblue" :weight bold))
+ "*Face used for the header in Ztree Diff buffer."
+ :group 'Ztree-diff :group 'font-lock-highlighting-faces)
+(defvar ztreep-diff-header-face 'ztreep-diff-header-face)
+
+(defface ztreep-diff-header-small-face
+ '((((type tty pc) (class color)) :foreground "lightblue" :weight bold)
+ (((background dark)) (:foreground "lightblue" :weight bold))
+ (t :weight bold :foreground "darkblue"))
+ "*Face used for the header in Ztree Diff buffer."
+ :group 'Ztree-diff :group 'font-lock-highlighting-faces)
+(defvar ztreep-diff-header-small-face 'ztreep-diff-header-small-face)
+
+(defface ztreep-diff-model-diff-face
+ '((t (:foreground "red")))
+ "*Face used for different files in Ztree-diff."
+ :group 'Ztree-diff :group 'font-lock-highlighting-faces)
+(defvar ztreep-diff-model-diff-face 'ztreep-diff-model-diff-face)
+
+(defface ztreep-diff-model-add-face
+ '((t (:foreground "blue")))
+ "*Face used for added files in Ztree-diff."
+ :group 'Ztree-diff :group 'font-lock-highlighting-faces)
+(defvar ztreep-diff-model-add-face 'ztreep-diff-model-add-face)
+
+(defface ztreep-diff-model-normal-face
+ '((t (:foreground "#7f7f7f")))
+ "*Face used for non-modified files in Ztree-diff."
+ :group 'Ztree-diff :group 'font-lock-highlighting-faces)
+(defvar ztreep-diff-model-normal-face 'ztreep-diff-model-normal-face)
+
+
+(defvar ztree-diff-filter-list (list ztree-diff-hidden-files-regexp)
+ "List of regexp file names to filter out.
+By default paths starting with dot (like .git) are ignored")
+(make-variable-buffer-local 'ztree-diff-filter-list)
+
+(defvar ztree-diff-dirs-pair nil
+ "Pair of the directories stored. Used to perform the full rescan.")
+(make-variable-buffer-local 'ztree-diff-dirs-pair)
+
+(defvar ztree-diff-show-equal-files t
+ "Show or not equal files/directories on both sides.")
+(make-variable-buffer-local 'ztree-diff-show-equal-files)
+
+;;;###autoload
+(define-minor-mode ztreediff-mode
+ "A minor mode for displaying the difference of the directory trees in text mode."
+ ;; initial value
+ nil
+ ;; modeline name
+ " Diff"
+ ;; The minor mode keymap
+ `(
+ (,(kbd "C") . ztree-diff-copy)
+ (,(kbd "h") . ztree-diff-toggle-show-equal-files)
+ (,(kbd "D") . ztree-diff-delete-file)
+ (,(kbd "v") . ztree-diff-view-file)
+ (,(kbd "d") . ztree-diff-simple-diff-files)
+ (,(kbd "r") . ztree-diff-partial-rescan)
+ ([f5] . ztree-diff-full-rescan)))
+
+
+(defun ztree-diff-node-face (node)
+ "Return the face for the NODE depending on diff status."
+ (let ((diff (ztree-diff-node-different node)))
+ (cond ((eq diff 'diff) ztreep-diff-model-diff-face)
+ ((eq diff 'new) ztreep-diff-model-add-face)
+ (t ztreep-diff-model-normal-face))))
+
+(defun ztree-diff-insert-buffer-header ()
+ "Insert the header to the ztree buffer."
+ (insert-with-face "Differences tree" ztreep-diff-header-face)
+ (newline-and-begin)
+ (when ztree-diff-dirs-pair
+ (insert-with-face (concat "Left: " (car ztree-diff-dirs-pair))
+ ztreep-diff-header-small-face)
+ (newline-and-begin)
+ (insert-with-face (concat "Right: " (cdr ztree-diff-dirs-pair))
+ ztreep-diff-header-small-face)
+ (newline-and-begin))
+ (insert-with-face "Legend:" ztreep-diff-header-small-face)
+ (newline-and-begin)
+ (insert-with-face " Normal file " ztreep-diff-model-normal-face)
+ (insert-with-face "- same on both sides" ztreep-diff-header-small-face)
+ (newline-and-begin)
+ (insert-with-face " Orphan file " ztreep-diff-model-add-face)
+ (insert-with-face "- does not exist on other side" ztreep-diff-header-small-face)
+ (newline-and-begin)
+ (insert-with-face " Mismatch file " ztreep-diff-model-diff-face)
+ (insert-with-face "- different from other side" ztreep-diff-header-small-face)
+ (newline-and-begin)
+ (insert-with-face "==============" ztreep-diff-header-face)
+ (newline-and-begin))
+
+(defun ztree-diff-full-rescan ()
+ "Force full rescan of the directory trees."
+ (interactive)
+ (when (and ztree-diff-dirs-pair
+ (yes-or-no-p (format "Force full rescan?")))
+ (ztree-diff (car ztree-diff-dirs-pair) (cdr ztree-diff-dirs-pair))))
+
+
+
+(defun ztree-diff-existing-common (node)
+ "Return the NODE if both left and right sides exist."
+ (let ((left (ztree-diff-node-left-path node))
+ (right (ztree-diff-node-right-path node)))
+ (if (and left right
+ (file-exists-p left)
+ (file-exists-p right))
+ node
+ nil)))
+
+(defun ztree-diff-existing-common-parent (node)
+ "Return the first node in up in hierarchy of the NODE which has both sides."
+ (let ((common (ztree-diff-existing-common node)))
+ (if common
+ common
+ (ztree-diff-existing-common-parent (ztree-diff-node-parent node)))))
+
+(defun ztree-diff-do-partial-rescan (node)
+ "Partly rescan the NODE."
+ (let* ((common (ztree-diff-existing-common-parent node))
+ (parent (ztree-diff-node-parent common)))
+ (if (not parent)
+ (when ztree-diff-dirs-pair
+ (ztree-diff (car ztree-diff-dirs-pair) (cdr ztree-diff-dirs-pair)))
+ (progn
+ (ztree-diff-model-partial-rescan common)
+ (ztree-diff-node-update-all-parents-diff node)
+ (ztree-refresh-buffer (line-number-at-pos))))))
+
+
+(defun ztree-diff-partial-rescan ()
+ "Perform partial rescan on the current node."
+ (interactive)
+ (let ((found (ztree-find-node-at-point)))
+ (when found
+ (ztree-diff-do-partial-rescan (car found)))))
+
+
+(defun ztree-diff-simple-diff (node)
+ "Create a simple diff buffer for files from left and right panels.
+Argument NODE node containing paths to files to call a diff on."
+ (let* ((node-left (ztree-diff-node-left-path node))
+ (node-right (ztree-diff-node-right-path node)))
+ (when (and
+ node-left
+ node-right
+ (not (file-directory-p node-left)))
+ ;; show the diff window on the bottom
+ ;; to not to crush tree appearance
+ (let ((split-width-threshold nil))
+ (diff node-left node-right)))))
+
+
+(defun ztree-diff-simple-diff-files ()
+ "Create a simple diff buffer for files from left and right panels."
+ (interactive)
+ (let ((found (ztree-find-node-at-point)))
+ (when found
+ (let ((node (car found)))
+ (ztree-diff-simple-diff node)))))
+
+(defun ztree-diff-node-action (node hard)
+ "Perform action on NODE:
+1 if both left and right sides present:
+ 1.1 if they are differend
+ 1.1.1 if HARD ediff
+ 1.1.2 simple diff otherwiste
+ 1.2 if they are the same - view left
+2 if left or right present - view left or rigth"
+ (let ((left (ztree-diff-node-left-path node))
+ (right (ztree-diff-node-right-path node))
+ (open-f '(lambda (path) (if hard (find-file path)
+ (let ((split-width-threshold nil))
+ (view-file-other-window path))))))
+ (cond ((and left right)
+ (if (not (ztree-diff-node-different node))
+ (funcall open-f left)
+ (if hard
+ (ediff left right)
+ (ztree-diff-simple-diff node))))
+ (left (funcall open-f left))
+ (right (funcall open-f right))
+ (t nil))))
+
+
+
+(defun ztree-diff-copy-file (node source-path destination-path copy-to-right)
+ "Update the NODE status and copy the file.
+File copied from SOURCE-PATH to DESTINATION-PATH.
+COPY-TO-RIGHT specifies which side of the NODE to update."
+ (let ((target-path (concat
+ (file-name-as-directory destination-path)
+ (file-name-nondirectory
+ (directory-file-name source-path)))))
+ (let ((err (condition-case error-trap
+ (progn
+ ;; don't ask for overwrite
+ ;; keep time stamp
+ (copy-file source-path target-path t t)
+ nil)
+ (error error-trap))))
+ ;; error message if failed
+ (if err (message (concat "Error: " (nth 2 err)))
+ (progn ; otherwise:
+ ;; assuming all went ok when left and right nodes are the same
+ ;; set both as not different
+ (ztree-diff-node-set-different node nil)
+ ;; update left/right paths
+ (if copy-to-right
+ (ztree-diff-node-set-right-path node target-path)
+ (ztree-diff-node-set-left-path node target-path))
+ (ztree-diff-node-update-all-parents-diff node)
+ (ztree-refresh-buffer (line-number-at-pos)))))))
+
+
+(defun ztree-diff-copy-dir (node source-path destination-path copy-to-right)
+ "Update the NODE status and copy the directory.
+Directory copied from SOURCE-PATH to DESTINATION-PATH.
+COPY-TO-RIGHT specifies which side of the NODE to update."
+ (let* ((src-path (file-name-as-directory source-path))
+ (target-path (file-name-as-directory destination-path))
+ (target-full-path (concat
+ target-path
+ (file-name-nondirectory
+ (directory-file-name source-path)))))
+ (let ((err (condition-case error-trap
+ (progn
+ ;; keep time stamp
+ ;; ask for overwrite
+ (copy-directory src-path target-path t t)
+ nil)
+ (error error-trap))))
+ ;; error message if failed
+ (if err (message (concat "Error: " (nth 1 err)))
+ (progn
+ (message target-full-path)
+ (if copy-to-right
+ (ztree-diff-node-set-right-path node
+ target-full-path)
+ (ztree-diff-node-set-left-path node
+ target-full-path))
+ (ztree-diff-model-update-node node)
+ (ztree-diff-node-update-all-parents-diff node)
+ (ztree-refresh-buffer (line-number-at-pos)))))))
+
+
+(defun ztree-diff-copy ()
+ "Copy the file under the cursor to other side."
+ (interactive)
+ (let ((found (ztree-find-node-at-point)))
+ (when found
+ (let* ((node (car found))
+ (side (cdr found))
+ (node-side (ztree-diff-node-side node))
+ (copy-to-right t) ; copy from left to right
+ (node-left (ztree-diff-node-left-path node))
+ (node-right (ztree-diff-node-right-path node))
+ (source-path nil)
+ (destination-path nil)
+ (parent (ztree-diff-node-parent node)))
+ (when parent ; do not copy the root node
+ ;; determine a side to copy from/to
+ ;; algorithm:
+ ;; 1) if both side are present, use the side
+ ;; variable
+ (setq copy-to-right (if (eq node-side 'both)
+ (eq side 'left)
+ ;; 2) if one of sides is absent, copy from
+ ;; the side where the file is present
+ (eq node-side 'left)))
+ ;; 3) in both cases determine if the destination
+ ;; directory is in place
+ (setq source-path (if copy-to-right node-left node-right)
+ destination-path (if copy-to-right
+ (ztree-diff-node-right-path parent)
+ (ztree-diff-node-left-path parent)))
+ (when (and source-path destination-path
+ (yes-or-no-p (format "Copy [%s]%s to [%s]%s/ ?"
+ (if copy-to-right "LEFT" "RIGHT")
+ (ztree-diff-node-short-name node)
+ (if copy-to-right "RIGHT" "LEFT")
+ destination-path)))
+ (if (file-directory-p source-path)
+ (ztree-diff-copy-dir node
+ source-path
+ destination-path
+ copy-to-right)
+ (ztree-diff-copy-file node
+ source-path
+ destination-path
+ copy-to-right))))))))
+
+(defun ztree-diff-view-file ()
+ "View file at point, depending on side."
+ (interactive)
+ (let ((found (ztree-find-node-at-point)))
+ (when found
+ (let* ((node (car found))
+ (side (cdr found))
+ (node-side (ztree-diff-node-side node))
+ (node-left (ztree-diff-node-left-path node))
+ (node-right (ztree-diff-node-right-path node)))
+ (when (or (eq node-side 'both)
+ (eq side node-side))
+ (cond ((and (eq side 'left)
+ node-left)
+ (view-file node-left))
+ ((and (eq side 'right)
+ node-right)
+ (view-file node-right))))))))
+
+
+(defun ztree-diff-delete-file ()
+ "Delete the file under the cursor."
+ (interactive)
+ (let ((found (ztree-find-node-at-point)))
+ (when found
+ (let* ((node (car found))
+ (side (cdr found))
+ (node-side (ztree-diff-node-side node))
+ (delete-from-left t)
+ (remove-path nil)
+ (parent (ztree-diff-node-parent node)))
+ (when parent ; do not delete the root node
+ ;; algorithm for determining what to delete similar to copy:
+ ;; 1. if the file is present on both sides, delete
+ ;; from the side currently selected
+ (setq delete-from-left (if (eq node-side 'both)
+ (eq side 'left)
+ ;; 2) if one of sides is absent, delete
+ ;; from the side where the file is present
+ (eq node-side 'left)))
+ (setq remove-path (if delete-from-left
+ (ztree-diff-node-left-path node)
+ (ztree-diff-node-right-path node)))
+ (when (yes-or-no-p (format "Delete the file [%s]%s ?"
+ (if delete-from-left "LEFT" "RIGHT")
+ remove-path))
+ (let* ((delete-command
+ (if (file-directory-p remove-path)
+ '(delete-directory remove-path t)
+ '(delete-file remove-path t)))
+ (children (ztree-diff-node-children parent))
+ (err
+ (condition-case error-trap
+ (progn
+ (eval delete-command)
+ nil)
+ (error error-trap))))
+ (if err (message (concat "Error: " (nth 2 err)))
+ (progn
+ (setq children (ztree-filter
+ #'(lambda (x) (not (ztree-diff-node-equal x node)))
+ children))
+ (ztree-diff-node-set-children parent children))
+ (ztree-diff-node-update-all-parents-diff node)
+ (ztree-refresh-buffer (line-number-at-pos))))))))))
+
+
+
+(defun ztree-node-is-in-filter-list (node)
+ "Determine if the NODE is in filter list.
+If the node is in the filter list it shall not be visible"
+ (ztree-find ztree-diff-filter-list #'(lambda (rx) (string-match rx node))))
+
+
+(defun ztree-node-is-visible (node)
+ "Determine if the NODE should be visible."
+ (and (ztree-diff-node-parent node) ; parent is always visible
+ (not (ztree-node-is-in-filter-list (ztree-diff-node-short-name node)))
+ (or ztree-diff-show-equal-files
+ (ztree-diff-node-different node))))
+
+(defun ztree-diff-toggle-show-equal-files ()
+ "Toggle visibility of the equal files."
+ (interactive)
+ (setq ztree-diff-show-equal-files (not ztree-diff-show-equal-files))
+ (ztree-refresh-buffer))
+
+;;;###autoload
+(defun ztree-diff (dir1 dir2)
+ "Create an interactive buffer with the directory tree of the path given.
+Argument DIR1 left directory.
+Argument DIR2 right directory."
+ (interactive "DLeft directory \nDRight directory ")
+ (let* ((difference (ztree-diff-model-create dir1 dir2))
+ (buf-name (concat "*"
+ (ztree-diff-node-short-name difference)
+ " <--> "
+ (ztree-diff-node-right-short-name difference)
+ "*")))
+ (ztree-view buf-name
+ difference
+ 'ztree-node-is-visible
+ 'ztree-diff-insert-buffer-header
+ 'ztree-diff-node-short-name-wrapper
+ 'ztree-diff-node-is-directory
+ 'ztree-diff-node-equal
+ 'ztree-diff-node-children
+ 'ztree-diff-node-face
+ 'ztree-diff-node-action
+ 'ztree-diff-node-side)
+ (ztreediff-mode)
+ (setq ztree-diff-dirs-pair (cons dir1 dir2))
+ (ztree-refresh-buffer)))
+
+
+
+
+(provide 'ztree-diff)
+;;; ztree-diff.el ends here
--- /dev/null
+;;; ztree-dir.el --- Text mode directory tree
+
+;; Copyright (C) 2013-2015 Free Software Foundation, Inc.
+;;
+;; Author: Alexey Veretennikov <alexey dot veretennikov at gmail dot com>
+;;
+;; Created: 2013-11-1l
+;;
+;; Keywords: files tools
+;; URL: https://github.com/fourier/ztree
+;; Compatibility: GNU Emacs GNU Emacs 24.x
+;;
+;; This file is part of GNU Emacs.
+;;
+;; GNU Emacs 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 3 of the License, or
+;; (at your option) any later version.
+;;
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
+;;
+;;; Commentary:
+;;
+;; Add the following to your .emacs file:
+;;
+;; (push (substitute-in-file-name "path-to-ztree-directory") load-path)
+;; (require 'ztree-dir)
+;;
+;; Call the ztree interactive function:
+;; M-x ztree-dir
+;; Open/close directories with double-click, Enter or Space keys
+;;
+;;; Issues:
+;;
+;;; TODO:
+;; 1) Add some file-handling and marking abilities
+;;
+;;; Code:
+
+(require 'ztree-util)
+(require 'ztree-view)
+
+;;
+;; Constants
+;;
+
+(defconst ztree-hidden-files-regexp "^\\."
+ "Hidden files regexp.
+By default all filest starting with dot '.', including . and ..")
+
+
+;;
+;; Faces
+;;
+
+(defface ztreep-header-face
+ '((((type tty pc) (class color)) :foreground "lightblue" :weight bold)
+ (((background dark)) (:height 1.2 :foreground "lightblue" :weight bold))
+ (t :height 1.2 :foreground "darkblue" :weight bold))
+ "*Face used for the header in Ztree buffer."
+ :group 'Ztree :group 'font-lock-highlighting-faces)
+(defvar ztreep-header-face 'ztreep-header-face)
+
+
+;;
+;; File bindings to the directory tree control
+;;
+
+(defun ztree-insert-buffer-header ()
+ "Insert the header to the ztree buffer."
+ (let ((start (point)))
+ (insert "Directory tree")
+ (newline-and-begin)
+ (insert "==============")
+ (set-text-properties start (point) '(face ztreep-header-face)))
+ (newline-and-begin))
+
+(defun ztree-file-not-hidden (filename)
+ "Determines if the file with FILENAME should be visible."
+ (not (string-match ztree-hidden-files-regexp
+ (file-short-name filename))))
+
+(defun ztree-find-file (node hard)
+ "Find the file at NODE.
+
+If HARD is non-nil, the file is opened in another window.
+Otherwise, the ztree window is used to find the file."
+ (when (and (stringp node) (file-readable-p node))
+ (if hard
+ (save-selected-window (find-file-other-window node))
+ (find-file node))))
+
+;;;###autoload
+(defun ztree-dir (path)
+ "Create an interactive buffer with the directory tree of the PATH given."
+ (interactive "DDirectory: ")
+ (when (and (file-exists-p path) (file-directory-p path))
+ (let ((buf-name (concat "*Directory " path " tree*")))
+ (ztree-view buf-name
+ (expand-file-name (substitute-in-file-name path))
+ 'ztree-file-not-hidden
+ 'ztree-insert-buffer-header
+ 'file-short-name
+ 'file-directory-p
+ 'string-equal
+ '(lambda (x) (directory-files x 'full))
+ nil ; face
+ 'ztree-find-file)))) ; action
+
+
+(provide 'ztree-dir)
+;;; ztree-dir.el ends here
--- /dev/null
+;;; ztree-pkg.el --- Package file for MELPA/ELPA
+(define-package "ztree" "1.0.1" "Several text-tree applications" :url "https://github.com/fourier/ztree" :keywords '("files" "tools"))
+
+;;; ztree-pkg.el ends here
--- /dev/null
+;;; ztree-util.el --- Auxulary utilities for the ztree package
+
+;; Copyright (C) 2013-2015 Free Software Foundation, Inc.
+;;
+;; Author: Alexey Veretennikov <alexey dot veretennikov at gmail dot com>
+;;
+;; Created: 2013-11-1l
+;;
+;; Keywords: files tools
+;; URL: https://github.com/fourier/ztree
+;; Compatibility: GNU Emacs GNU Emacs 24.x
+;;
+;; This file is part of GNU Emacs.
+;;
+;; GNU Emacs 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 3 of the License, or
+;; (at your option) any later version.
+;;
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
+;;
+;;; Commentary:
+
+;;; Code:
+(defun ztree-find (where which)
+ "Find element of the list WHERE matching predicate WHICH."
+ (catch 'found
+ (dolist (elt where)
+ (when (funcall which elt)
+ (throw 'found elt)))
+ nil))
+
+(defun ztree-filter (condp lst)
+ "Filter out elements not satisfying predicate CONDP in the list LST.
+Taken from http://www.emacswiki.org/emacs/ElispCookbook#toc39"
+ (delq nil
+ (mapcar (lambda (x) (and (funcall condp x) x)) lst)))
+
+
+(defun printable-string (string)
+ "Strip newline character from file names, like 'Icon\n.
+Argument STRING string to process.'."
+ (replace-regexp-in-string "\n" "" string))
+
+(defun file-short-name (file)
+ "By given FILE name return base file/directory name.
+Taken from http://lists.gnu.org/archive/html/emacs-devel/2011-01/msg01238.html"
+ (printable-string (file-name-nondirectory (directory-file-name file))))
+
+
+(defun newline-and-begin ()
+ "Move a point to the beginning of the next line."
+ (newline)
+ (beginning-of-line))
+
+(defun car-atom (value)
+ "Return VALUE if value is an atom, otherwise (car value) or nil.
+Used since `car-safe' returns nil for atoms"
+ (if (atom value) value (car value)))
+
+
+(defun insert-with-face (text face)
+ "Insert TEXT with the FACE provided."
+ (let ((start (point)))
+ (insert text)
+ (put-text-property start (point) 'face face)))
+
+
+(defmacro defrecord (record-name record-fields)
+ "Create a record (structure) and getters/setters.
+
+Record is the following set of functions:
+ - Record constructor with name \"RECORD-NAME\"-create and list of
+arguments which will be assigned to RECORD-FIELDS
+ - Record getters with names \"record-name\"-\"field\" accepting one
+argument - the record; \"field\" is from \"record-fields\" symbols
+ - Record setters with names \"record-name\"-set-\"field\" accepting two
+arguments - the record and the field value
+
+Example:
+\(defrecord person (name age))
+
+will be expanded to the following functions:
+
+\(defun person-create (name age) (...)
+\(defun person-name (record) (...)
+\(defun person-age (record) (...)
+\(defun person-set-name (record value) (...)
+\(defun person-set-age (record value) (...)"
+ (let ((ctor-name (intern (concat (symbol-name record-name) "-create")))
+ (rec-var (make-symbol "record")))
+ `(progn
+ ;; constructor with the name "record-name-create"
+ ;; with arguments list "record-fields" expanded
+ (defun ,ctor-name (,@record-fields)
+ (let ((,rec-var))
+ ,@(mapcar #'(lambda (x)
+ (list 'setq rec-var (list 'plist-put rec-var (list 'quote x) x)))
+ record-fields)))
+ ;; getters with names "record-name-field" where the "field"
+ ;; is from record-fields
+ ,@(mapcar #'(lambda (x)
+ (let ((getter-name (intern (concat (symbol-name record-name)
+ "-"
+ (symbol-name x)))))
+ `(progn
+ (defun ,getter-name (,rec-var)
+ (plist-get ,rec-var ',x)
+ ))))
+ record-fields)
+ ;; setters wit names "record-name-set-field where the "field"
+ ;; is from record-fields
+ ;; arguments for setters: (record value)
+ ,@(mapcar #'(lambda (x)
+ (let ((setter-name (intern (concat (symbol-name record-name)
+ "-set-"
+ (symbol-name x)))))
+ `(progn
+ (defun ,setter-name (,rec-var value)
+ (setq ,rec-var (plist-put ,rec-var ',x value))
+ ))))
+ record-fields))))
+
+
+(provide 'ztree-util)
+
+;;; ztree-util.el ends here
--- /dev/null
+;;; ztree-view.el --- Text mode tree view (buffer)
+
+;; Copyright (C) 2013-2015 Free Software Foundation, Inc.
+;;
+;; Author: Alexey Veretennikov <alexey dot veretennikov at gmail dot com>
+;;
+;; Created: 2013-11-1l
+;;
+;; Keywords: files tools
+;; URL: https://github.com/fourier/ztree
+;; Compatibility: GNU Emacs GNU Emacs 24.x
+;;
+;; This file is part of GNU Emacs.
+;;
+;; GNU Emacs 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 3 of the License, or
+;; (at your option) any later version.
+;;
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
+;;
+;;; Commentary:
+;;
+;; Add the following to your .emacs file:
+;;
+;; (push (substitute-in-file-name "path-to-ztree-directory") load-path)
+;; (require 'ztree-view)
+;;
+;; Call the ztree interactive function:
+;; Use the following function: ztree-view
+;;
+;;; Issues:
+;;
+;;; TODO:
+;;
+;;
+;;; Code:
+
+(require 'ztree-util)
+
+;;
+;; Globals
+;;
+
+(defvar ztree-expanded-nodes-list nil
+ "A list of Expanded nodes (i.e. directories) entries.")
+(make-variable-buffer-local 'ztree-expanded-nodes-list)
+
+(defvar ztree-start-node nil
+ "Start node(i.e. directory) for the window.")
+(make-variable-buffer-local 'ztree-start-node)
+
+(defvar ztree-line-to-node-table nil
+ "List of tuples with full node(i.e. file/directory name and the line.")
+(make-variable-buffer-local 'ztree-line-to-node-table)
+
+(defvar ztree-start-line nil
+ "Index of the start line - the root.")
+(make-variable-buffer-local 'ztree-start-line)
+
+(defvar ztree-parent-lines-array nil
+ "Array of parent lines.
+The ith value of the array is the parent line for line i.
+If ith value is i - it is the root line")
+(make-variable-buffer-local 'ztree-parent-lines-array)
+
+(defvar ztree-count-subsequent-bs nil
+ "Counter for the subsequest BS keys (to identify double BS).
+Used in order to not to use cl package and `lexical-let'")
+(make-variable-buffer-local 'ztree-count-subsequent-bs)
+
+(defvar ztree-line-tree-properties nil
+ "Hash with key - line number, value - property ('left, 'right, 'both).
+Used for 2-side trees, to determine if the node exists on left or right
+or both sides")
+(make-variable-buffer-local 'ztree-line-tree-properties)
+
+(defvar ztree-tree-header-fun nil
+ "Function inserting the header into the tree buffer.
+MUST inster newline at the end!")
+(make-variable-buffer-local 'ztree-tree-header-fun)
+
+(defvar ztree-node-short-name-fun nil
+ "Function which creates a pretty-printable short string from the node.")
+(make-variable-buffer-local 'ztree-node-short-name-fun)
+
+(defvar ztree-node-is-expandable-fun nil
+ "Function which determines if the node is expandable.
+For example if the node is a directory")
+(make-variable-buffer-local 'ztree-node-is-expandable-fun)
+
+(defvar ztree-node-equal-fun nil
+ "Function which determines if the 2 nodes are equal.")
+(make-variable-buffer-local 'ztree-node-equal-fun)
+
+(defvar ztree-node-contents-fun nil
+ "Function returning list of node contents.")
+(make-variable-buffer-local 'ztree-node-contents-fun)
+
+(defvar ztree-node-side-fun nil
+ "Function returning position of the node: 'left, 'right or 'both.
+If not defined(by default) - using single screen tree, otherwise
+the buffer is split to 2 trees")
+(make-variable-buffer-local 'ztree-node-side-fun)
+
+(defvar ztree-node-face-fun nil
+ "Function returning face for the node.")
+(make-variable-buffer-local 'ztree-node-face-fun)
+
+(defvar ztree-node-action-fun nil
+ "Function called when Enter/Space pressed on the node.")
+(make-variable-buffer-local 'ztree-node-action-fun)
+
+(defvar ztree-node-showp-fun nil
+ "Function called to decide if the node should be visible.")
+(make-variable-buffer-local 'ztree-node-showp-fun)
+
+
+;;
+;; Major mode definitions
+;;
+
+(defvar ztree-mode-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map (kbd "\r") 'ztree-perform-action)
+ (define-key map (kbd "SPC") 'ztree-perform-soft-action)
+ (define-key map [double-mouse-1] 'ztree-perform-action)
+ (define-key map (kbd "TAB") 'ztree-jump-side)
+ (define-key map (kbd "g") 'ztree-refresh-buffer)
+ (define-key map (kbd "x") 'ztree-toggle-expand-subtree)
+ (if window-system
+ (define-key map (kbd "<backspace>") 'ztree-move-up-in-tree)
+ (define-key map "\177" 'ztree-move-up-in-tree))
+ map)
+ "Keymap for `ztree-mode'.")
+
+
+(defface ztreep-node-face
+ '((((background dark)) (:foreground "#ffffff"))
+ (((type nil)) (:inherit 'font-lock-function-name-face))
+ (t (:foreground "Blue")))
+ "*Face used for expandable entries(directories etc) in Ztree buffer."
+ :group 'Ztree :group 'font-lock-highlighting-faces)
+(defvar ztreep-node-face 'ztreep-node-face)
+
+(defface ztreep-leaf-face
+ '((((background dark)) (:foreground "cyan1"))
+ (((type nil)) (:inherit 'font-lock-variable-name-face))
+ (t (:foreground "darkblue")))
+ "*Face used for not expandable nodes(leafs, i.e. files) in Ztree buffer."
+ :group 'Ztree :group 'font-lock-highlighting-faces)
+(defvar ztreep-leaf-face 'ztreep-leaf-face)
+
+(defface ztreep-arrow-face
+ '((((background dark)) (:foreground "#7f7f7f"))
+ (t (:foreground "#8d8d8d")))
+ "*Face used for arrows in Ztree buffer."
+ :group 'Ztree :group 'font-lock-highlighting-faces)
+(defvar ztreep-arrow-face 'ztreep-arrow-face)
+
+(defface ztreep-expand-sign-face
+ '((((background dark)) (:foreground "#7f7fff"))
+ (t (:foreground "#8d8d8d")))
+ "*Face used for expand sign [+] in Ztree buffer."
+ :group 'Ztree :group 'font-lock-highlighting-faces)
+(defvar ztreep-expand-sign-face 'ztreep-expand-sign-face)
+
+
+;;;###autoload
+(define-derived-mode ztree-mode special-mode "Ztree"
+ "A major mode for displaying the directory tree in text mode."
+ ;; only spaces
+ (setq indent-tabs-mode nil)
+ ;; fix for electric-indent-mode
+ ;; for emacs 24.4
+ (if (fboundp 'electric-indent-local-mode)
+ (electric-indent-local-mode -1)
+ ;; for emacs 24.3 or less
+ (add-hook 'electric-indent-functions
+ (lambda (arg) 'no-indent) nil 'local)))
+
+
+(defun ztree-find-node-in-line (line)
+ "Return the node for the LINE specified.
+Search through the array of node-line pairs."
+ (gethash line ztree-line-to-node-table))
+
+(defun ztree-find-node-at-point ()
+ "Find the node at point.
+Returns cons pair (node, side) for the current point
+or nil if there is no node"
+ (let ((center (/ (window-width) 2))
+ (node (ztree-find-node-in-line (line-number-at-pos))))
+ (when node
+ (cons node (if (> (current-column) center) 'right 'left)))))
+
+
+(defun ztree-is-expanded-node (node)
+ "Find if the NODE is in the list of expanded nodes."
+ (ztree-find ztree-expanded-nodes-list
+ #'(lambda (x) (funcall ztree-node-equal-fun x node))))
+
+
+(defun ztree-set-parent-for-line (line parent)
+ "For given LINE set the PARENT in the global array."
+ (aset ztree-parent-lines-array (- line ztree-start-line) parent))
+
+(defun ztree-get-parent-for-line (line)
+ "For given LINE return a parent."
+ (when (and (>= line ztree-start-line)
+ (< line (+ (length ztree-parent-lines-array) ztree-start-line)))
+ (aref ztree-parent-lines-array (- line ztree-start-line))))
+
+(defun scroll-to-line (line)
+ "Recommended way to set the cursor to specified LINE."
+ (goto-char (point-min))
+ (forward-line (1- line)))
+
+
+(defun ztree-do-toggle-expand-subtree-iter (node state)
+ "Iteration in expanding subtree.
+Argument NODE current node.
+Argument STATE node state."
+ (when (funcall ztree-node-is-expandable-fun node)
+ (let ((children (funcall ztree-node-contents-fun node)))
+ (ztree-do-toggle-expand-state node state)
+ (dolist (child children)
+ (ztree-do-toggle-expand-subtree-iter child state)))))
+
+
+(defun ztree-do-toggle-expand-subtree ()
+ "Implements the subtree expand."
+ (let* ((line (line-number-at-pos))
+ (node (ztree-find-node-in-line line))
+ ;; save the current window start position
+ (current-pos (window-start)))
+ ;; only for expandable nodes
+ (when (funcall ztree-node-is-expandable-fun node)
+ ;; get the current expand state and invert it
+ (let ((do-expand (not (ztree-is-expanded-node node))))
+ (ztree-do-toggle-expand-subtree-iter node do-expand))
+ ;; refresh buffer and scroll back to the saved line
+ (ztree-refresh-buffer line)
+ ;; restore window start position
+ (set-window-start (selected-window) current-pos))))
+
+
+(defun ztree-do-perform-action (hard)
+ "Toggle expand/collapsed state for nodes or perform an action.
+HARD specifies (t or nil) if the hard action, binded on RET,
+should be performed on node."
+ (let* ((line (line-number-at-pos))
+ (node (ztree-find-node-in-line line)))
+ (when node
+ (if (funcall ztree-node-is-expandable-fun node)
+ ;; only for expandable nodes
+ (ztree-toggle-expand-state node)
+ ;; perform action
+ (when ztree-node-action-fun
+ (funcall ztree-node-action-fun node hard)))
+ ;; save the current window start position
+ (let ((current-pos (window-start)))
+ ;; refresh buffer and scroll back to the saved line
+ (ztree-refresh-buffer line)
+ ;; restore window start position
+ (set-window-start (selected-window) current-pos)))))
+
+
+(defun ztree-perform-action ()
+ "Toggle expand/collapsed state for nodes or perform the action.
+Performs the hard action, binded on RET, on node."
+ (interactive)
+ (ztree-do-perform-action t))
+
+(defun ztree-perform-soft-action ()
+ "Toggle expand/collapsed state for nodes or perform the action.
+Performs the soft action, binded on Space, on node."
+ (interactive)
+ (ztree-do-perform-action nil))
+
+
+(defun ztree-toggle-expand-subtree()
+ "Toggle Expanded/Collapsed state on all nodes of the subtree"
+ (interactive)
+ (ztree-do-toggle-expand-subtree))
+
+(defun ztree-do-toggle-expand-state (node do-expand)
+ "Set the expanded state of the NODE to DO-EXPAND."
+ (if (not do-expand)
+ (setq ztree-expanded-nodes-list
+ (ztree-filter
+ #'(lambda (x) (not (funcall ztree-node-equal-fun node x)))
+ ztree-expanded-nodes-list))
+ (push node ztree-expanded-nodes-list)))
+
+
+(defun ztree-toggle-expand-state (node)
+ "Toggle expanded/collapsed state for NODE."
+ (ztree-do-toggle-expand-state node (not (ztree-is-expanded-node node))))
+
+
+(defun ztree-move-up-in-tree ()
+ "Action on Backspace key.
+Jump to the line of a parent node. If previous key was Backspace
+then close the node."
+ (interactive)
+ (when ztree-parent-lines-array
+ (let* ((line (line-number-at-pos (point)))
+ (parent (ztree-get-parent-for-line line)))
+ (when parent
+ (if (and (equal last-command 'ztree-move-up-in-tree)
+ (not ztree-count-subsequent-bs))
+ (let ((node (ztree-find-node-in-line line)))
+ (when (ztree-is-expanded-node node)
+ (ztree-toggle-expand-state node))
+ (setq ztree-count-subsequent-bs t)
+ (ztree-refresh-buffer line))
+ (progn (setq ztree-count-subsequent-bs nil)
+ (scroll-to-line parent)))))))
+
+
+(defun ztree-get-splitted-node-contens (node)
+ "Return pair of 2 elements: list of expandable nodes and list of leafs.
+Argument NODE node which contents will be returned."
+ (let ((nodes (funcall ztree-node-contents-fun node))
+ (comp #'(lambda (x y)
+ (string< (funcall ztree-node-short-name-fun x)
+ (funcall ztree-node-short-name-fun y)))))
+ (cons (sort (ztree-filter
+ #'(lambda (f) (funcall ztree-node-is-expandable-fun f))
+ nodes) comp)
+ (sort (ztree-filter
+ #'(lambda (f) (not (funcall ztree-node-is-expandable-fun f)))
+ nodes) comp))))
+
+
+(defun ztree-draw-char (c x y &optional face)
+ "Draw char C at the position (1-based) (X Y).
+Optional argument FACE face to use to draw a character."
+ (save-excursion
+ (scroll-to-line y)
+ (beginning-of-line)
+ (goto-char (+ x (-(point) 1)))
+ (delete-char 1)
+ (insert-char c 1)
+ (put-text-property (1- (point)) (point) 'face (if face face 'ztreep-arrow-face))))
+
+(defun ztree-draw-vertical-line (y1 y2 x &optional face)
+ "Draw a vertical line of '|' characters from Y1 row to Y2 in X column.
+Optional argument FACE face to draw line with."
+ (let ((count (abs (- y1 y2))))
+ (if (> y1 y2)
+ (progn
+ (dotimes (y count)
+ (ztree-draw-char ?\| x (+ y2 y) face))
+ (ztree-draw-char ?\| x (+ y2 count) face))
+ (progn
+ (dotimes (y count)
+ (ztree-draw-char ?\| x (+ y1 y) face))
+ (ztree-draw-char ?\| x (+ y1 count) face)))))
+
+(defun ztree-draw-vertical-rounded-line (y1 y2 x &optional face)
+ "Draw a vertical line of '|' characters finishing with '`' character.
+Draws the line from Y1 row to Y2 in X column.
+Optional argument FACE facet to draw the line with."
+ (let ((count (abs (- y1 y2))))
+ (if (> y1 y2)
+ (progn
+ (dotimes (y count)
+ (ztree-draw-char ?\| x (+ y2 y) face))
+ (ztree-draw-char ?\` x (+ y2 count) face))
+ (progn
+ (dotimes (y count)
+ (ztree-draw-char ?\| x (+ y1 y) face))
+ (ztree-draw-char ?\` x (+ y1 count) face)))))
+
+
+(defun ztree-draw-horizontal-line (x1 x2 y)
+ "Draw the horizontal line from column X1 to X2 in the row Y."
+ (if (> x1 x2)
+ (dotimes (x (1+ (- x1 x2)))
+ (ztree-draw-char ?\- (+ x2 x) y))
+ (dotimes (x (1+ (- x2 x1)))
+ (ztree-draw-char ?\- (+ x1 x) y))))
+
+
+(defun ztree-draw-tree (tree depth start-offset)
+ "Draw the TREE of lines with parents.
+Argument DEPTH current depth.
+Argument START-OFFSET column to start drawing from."
+ (if (atom tree)
+ nil
+ (let* ((root (car tree))
+ (children (cdr tree))
+ (offset (+ start-offset (* depth 4)))
+ (line-start (+ 3 offset))
+ (line-end-leaf (+ 7 offset))
+ (line-end-node (+ 4 offset))
+ ;; determine if the line is visible. It is always the case
+ ;; for 1-sided trees; however for 2 sided trees
+ ;; it depends on which side is the actual element
+ ;; and which tree (left with offset 0 or right with offset > 0
+ ;; we are drawing
+ (visible #'(lambda (line) ()
+ (if (not ztree-node-side-fun) t
+ (let ((side
+ (gethash line ztree-line-tree-properties)))
+ (cond ((eq side 'left) (= start-offset 0))
+ ((eq side 'right) (> start-offset 0))
+ (t t)))))))
+ (when children
+ ;; draw the line to the last child
+ ;; since we push'd children to the list, it's the first visible line
+ ;; from the children list
+ (let ((last-child (ztree-find children
+ #'(lambda (x)
+ (funcall visible (car-atom x)))))
+ (x-offset (+ 2 offset)))
+ (when last-child
+ (ztree-draw-vertical-rounded-line (1+ root)
+ (car-atom last-child)
+ x-offset)))
+ ;; draw recursively
+ (dolist (child children)
+ (ztree-draw-tree child (1+ depth) start-offset)
+ (let ((end (if (listp child) line-end-node line-end-leaf)))
+ (when (funcall visible (car-atom child))
+ (ztree-draw-horizontal-line line-start
+ end
+ (car-atom child)))))))))
+
+(defun ztree-fill-parent-array (tree)
+ "Set the root lines array.
+Argument TREE nodes tree to create an array of lines from."
+ (let ((root (car tree))
+ (children (cdr tree)))
+ (dolist (child children)
+ (ztree-set-parent-for-line (car-atom child) root)
+ (when (listp child)
+ (ztree-fill-parent-array child)))))
+
+
+(defun ztree-insert-node-contents (path)
+ "Insert node contents with initial depth 0.
+`ztree-insert-node-contents-1' return the tree of line
+numbers to determine who is parent line of the
+particular line. This tree is used to draw the
+graph.
+Argument PATH start node."
+ (let ((tree (ztree-insert-node-contents-1 path 0))
+ ;; number of 'rows' in tree is last line minus start line
+ (num-of-items (- (line-number-at-pos (point)) ztree-start-line)))
+ ;; create a parents array to store parents of lines
+ ;; parents array used for navigation with the BS
+ (setq ztree-parent-lines-array (make-vector num-of-items 0))
+ ;; set the root node in lines parents array
+ (ztree-set-parent-for-line ztree-start-line ztree-start-line)
+ ;; fill the parent arrray from the tree
+ (ztree-fill-parent-array tree)
+ ;; draw the tree starting with depth 0 and offset 0
+ (ztree-draw-tree tree 0 0)
+ ;; for the 2-sided tree we need to draw the vertical line
+ ;; and an additional tree
+ (if ztree-node-side-fun ; 2-sided tree
+ (let ((width (window-width)))
+ ;; draw the vertical line in the middle of the window
+ (ztree-draw-vertical-line ztree-start-line
+ (1- (+ num-of-items ztree-start-line))
+ (/ width 2)
+ 'vertical-border)
+ (ztree-draw-tree tree 0 (1+ (/ width 2)))))))
+
+
+(defun ztree-insert-node-contents-1 (node depth)
+ "Recursively insert contents of the NODE with current DEPTH."
+ (let* ((expanded (ztree-is-expanded-node node))
+ ;; insert node entry with defined depth
+ (root-line (ztree-insert-entry node depth expanded))
+ ;; children list is the list of lines which are children
+ ;; of the root line
+ (children nil))
+ (when expanded ;; if expanded we need to add all subnodes
+ (let* ((contents (ztree-get-splitted-node-contens node))
+ ;; contents is the list of 2 elements:
+ (nodes (car contents)) ; expandable entries - nodes
+ (leafs (cdr contents))) ; leafs - which doesn't have subleafs
+ ;; iterate through all expandable entries to insert them first
+ (dolist (node nodes)
+ ;; if it is not in the filter list
+ (when (funcall ztree-node-showp-fun node)
+ ;; insert node on the next depth level
+ ;; and push the returning result (in form (root children))
+ ;; to the children list
+ (push (ztree-insert-node-contents-1 node (1+ depth))
+ children)))
+ ;; now iterate through all the leafs
+ (dolist (leaf leafs)
+ ;; if not in filter list
+ (when (funcall ztree-node-showp-fun leaf)
+ ;; insert the leaf and add it to children
+ (push (ztree-insert-entry leaf (1+ depth) nil)
+ children)))))
+ ;; result value is the list - head is the root line,
+ ;; rest are children
+ (cons root-line children)))
+
+(defun ztree-insert-entry (node depth expanded)
+ "Inselt the NODE to the current line with specified DEPTH and EXPANDED state."
+ (let ((line (line-number-at-pos))
+ (expandable (funcall ztree-node-is-expandable-fun node))
+ (short-name (funcall ztree-node-short-name-fun node)))
+ (if ztree-node-side-fun ; 2-sided tree
+ (let ((right-short-name (funcall ztree-node-short-name-fun node t))
+ (side (funcall ztree-node-side-fun node))
+ (width (window-width)))
+ (when (eq side 'left) (setq right-short-name ""))
+ (when (eq side 'right) (setq short-name ""))
+ (ztree-insert-single-entry short-name depth
+ expandable expanded 0
+ (when ztree-node-face-fun
+ (funcall ztree-node-face-fun node)))
+ (ztree-insert-single-entry right-short-name depth
+ expandable expanded (1+ (/ width 2))
+ (when ztree-node-face-fun
+ (funcall ztree-node-face-fun node)))
+ (puthash line side ztree-line-tree-properties))
+ (ztree-insert-single-entry short-name depth expandable expanded 0))
+ (puthash line node ztree-line-to-node-table)
+ (newline-and-begin)
+ line))
+
+(defun ztree-insert-single-entry (short-name depth
+ expandable expanded
+ offset
+ &optional face)
+ "Writes a SHORT-NAME in a proper position with the type given.
+Writes a string with given DEPTH, prefixed with [ ] if EXPANDABLE
+and [-] or [+] depending on if it is EXPANDED from the specified OFFSET.
+Optional argument FACE face to write text with."
+ (let ((node-sign #'(lambda (exp)
+ (insert "[" (if exp "-" "+") "]")
+ (set-text-properties (- (point) 3)
+ (point)
+ '(face ztreep-expand-sign-face)))))
+ (move-to-column offset t)
+ (delete-region (point) (line-end-position))
+ (when (> depth 0)
+ (dotimes (i depth)
+ (insert " ")
+ (insert-char ?\s 3))) ; insert 3 spaces
+ (when (> (length short-name) 0)
+ (if expandable
+ (progn
+ (funcall node-sign expanded) ; for expandable nodes insert "[+/-]"
+ (insert " ")
+ (put-text-property 0 (length short-name)
+ 'face (if face face 'ztreep-node-face) short-name)
+ (insert short-name))
+ (progn
+ (insert " ")
+ (put-text-property 0 (length short-name)
+ 'face (if face face 'ztreep-leaf-face) short-name)
+ (insert short-name))))))
+
+(defun ztree-jump-side ()
+ "Jump to another side for 2-sided trees."
+ (interactive)
+ (when ztree-node-side-fun ; 2-sided tree
+ (let ((center (/ (window-width) 2)))
+ (cond ((< (current-column) center)
+ (move-to-column (1+ center)))
+ ((> (current-column) center)
+ (move-to-column 1))
+ (t nil)))))
+
+
+
+(defun ztree-refresh-buffer (&optional line)
+ "Refresh the buffer.
+Optional argument LINE scroll to the line given."
+ (interactive)
+ (when (and (equal major-mode 'ztree-mode)
+ (boundp 'ztree-start-node))
+ (setq ztree-line-to-node-table (make-hash-table))
+ ;; create a hash table of node properties for line
+ ;; used in 2-side tree mode
+ (when ztree-node-side-fun
+ (setq ztree-line-tree-properties (make-hash-table)))
+ (toggle-read-only)
+ (erase-buffer)
+ (funcall ztree-tree-header-fun)
+ (setq ztree-start-line (line-number-at-pos (point)))
+ (ztree-insert-node-contents ztree-start-node)
+ (scroll-to-line (if line line ztree-start-line))
+ (toggle-read-only)))
+
+
+(defun ztree-view (
+ buffer-name
+ start-node
+ filter-fun
+ header-fun
+ short-name-fun
+ expandable-p
+ equal-fun
+ children-fun
+ face-fun
+ action-fun
+ &optional node-side-fun
+ )
+ "Create a ztree view buffer configured with parameters given.
+Argument BUFFER-NAME Name of the buffer created.
+Argument START-NODE Starting node - the root of the tree.
+Argument FILTER-FUN Function which will define if the node should not be
+visible.
+Argument HEADER-FUN Function which inserts the header into the buffer
+before drawing the tree.
+Argument SHORT-NAME-FUN Function which return the short name for a node given.
+Argument EXPANDABLE-P Function to determine if the node is expandable.
+Argument EQUAL-FUN An equality function for nodes.
+Argument CHILDREN-FUN Function to get children from the node.
+Argument FACE-FUN Function to determine face of the node.
+Argument ACTION-FUN an action to perform when the Return is pressed.
+Optional argument NODE-SIDE-FUN Determines the side of the node."
+ (let ((buf (get-buffer-create buffer-name)))
+ (switch-to-buffer buf)
+ (ztree-mode)
+ ;; configure ztree-view
+ (setq ztree-start-node start-node)
+ (setq ztree-expanded-nodes-list (list ztree-start-node))
+ (setq ztree-node-showp-fun filter-fun)
+ (setq ztree-tree-header-fun header-fun)
+ (setq ztree-node-short-name-fun short-name-fun)
+ (setq ztree-node-is-expandable-fun expandable-p)
+ (setq ztree-node-equal-fun equal-fun)
+ (setq ztree-node-contents-fun children-fun)
+ (setq ztree-node-face-fun face-fun)
+ (setq ztree-node-action-fun action-fun)
+ (setq ztree-node-side-fun node-side-fun)
+ (ztree-refresh-buffer)))
+
+
+(provide 'ztree-view)
+;;; ztree-view.el ends here