-
Notifications
You must be signed in to change notification settings - Fork 5
/
tree-mode.el
526 lines (468 loc) · 18 KB
/
tree-mode.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
;;; tree-mode.el --- A mode to manage tree widgets
;; Copyright 2007 Ye Wenbin
;;
;; Author: [email protected]
;; Version: $Id: tree-mode.el,v 1.1.1.1 2007-03-13 13:16:10 ywb Exp $
;; Keywords: help, convenience, widget
;;
;; This file is part of PDE (Perl Development Environment).
;; But it is useful for generic programming.
;; 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
;; 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 this program; if not, write to the Free Software
;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
;;; Dependencies:
;; no extra libraries is required
;;; Installation:
;; Put this file into your load-path and the following into your ~/.emacs:
;; (require 'tree-mode)
;;; Code:
(require 'tree-widget)
(eval-when-compile
(require 'cl))
(defvar tree-mode-version "1.0")
(defvar tree-mode-list nil)
(defvar tree-mode-map
(let ((map (make-sparse-keymap)))
(set-keymap-parent map widget-keymap)
(define-key map " " 'scroll-up)
(define-key map "\C-?" 'scroll-down)
(define-key map "D" 'tree-mode-delete-tree)
(define-key map "p" 'tree-mode-previous-node)
(define-key map "n" 'tree-mode-next-node)
(define-key map "j" 'tree-mode-next-sib)
(define-key map "k" 'tree-mode-previous-sib)
(define-key map "u" 'tree-mode-goto-parent)
(define-key map "r" 'tree-mode-goto-root)
(define-key map "g" 'tree-mode-reflesh)
(define-key map "E" 'tree-mode-expand-level)
(define-key map "e" 'tree-mode-toggle-expand)
(define-key map "s" 'tree-mode-sort-by-tag)
(define-key map "/" 'tree-mode-keep-match)
(define-key map "!" 'tree-mode-collapse-other-except)
;; (define-key map "\C-s" 'tree-mode-isearch-forward)
;; (define-key map "\C-r" 'tree-mode-isearch-backward)
(dotimes (i 10)
(define-key map `[,(+ ?0 i)] 'digit-argument))
map))
(defvar tree-mode-menu nil)
(unless tree-mode-menu
(easy-menu-define
tree-mode-menu tree-mode-map "Tree menu"
'("Tree"
["Next tree node" tree-mode-next-node t]
["Previous tree node" tree-mode-previous-node t]
["Next sibling node" tree-mode-next-sib t]
["Previous sibling node" tree-mode-previous-sib t]
["Goto parent node" tree-mode-goto-parent t]
["Goto root node" tree-mode-goto-root t]
"--"
["Toggle Expand" tree-mode-toggle-expand t]
["Expand to level 1" (lambda () (interactive)
(tree-mode-expand-level 1)) t]
["Expand to level 2" (lambda () (interactive)
(tree-mode-expand-level 2)) t]
"--"
["Collapse other tree" tree-mode-collapse-other-except t]
["Sort by tag" tree-mode-sort-by-tag t]
["Keep match" tree-mode-keep-match t])))
(defvar tree-mode-insert-tree-hook nil
"Hooks run after insert a tree into buffer. Each function is
passed the new tree created")
(defvar tree-mode-delete-tree-hook nil
"Hooks run after delete a tree in the buffer. Each function is
passed the new tree created")
(defun tree-mode-nearest-widget ()
"Return widget at point or next nearest widget."
(or (widget-at)
(ignore-errors
(let ((pos (point)))
(widget-forward 1)
(and (< pos (point))
(widget-at))))))
(defun tree-mode-scan-tree ()
"Find all tree widget in current buffer."
(save-excursion
(goto-char (point-min))
(setq tree-mode-list nil)
(let ((widget (tree-mode-nearest-widget))
parent)
(while widget
(if (tree-widget-p (setq parent (widget-get widget :parent)))
(push parent tree-mode-list))
(goto-char (widget-get (or parent widget) :to))
(setq widget (tree-mode-nearest-widget)))
(setq tree-mode-list (nreverse tree-mode-list)))))
;;;###autoload
(define-minor-mode tree-minor-mode
"More keybindings for tree-widget.
\\{tree-mode-map}"
:lighter " Tree"
:keymap tree-mode-map
(when tree-minor-mode
(make-local-variable 'tree-mode-list)
(tree-mode-scan-tree)))
(define-derived-mode tree-mode nil "Tree"
"A mode to manage many tree widgets"
(make-local-variable 'tree-mode-list)
(make-local-variable 'tree-mode-insert-tree-hook)
(make-local-variable 'tree-mode-delete-tree-hook)
(widget-setup))
;; put :button-icon in push-button to setup the node icon
(add-hook 'tree-widget-before-create-icon-functions
'tree-mode-icon-create)
(defun tree-mode-icon-create (icon)
(let ((img (widget-get (widget-get icon :node) :button-icon)))
(if img (widget-put icon :glyph-name img))))
(defun tree-mode-insert (tree &optional before)
"Insert tree to buffer.
If BEFORE is non-nil and is a tree in current buffer, the new
TREE will insert at position of BEFORE."
(if (and before (memq before tree-mode-list))
(goto-char (widget-get before :from))
(goto-char (point-max)))
(setq tree (widget-create tree))
(setq tree-mode-list (append tree-mode-list (list tree)))
(run-hook-with-args 'tree-mode-insert-tree-hook tree)
tree)
(defun tree-mode-delete (tree)
"Delete tree in the buffer."
(setq tree-mode-list (delq tree tree-mode-list))
(widget-delete tree)
(run-hook-with-args 'tree-mode-delete-tree-hook tree))
(defun tree-mode-tree-buffer (tree)
"Return the buffer where the TREE is inserted"
(marker-buffer (widget-get tree :from)))
(defun tree-mode-kill-buffer (&rest ignore)
"If no tree in current buffer, kill this buffer."
(if (= (length tree-mode-list) 0)
(kill-buffer (current-buffer))))
;;{{{ Predicate and others
(defun tree-mode-root-treep (tree)
"Test if the TREE is root"
(and (tree-widget-p tree)
(null (widget-get tree :parent))))
(defun tree-mode-tree-linep ()
"If there is tree-widget in current line, return t."
(let ((wid (tree-mode-icon-current-line)))
(and wid (not (tree-widget-leaf-node-icon-p wid)))))
(defun tree-mode-root-linep ()
"If the root tree node in current line, return t"
(let ((wid (tree-mode-icon-current-line)))
(and wid (not (tree-widget-leaf-node-icon-p wid))
(null (widget-get (widget-get wid :parent) :parent)))))
(defun tree-mode-icon-current-line ()
"Return the icon widget in current line"
(save-excursion
(forward-line 0)
(tree-mode-nearest-widget)))
(defun tree-mode-button-current-line ()
"Return the push button in current line."
(save-excursion
(let ((pos (line-beginning-position))
but)
(goto-char (line-end-position))
(while (and (not but) (> (point) pos))
(setq but (get-char-property (point) 'button))
(backward-char 1))
but)))
(defun tree-mode-parent-current-line ()
"If current line is root line, return the root tree, otherwise
return the parent tree"
(let ((wid (tree-mode-icon-current-line))
parent)
(when wid
(if (tree-widget-leaf-node-icon-p wid)
(widget-get wid :parent)
(setq parent (widget-get (widget-get wid :parent) :parent))
(or parent (widget-get wid :parent))))))
(defun tree-mode-widget-root (wid)
"Return tree root of the widget WID."
(let (parent)
(while (setq parent (widget-get wid :parent))
(setq wid parent))
wid))
(defun tree-mode-tree-ap (&optional pos)
"Return the root tree at point"
(save-excursion
(if pos (goto-char pos))
(ignore-errors
(tree-mode-widget-root (tree-mode-icon-current-line)))))
;;}}}
(defun tree-mode-opened-tree (tree)
"Find all opened tree.
Return the tag list with the same depth."
(if (widget-get tree :open)
(cons (widget-get (tree-widget-node tree) :tag)
(delq nil
(mapcar (lambda (child)
(and (tree-widget-p child)
(tree-mode-opened-tree child)))
(widget-get tree :children))))))
(defun tree-mode-open-tree (tree path)
"Open tree using tag list given by `tree-mode-opened-tree'."
(when path
(if (not (widget-get tree :open))
(widget-apply-action tree))
(setq path (cdr path))
(and path
(mapc (lambda (child)
(and (tree-widget-p child)
(let* ((tag (widget-get (tree-widget-node child) :tag))
(subpath (assoc tag path)))
(if subpath
(tree-mode-open-tree child subpath)))))
(widget-get tree :children)))))
(defun tree-mode-reflesh-tree (tree)
"Redraw TREE.
If tree has attribute :dynargs, generate new :args from that function.
Otherwise use :old-args which saved by `tree-mode-backup-args'."
(let ((path (tree-mode-opened-tree tree)))
(if (widget-get tree :dynargs)
(widget-put tree :args nil)
(if (widget-get tree :old-args)
(widget-put tree :args (widget-get tree :old-args))))
(widget-value-set tree (widget-value tree))
(tree-mode-open-tree tree path)))
(defun tree-mode-reflesh-parent (widget &rest ignore)
"Put this function to :notify property of tree-widget node."
(tree-mode-reflesh-tree (widget-get widget :parent)))
;;{{{ Movement commands
(defun tree-mode-next-node (arg)
"Move to next node."
(interactive "p")
(widget-forward (* arg 2)))
(defun tree-mode-previous-node (arg)
(interactive "p")
(tree-mode-next-node (- arg)))
(defun tree-mode-next-sib (arg)
"Move to next sibling node."
(interactive "p")
(let (me siblings sib others out-range)
(if (tree-mode-root-linep)
(setq me (tree-mode-tree-ap)
siblings tree-mode-list)
(let ((parent (tree-mode-parent-current-line)))
(setq me (tree-mode-button-current-line))
(if (tree-mode-tree-linep)
(setq me (widget-get me :parent)))
(setq siblings (widget-get parent :children))))
(setq others (member me siblings))
(if (> arg 0)
(setq sib
(if (>= arg (length others))
(progn
(setq out-range t)
(car (last others)))
(nth arg others)))
(setq sib (- (length siblings)
(length others)
(- arg))
out-range (< sib 0))
(setq sib (nth (max 0 sib) siblings)))
(goto-char (widget-get sib :from))
(if out-range
(message "No %s sibling more!" (if (< arg 0) "previous" "next")))))
(defun tree-mode-previous-sib (arg)
"Move to previous sibling node."
(interactive "p")
(tree-mode-next-sib (- arg)))
(defun tree-mode-goto-root ()
"Move to root node"
(interactive)
(let ((root (tree-mode-tree-ap)))
(if root
(goto-char (widget-get root :from))
(message "No Root!"))))
(defun tree-mode-goto-parent (arg)
"Move to parent node."
(interactive "p")
(let ((parent (tree-mode-parent-current-line)))
(setq arg (1- arg))
(if parent
(progn
(goto-char (widget-get parent :from))
(while (and (> arg 0)
(setq parent (widget-get parent :parent))
(goto-char (widget-get parent :from))
(setq arg (1- arg)))))
(message "No parent!"))))
(defun tree-mode-find-node (tree path)
"Find node by path.
Return a cons cell (NODE . REST). Check the rest to find if the node
is node of the full path.
PATH is a list of node tag to search from root.
Note if the tree is not opened, It will open some node when need.
`set-buffer' to tree buffer before call this function."
(when (and (tree-widget-p tree) path)
(let ((children (cdr (widget-get tree :children))) ; car is root node
;; if last node, both push-button and tree-widget will check
(predicate (if (= (length path) 1)
'widget-type 'tree-widget-p))
node found)
(while (and (not found) children)
(setq node (car children))
(if (and (funcall predicate node)
(string= (tree-mode-node-tag node) (car path)))
(progn
(when (cdr path)
;; if tree is not open, open it
(if (and (tree-widget-p node)
(not (widget-get node :open)))
(widget-apply-action node))
(setq found (tree-mode-find-node (car children) (cdr path))))
(or found
(setq found (cons (car children) (cdr path)))))
(setq children (cdr children))))
found)))
;;}}}
;;{{{ Expand or collapse
(defun tree-mode-collapse-other-except ()
"Collapse other trees. If the tree at point is contract, expand it."
(interactive)
(let ((me (tree-mode-icon-current-line)))
(if (tree-widget-leaf-node-icon-p me)
(message "Not a tree under point!")
(setq me (widget-get me :parent))
(unless (widget-get me :open)
(widget-apply-action me))
(mapc (lambda (tree)
(if (widget-get tree :open)
(widget-apply-action tree)))
(remq me (if (tree-mode-root-treep me)
tree-mode-list
(widget-get (widget-get me :parent)
:children)))))))
(defun tree-mode-collapse-children (tree)
"Collapse child node"
(mapc (lambda (child)
(if (widget-get child :open)
(widget-apply-action child)))
(widget-get tree :children)))
(defun tree-mode-expand-children (tree)
"Expand child node"
(mapc (lambda (child)
(if (and (tree-widget-p child)
(not (widget-get child :open)))
(widget-apply-action child)))
(widget-get tree :children)))
(defun tree-mode-toggle-expand-node (&rest ignore)
"Put it to :notify of tree widget node."
(tree-mode-toggle-expand))
(defun tree-mode-toggle-expand (&optional arg)
(interactive "P")
(let ((me (tree-mode-icon-current-line))
expandp open)
(if (tree-widget-leaf-node-icon-p me)
(message "Not a tree under point!")
(setq me (widget-get me :parent))
(setq expandp (widget-get me :open))
(setq open (if (null arg)
(not expandp)
(> (prefix-numeric-value arg) 0)))
(unless (eq open expandp)
(widget-apply-action me)))))
(defun tree-mode-expand-level (level)
"Expand tree to LEVEL. With prefix argument 0 or negative, will
expand all leaves of the tree."
(interactive "p")
(let ((me (tree-mode-icon-current-line)))
(if (tree-widget-leaf-node-icon-p me)
(message "Not a tree under point!")
(setq me (widget-get me :parent))
(tree-mode-expand-level-1 me (1- level)))))
(defun tree-mode-expand-level-1 (tree level)
(when (tree-widget-p tree)
(if (not (widget-get tree :open))
(widget-apply-action tree))
(if (= level 0)
(tree-mode-collapse-children tree)
(mapc (lambda (child)
(tree-mode-expand-level-1 child (1- level)))
(widget-get tree :children)))))
;;}}}
(defun tree-mode-node-tag (node)
"Return tag of push-button or tree-widget"
(or (widget-get node :tag)
(widget-get (widget-get node :node) :tag)))
;;{{{ Commands about tree nodes
(defun tree-mode-backup-args (widget)
"Save :args of tree-widget if need."
(unless (and (widget-get widget :dynargs)
(null (widget-get widget :old-args)))
;; if widget don't have a dynamic args function
;; restore args to old-args for recover
(widget-put widget :old-args (copy-sequence (widget-get widget :args)))))
(defun tree-mode-filter-children (widget filter)
"Remove children nodes when call FILTER with the node return true."
(tree-mode-backup-args widget)
(widget-put widget :args
(delq nil (mapcar (lambda (child)
(if (funcall filter child)
child))
(widget-get widget :args))))
(widget-value-set widget (widget-value widget)))
(defun tree-mode-sort-by-nchild (wid1 wid2)
"Sort node by which node has children"
(widget-get wid1 :children))
(defun tree-mode-sort-children (widget sorter)
"Sort children nodes by SORTER."
(tree-mode-backup-args widget)
(widget-put widget :args
(sort (copy-sequence (widget-get widget :args)) sorter))
(widget-value-set widget (widget-value widget)))
(defun tree-mode-sort-by-tag (arg)
"Sort children node by tag."
(interactive "P")
(let ((tree (tree-mode-parent-current-line)))
(if tree
(tree-mode-sort-children tree
(lambda (w1 w2)
(or (tree-mode-sort-by-nchild w1 w2)
(string< (tree-mode-node-tag w1)
(tree-mode-node-tag w2)))))
(message "No tree at point!"))))
(defun tree-mode-delete-match (regexp)
"Remove node which tag match REGEXP."
(interactive "sDelete node match: ")
(let ((tree (tree-mode-parent-current-line)))
(if tree
(tree-mode-filter-children
tree
(lambda (child) (not (string-match regexp (tree-mode-node-tag child)))))
(message "No tree at point!"))))
(defun tree-mode-keep-match (regexp)
"Keep node which tag match REGEXP"
(interactive "sKeep node match: ")
(let ((tree (tree-mode-parent-current-line)))
(if tree
(tree-mode-filter-children
tree
(lambda (child) (string-match regexp (tree-mode-node-tag child))))
(message "No tree at point!"))))
(defun tree-mode-reflesh ()
"Reflesh parent tree."
(interactive)
(let ((tree (tree-mode-parent-current-line)))
(if tree
(tree-mode-reflesh-tree tree)
(message "No tree at point!"))))
(defun tree-mode-delete-tree ()
"Delete a tree from buffer."
(interactive)
(if (tree-mode-root-linep)
(if (yes-or-no-p "Delete current tree? ")
(tree-mode-delete (tree-mode-tree-ap)))
(message "No tree at point!")))
;;}}}
(provide 'tree-mode)
;;; tree-mode.el ends here