]> code.delx.au - gnu-emacs-elpa/blob - ztree-view.el
Fix variable declarations
[gnu-emacs-elpa] / ztree-view.el
1 ;;; ztree-view.el --- Text mode tree view (buffer)
2
3 ;; Copyright (C) 2013 Alexey Veretennikov
4 ;;
5 ;; Author: Alexey Veretennikov <alexey dot veretennikov at gmail dot com>
6 ;; Created: 2013-11-1l
7 ;; Version: 1.0.1
8 ;; Keywords: files
9 ;; URL: https://github.com/fourier/ztree
10 ;; Compatibility: GNU Emacs GNU Emacs 24.x
11 ;;
12 ;; This file is NOT part of GNU Emacs.
13 ;;
14 ;; This program is free software; you can redistribute it and/or
15 ;; modify it under the terms of the GNU General Public License
16 ;; as published by the Free Software Foundation; either version 2
17 ;; of the License, or (at your option) any later version.
18 ;;
19 ;; This program is distributed in the hope that it will be useful,
20 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
21 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 ;; GNU General Public License for more details.
23 ;;
24 ;; You should have received a copy of the GNU General Public License
25 ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
26 ;;
27 ;;; Commentary:
28 ;;
29 ;; Add the following to your .emacs file:
30 ;;
31 ;; (push (substitute-in-file-name "path-to-ztree-directory") load-path)
32 ;; (require 'ztree-view)
33 ;;
34 ;; Call the ztree interactive function:
35 ;; Use the following function: ztree-view
36 ;;
37 ;;; Issues:
38 ;;
39 ;;; TODO:
40 ;;
41 ;;
42 ;;; Change Log:
43 ;;
44 ;; 2013-11-10 (1.0.0)
45 ;; Initial Release.
46 ;;
47 ;;; Code:
48
49 (require 'ztree-util)
50
51 ;;
52 ;; Globals
53 ;;
54
55 (defvar ztree-expanded-nodes-list nil
56 "A list of Expanded nodes (i.e. directories) entries.")
57 (make-variable-buffer-local 'ztree-expanded-nodes-list)
58
59 (defvar ztree-start-node nil
60 "Start node(i.e. directory) for the window.")
61 (make-variable-buffer-local 'ztree-start-node)
62
63 (defvar ztree-line-to-node-table nil
64 "List of tuples with full node(i.e. file/directory name
65 and the line.")
66 (make-variable-buffer-local 'ztree-line-to-node-table)
67
68 (defvar ztree-start-line nil
69 "Index of the start line - the root")
70 (make-variable-buffer-local 'ztree-start-line)
71
72 (defvar ztree-parent-lines-array nil
73 "Array of parent lines, there the ith value of the array
74 is the parent line for line i. If ith value is i - it is the root
75 line")
76 (make-variable-buffer-local 'ztree-parent-lines-array)
77
78 (defvar ztree-count-subsequent-bs nil
79 "Counter for the subsequest BS keys (to identify double BS). Used
80 in order to not to use cl package and lexical-let")
81 (make-variable-buffer-local 'ztree-count-subsequent-bs)
82
83 (defvar ztree-line-tree-properties nil
84 "Hash with key - line number, value - property ('left, 'right, 'both).
85 Used for 2-side trees, to determine if the node exists on left or right
86 or both sides")
87 (make-variable-buffer-local 'ztree-line-tree-properties)
88
89 (defvar ztree-tree-header-fun nil
90 "Function inserting the header into the tree buffer.
91 MUST inster newline at the end!")
92 (make-variable-buffer-local 'ztree-tree-header-fun)
93
94 (defvar ztree-node-short-name-fun nil
95 "Function which creates a pretty-printable short string from
96 the node")
97 (make-variable-buffer-local 'ztree-node-short-name-fun)
98
99 (defvar ztree-node-is-expandable-fun nil
100 "Function which determines if the node is expandable,
101 for example if the node is a directory")
102 (make-variable-buffer-local 'ztree-node-is-expandable-fun)
103
104 (defvar ztree-node-equal-fun nil
105 "Function which determines if the 2 nodes are equal")
106 (make-variable-buffer-local 'ztree-node-equal-fun)
107
108 (defvar ztree-node-contents-fun nil
109 "Function returning list of node contents")
110 (make-variable-buffer-local 'ztree-node-contents-fun)
111
112 (defvar ztree-node-side-fun nil
113 "Function returning position of the node: 'left, 'right or 'both.
114 If not defined(by default) - using single screen tree, otherwise
115 the buffer is split to 2 trees")
116 (make-variable-buffer-local 'ztree-node-side-fun)
117
118 (defvar ztree-node-face-fun nil
119 "Function returning face for the node")
120 (make-variable-buffer-local 'ztree-node-face-fun)
121
122 (defvar ztree-node-action-fun nil
123 "Function called when Enter/Space pressed on the node")
124 (make-variable-buffer-local 'ztree-node-action-fun)
125
126 (defvar ztree-node-showp-fun nil
127 "Function called to decide if the node should be visible")
128 (make-variable-buffer-local 'ztree-node-showp-fun)
129
130
131 ;;
132 ;; Major mode definitions
133 ;;
134
135 (defvar ztree-mode-map
136 (let ((map (make-sparse-keymap)))
137 (define-key map (kbd "\r") 'ztree-perform-action)
138 (define-key map (kbd "SPC") 'ztree-perform-soft-action)
139 (define-key map [double-mouse-1] 'ztree-perform-action)
140 (define-key map (kbd "TAB") 'ztree-jump-side)
141 (define-key map (kbd "g") 'ztree-refresh-buffer)
142 (define-key map (kbd "x") 'ztree-toggle-expand-subtree)
143 (if window-system
144 (define-key map (kbd "<backspace>") 'ztree-move-up-in-tree)
145 (define-key map "\177" 'ztree-move-up-in-tree))
146 map)
147 "Keymap for `ztree-mode'.")
148
149
150 (defface ztreep-node-face
151 '((((background dark)) (:foreground "#ffffff"))
152 (((type nil)) (:inherit 'font-lock-function-name-face))
153 (t (:foreground "Blue")))
154 "*Face used for expandable entries(directories etc) in Ztree buffer."
155 :group 'Ztree :group 'font-lock-highlighting-faces)
156 (defvar ztreep-node-face 'ztreep-node-face)
157
158 (defface ztreep-leaf-face
159 '((((background dark)) (:foreground "cyan1"))
160 (((type nil)) (:inherit 'font-lock-variable-name-face))
161 (t (:foreground "darkblue")))
162 "*Face used for not expandable nodes(leafs, i.e. files) in Ztree buffer."
163 :group 'Ztree :group 'font-lock-highlighting-faces)
164 (defvar ztreep-leaf-face 'ztreep-leaf-face)
165
166 (defface ztreep-arrow-face
167 '((((background dark)) (:foreground "#7f7f7f"))
168 (t (:foreground "#8d8d8d")))
169 "*Face used for arrows in Ztree buffer."
170 :group 'Ztree :group 'font-lock-highlighting-faces)
171 (defvar ztreep-arrow-face 'ztreep-arrow-face)
172
173 (defface ztreep-expand-sign-face
174 '((((background dark)) (:foreground "#7f7fff"))
175 (t (:foreground "#8d8d8d")))
176 "*Face used for expand sign [+] in Ztree buffer."
177 :group 'Ztree :group 'font-lock-highlighting-faces)
178 (defvar ztreep-expand-sign-face 'ztreep-expand-sign-face)
179
180
181 ;;;###autoload
182 (define-derived-mode ztree-mode special-mode "Ztree"
183 "A major mode for displaying the directory tree in text mode."
184 ;; only spaces
185 (setq indent-tabs-mode nil)
186 ;; fix for electric-indent-mode
187 ;; for emacs 24.4
188 (if (fboundp 'electric-indent-local-mode)
189 (electric-indent-local-mode -1)
190 ;; for emacs 24.3 or less
191 (add-hook 'electric-indent-functions
192 (lambda (arg) 'no-indent) nil 'local)))
193
194
195 (defun ztree-find-node-in-line (line)
196 "Search through the array of node-line pairs and return the
197 node for the line specified"
198 (gethash line ztree-line-to-node-table))
199
200 (defun ztree-find-node-at-point ()
201 "Returns cons pair (node, side) for the current point or nil
202 if there is no node"
203 (let ((center (/ (window-width) 2))
204 (node (ztree-find-node-in-line (line-number-at-pos))))
205 (when node
206 (cons node (if (> (current-column) center) 'right 'left)))))
207
208
209 (defun ztree-is-expanded-node (node)
210 "Find if the node is in the list of expanded nodes"
211 (ztree-find ztree-expanded-nodes-list
212 #'(lambda (x) (funcall ztree-node-equal-fun x node))))
213
214
215 (defun ztree-set-parent-for-line (line parent)
216 (aset ztree-parent-lines-array (- line ztree-start-line) parent))
217
218 (defun ztree-get-parent-for-line (line)
219 (when (and (>= line ztree-start-line)
220 (< line (+ (length ztree-parent-lines-array) ztree-start-line)))
221 (aref ztree-parent-lines-array (- line ztree-start-line))))
222
223 (defun scroll-to-line (line)
224 "Recommended way to set the cursor to specified line"
225 (goto-char (point-min))
226 (forward-line (1- line)))
227
228
229 (defun ztree-do-toggle-expand-subtree-iter (node state)
230 (when (funcall ztree-node-is-expandable-fun node)
231 (let ((children (funcall ztree-node-contents-fun node)))
232 (ztree-do-toggle-expand-state node state)
233 (dolist (child children)
234 (ztree-do-toggle-expand-subtree-iter child state)))))
235
236
237 (defun ztree-do-toggle-expand-subtree ()
238 (let* ((line (line-number-at-pos))
239 (node (ztree-find-node-in-line line))
240 ;; save the current window start position
241 (current-pos (window-start)))
242 ;; only for expandable nodes
243 (when (funcall ztree-node-is-expandable-fun node)
244 ;; get the current expand state and invert it
245 (let ((do-expand (not (ztree-is-expanded-node node))))
246 (ztree-do-toggle-expand-subtree-iter node do-expand))
247 ;; refresh buffer and scroll back to the saved line
248 (ztree-refresh-buffer line)
249 ;; restore window start position
250 (set-window-start (selected-window) current-pos))))
251
252
253 (defun ztree-do-perform-action (hard)
254 (let* ((line (line-number-at-pos))
255 (node (ztree-find-node-in-line line)))
256 (when node
257 (if (funcall ztree-node-is-expandable-fun node)
258 ;; only for expandable nodes
259 (ztree-toggle-expand-state node)
260 ;; perform action
261 (when ztree-node-action-fun
262 (funcall ztree-node-action-fun node hard)))
263 ;; save the current window start position
264 (let ((current-pos (window-start)))
265 ;; refresh buffer and scroll back to the saved line
266 (ztree-refresh-buffer line)
267 ;; restore window start position
268 (set-window-start (selected-window) current-pos)))))
269
270
271 (defun ztree-perform-action ()
272 "Toggle expand/collapsed state for nodes or perform hard action,
273 binded on RET, on node"
274 (interactive)
275 (ztree-do-perform-action t))
276
277 (defun ztree-perform-soft-action ()
278 "Toggle expand/collapsed state for nodes or perform soft action,
279 binded on Space, on node"
280 (interactive)
281 (ztree-do-perform-action nil))
282
283
284 (defun ztree-toggle-expand-subtree()
285 "Toggle Expanded/Collapsed state on all nodes of the subtree"
286 (interactive)
287 (ztree-do-toggle-expand-subtree))
288
289 (defun ztree-do-toggle-expand-state (node do-expand)
290 "Set the expanded state of the node to do-expand"
291 (if (not do-expand)
292 (setq ztree-expanded-nodes-list
293 (ztree-filter
294 #'(lambda (x) (not (funcall ztree-node-equal-fun node x)))
295 ztree-expanded-nodes-list))
296 (push node ztree-expanded-nodes-list)))
297
298
299 (defun ztree-toggle-expand-state (node)
300 "Toggle expanded/collapsed state for nodes"
301 (ztree-do-toggle-expand-state node (not (ztree-is-expanded-node node))))
302
303
304 (defun ztree-move-up-in-tree ()
305 "Action on Backspace key: to jump to the line of a parent node or
306 if previous key was Backspace - close the node"
307 (interactive)
308 (when ztree-parent-lines-array
309 (let* ((line (line-number-at-pos (point)))
310 (parent (ztree-get-parent-for-line line)))
311 (when parent
312 (if (and (equal last-command 'ztree-move-up-in-tree)
313 (not ztree-count-subsequent-bs))
314 (let ((node (ztree-find-node-in-line line)))
315 (when (ztree-is-expanded-node node)
316 (ztree-toggle-expand-state node))
317 (setq ztree-count-subsequent-bs t)
318 (ztree-refresh-buffer line))
319 (progn (setq ztree-count-subsequent-bs nil)
320 (scroll-to-line parent)))))))
321
322
323 (defun ztree-get-splitted-node-contens (node)
324 "Returns pair of 2 elements: list of expandable nodes and
325 list of leafs"
326 (let ((nodes (funcall ztree-node-contents-fun node))
327 (comp #'(lambda (x y)
328 (string< (funcall ztree-node-short-name-fun x)
329 (funcall ztree-node-short-name-fun y)))))
330 (cons (sort (ztree-filter
331 #'(lambda (f) (funcall ztree-node-is-expandable-fun f))
332 nodes) comp)
333 (sort (ztree-filter
334 #'(lambda (f) (not (funcall ztree-node-is-expandable-fun f)))
335 nodes) comp))))
336
337
338 (defun ztree-draw-char (c x y &optional face)
339 "Draw char c at the position (1-based) (x y)"
340 (save-excursion
341 (scroll-to-line y)
342 (beginning-of-line)
343 (goto-char (+ x (-(point) 1)))
344 (delete-char 1)
345 (insert-char c 1)
346 (put-text-property (1- (point)) (point) 'face (if face face 'ztreep-arrow-face))))
347
348 (defun ztree-draw-vertical-line (y1 y2 x &optional face)
349 "Draw a vertical line of '|' characters"
350 (let ((count (abs (- y1 y2))))
351 (if (> y1 y2)
352 (progn
353 (dotimes (y count)
354 (ztree-draw-char ?\| x (+ y2 y) face))
355 (ztree-draw-char ?\| x (+ y2 count) face))
356 (progn
357 (dotimes (y count)
358 (ztree-draw-char ?\| x (+ y1 y) face))
359 (ztree-draw-char ?\| x (+ y1 count) face)))))
360
361 (defun ztree-draw-vertical-rounded-line (y1 y2 x &optional face)
362 "Draw a vertical line of '|' characters finishing with '`' character"
363 (let ((count (abs (- y1 y2))))
364 (if (> y1 y2)
365 (progn
366 (dotimes (y count)
367 (ztree-draw-char ?\| x (+ y2 y) face))
368 (ztree-draw-char ?\` x (+ y2 count) face))
369 (progn
370 (dotimes (y count)
371 (ztree-draw-char ?\| x (+ y1 y) face))
372 (ztree-draw-char ?\` x (+ y1 count) face)))))
373
374
375 (defun ztree-draw-horizontal-line (x1 x2 y)
376 (if (> x1 x2)
377 (dotimes (x (1+ (- x1 x2)))
378 (ztree-draw-char ?\- (+ x2 x) y))
379 (dotimes (x (1+ (- x2 x1)))
380 (ztree-draw-char ?\- (+ x1 x) y))))
381
382
383 (defun ztree-draw-tree (tree depth start-offset)
384 "Draw the tree of lines with parents"
385 (if (atom tree)
386 nil
387 (let* ((root (car tree))
388 (children (cdr tree))
389 (offset (+ start-offset (* depth 4)))
390 (line-start (+ 3 offset))
391 (line-end-leaf (+ 7 offset))
392 (line-end-node (+ 4 offset))
393 ;; determine if the line is visible. It is always the case
394 ;; for 1-sided trees; however for 2 sided trees
395 ;; it depends on which side is the actual element
396 ;; and which tree (left with offset 0 or right with offset > 0
397 ;; we are drawing
398 (visible #'(lambda (line) ()
399 (if (not ztree-node-side-fun) t
400 (let ((side
401 (gethash line ztree-line-tree-properties)))
402 (cond ((eq side 'left) (= start-offset 0))
403 ((eq side 'right) (> start-offset 0))
404 (t t)))))))
405 (when children
406 ;; draw the line to the last child
407 ;; since we push'd children to the list, it's the first visible line
408 ;; from the children list
409 (let ((last-child (ztree-find children
410 #'(lambda (x)
411 (funcall visible (car-atom x)))))
412 (x-offset (+ 2 offset)))
413 (when last-child
414 (ztree-draw-vertical-rounded-line (1+ root)
415 (car-atom last-child)
416 x-offset)))
417 ;; draw recursively
418 (dolist (child children)
419 (ztree-draw-tree child (1+ depth) start-offset)
420 (let ((end (if (listp child) line-end-node line-end-leaf)))
421 (when (funcall visible (car-atom child))
422 (ztree-draw-horizontal-line line-start
423 end
424 (car-atom child)))))))))
425
426 (defun ztree-fill-parent-array (tree)
427 ;; set the root line
428 (let ((root (car tree))
429 (children (cdr tree)))
430 (dolist (child children)
431 (ztree-set-parent-for-line (car-atom child) root)
432 (when (listp child)
433 (ztree-fill-parent-array child)))))
434
435
436 (defun ztree-insert-node-contents (path)
437 ;; insert node contents with initial depth 0
438 ;; ztree-insert-node-contents-1 return the tree of line
439 ;; numbers to determine who is parent line of the
440 ;; particular line. This tree is used to draw the
441 ;; graph
442 (let ((tree (ztree-insert-node-contents-1 path 0))
443 ;; number of 'rows' in tree is last line minus start line
444 (num-of-items (- (line-number-at-pos (point)) ztree-start-line)))
445 ;; create a parents array to store parents of lines
446 ;; parents array used for navigation with the BS
447 (setq ztree-parent-lines-array (make-vector num-of-items 0))
448 ;; set the root node in lines parents array
449 (ztree-set-parent-for-line ztree-start-line ztree-start-line)
450 ;; fill the parent arrray from the tree
451 (ztree-fill-parent-array tree)
452 ;; draw the tree starting with depth 0 and offset 0
453 (ztree-draw-tree tree 0 0)
454 ;; for the 2-sided tree we need to draw the vertical line
455 ;; and an additional tree
456 (if ztree-node-side-fun ; 2-sided tree
457 (let ((width (window-width)))
458 ;; draw the vertical line in the middle of the window
459 (ztree-draw-vertical-line ztree-start-line
460 (1- (+ num-of-items ztree-start-line))
461 (/ width 2)
462 'vertical-border)
463 (ztree-draw-tree tree 0 (1+ (/ width 2)))))))
464
465
466 (defun ztree-insert-node-contents-1 (node depth)
467 (let* ((expanded (ztree-is-expanded-node node))
468 ;; insert node entry with defined depth
469 (root-line (ztree-insert-entry node depth expanded))
470 ;; children list is the list of lines which are children
471 ;; of the root line
472 (children nil))
473 (when expanded ;; if expanded we need to add all subnodes
474 (let* ((contents (ztree-get-splitted-node-contens node))
475 ;; contents is the list of 2 elements:
476 (nodes (car contents)) ; expandable entries - nodes
477 (leafs (cdr contents))) ; leafs - which doesn't have subleafs
478 ;; iterate through all expandable entries to insert them first
479 (dolist (node nodes)
480 ;; if it is not in the filter list
481 (when (funcall ztree-node-showp-fun node)
482 ;; insert node on the next depth level
483 ;; and push the returning result (in form (root children))
484 ;; to the children list
485 (push (ztree-insert-node-contents-1 node (1+ depth))
486 children)))
487 ;; now iterate through all the leafs
488 (dolist (leaf leafs)
489 ;; if not in filter list
490 (when (funcall ztree-node-showp-fun leaf)
491 ;; insert the leaf and add it to children
492 (push (ztree-insert-entry leaf (1+ depth) nil)
493 children)))))
494 ;; result value is the list - head is the root line,
495 ;; rest are children
496 (cons root-line children)))
497
498 (defun ztree-insert-entry (node depth expanded)
499 (let ((line (line-number-at-pos))
500 (expandable (funcall ztree-node-is-expandable-fun node))
501 (short-name (funcall ztree-node-short-name-fun node)))
502 (if ztree-node-side-fun ; 2-sided tree
503 (let ((right-short-name (funcall ztree-node-short-name-fun node t))
504 (side (funcall ztree-node-side-fun node))
505 (width (window-width)))
506 (when (eq side 'left) (setq right-short-name ""))
507 (when (eq side 'right) (setq short-name ""))
508 (ztree-insert-single-entry short-name depth
509 expandable expanded 0
510 (when ztree-node-face-fun
511 (funcall ztree-node-face-fun node)))
512 (ztree-insert-single-entry right-short-name depth
513 expandable expanded (1+ (/ width 2))
514 (when ztree-node-face-fun
515 (funcall ztree-node-face-fun node)))
516 (puthash line side ztree-line-tree-properties))
517 (ztree-insert-single-entry short-name depth expandable expanded 0))
518 (puthash line node ztree-line-to-node-table)
519 (newline-and-begin)
520 line))
521
522 (defun ztree-insert-single-entry (short-name depth
523 expandable expanded
524 offset
525 &optional face)
526 (let ((node-sign #'(lambda (exp)
527 (insert "[" (if exp "-" "+") "]")
528 (set-text-properties (- (point) 3)
529 (point)
530 '(face ztreep-expand-sign-face)))))
531 (move-to-column offset t)
532 (delete-region (point) (line-end-position))
533 (when (> depth 0)
534 (dotimes (i depth)
535 (insert " ")
536 (insert-char ?\s 3))) ; insert 3 spaces
537 (when (> (length short-name) 0)
538 (if expandable
539 (progn
540 (funcall node-sign expanded) ; for expandable nodes insert "[+/-]"
541 (insert " ")
542 (put-text-property 0 (length short-name)
543 'face (if face face 'ztreep-node-face) short-name)
544 (insert short-name))
545 (progn
546 (insert " ")
547 (put-text-property 0 (length short-name)
548 'face (if face face 'ztreep-leaf-face) short-name)
549 (insert short-name))))))
550
551 (defun ztree-jump-side ()
552 (interactive)
553 (when ztree-node-side-fun ; 2-sided tree
554 (let ((center (/ (window-width) 2)))
555 (cond ((< (current-column) center)
556 (move-to-column (1+ center)))
557 ((> (current-column) center)
558 (move-to-column 1))
559 (t nil)))))
560
561
562
563 (defun ztree-refresh-buffer (&optional line)
564 (interactive)
565 (when (and (equal major-mode 'ztree-mode)
566 (boundp 'ztree-start-node))
567 (setq ztree-line-to-node-table (make-hash-table))
568 ;; create a hash table of node properties for line
569 ;; used in 2-side tree mode
570 (when ztree-node-side-fun
571 (setq ztree-line-tree-properties (make-hash-table)))
572 (toggle-read-only)
573 (erase-buffer)
574 (funcall ztree-tree-header-fun)
575 (setq ztree-start-line (line-number-at-pos (point)))
576 (ztree-insert-node-contents ztree-start-node)
577 (scroll-to-line (if line line ztree-start-line))
578 (toggle-read-only)))
579
580
581 (defun ztree-view (
582 buffer-name
583 start-node
584 filter-fun
585 header-fun
586 short-name-fun
587 expandable-p
588 equal-fun
589 children-fun
590 face-fun
591 action-fun
592 &optional node-side-fun
593 )
594 (let ((buf (get-buffer-create buffer-name)))
595 (switch-to-buffer buf)
596 (ztree-mode)
597 ;; configure ztree-view
598 (setq ztree-start-node start-node)
599 (setq ztree-expanded-nodes-list (list ztree-start-node))
600 (setq ztree-node-showp-fun filter-fun)
601 (setq ztree-tree-header-fun header-fun)
602 (setq ztree-node-short-name-fun short-name-fun)
603 (setq ztree-node-is-expandable-fun expandable-p)
604 (setq ztree-node-equal-fun equal-fun)
605 (setq ztree-node-contents-fun children-fun)
606 (setq ztree-node-face-fun face-fun)
607 (setq ztree-node-action-fun action-fun)
608 (setq ztree-node-side-fun node-side-fun)
609 (ztree-refresh-buffer)))
610
611
612 (provide 'ztree-view)
613 ;;; ztree-view.el ends here