-
Notifications
You must be signed in to change notification settings - Fork 41
/
origami.el
828 lines (713 loc) · 35 KB
/
origami.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
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
;;; origami.el --- Flexible text folding -*- lexical-binding: t -*-
;; Author: Greg Sexton <[email protected]>
;; Version: 1.0
;; Keywords: folding
;; URL: https://github.com/gregsexton/origami.el
;; Package-Requires: ((s "1.9.0") (dash "2.5.0") (emacs "24") (cl-lib "0.5"))
;; The MIT License (MIT)
;; Copyright (c) 2014 Greg Sexton
;; Permission is hereby granted, free of charge, to any person obtaining a copy
;; of this software and associated documentation files (the "Software"), to deal
;; in the Software without restriction, including without limitation the rights
;; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
;; copies of the Software, and to permit persons to whom the Software is
;; furnished to do so, subject to the following conditions:
;; The above copyright notice and this permission notice shall be included in
;; all copies or substantial portions of the Software.
;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
;; THE SOFTWARE.
;;; Commentary:
;;; Code:
(require 'dash)
(require 's)
(require 'cl-lib)
(require 'origami-parsers)
;;; fold display mode and faces
(defgroup origami nil
"Flexible text folding"
:prefix "origami-"
:group 'convenience)
(defcustom origami-fold-replacement "..."
;; TODO: this should also be specifiable as a function: folded text -> string
"Show this string instead of the folded text."
:type 'string
:group 'origami)
(defcustom origami-show-fold-header nil
"Highlight the line the fold start on."
:type 'boolean
:group 'origami)
(defface origami-fold-header-face
`((t (:box (:line-width 1 :color ,(face-attribute 'highlight :background))
:background ,(face-attribute 'highlight :background))))
"Face used to display fold headers.")
(defface origami-fold-fringe-face
'((t ()))
"Face used to display fringe contents.")
(defface origami-fold-replacement-face
'((t :inherit 'font-lock-comment-face))
"Face used to display the fold replacement text.")
;;; overlay manipulation
(defun origami-header-overlay-range (fold-overlay)
"Given a `fold-overlay', return the range that the corresponding
header overlay should cover. Result is a cons cell of (begin . end)."
(with-current-buffer (overlay-buffer fold-overlay)
(let ((fold-begin
(save-excursion
(goto-char (overlay-start fold-overlay))
(line-beginning-position)))
(fold-end
;; Find the end of the folded region -- include the following
;; newline if possible. The header will span the entire fold.
(save-excursion
(goto-char (overlay-end fold-overlay))
(when (looking-at ".")
(forward-char 1)
(when (looking-at "\n")
(forward-char 1)))
(point))))
(cons fold-begin fold-end))))
(defun origami-header-overlay-reset-position (header-overlay)
(-when-let (fold-ov (overlay-get header-overlay 'fold-overlay))
(let ((range (origami-header-overlay-range fold-ov)))
(move-overlay header-overlay (car range) (cdr range)))))
(defun origami-header-modify-hook (header-overlay after-p b e &optional l)
(if after-p (origami-header-overlay-reset-position header-overlay)))
(defun origami-create-overlay (beg end offset buffer)
(when (> (- end beg) 0)
(let ((ov (make-overlay (+ beg offset) end buffer)))
(overlay-put ov 'creator 'origami)
(overlay-put ov 'isearch-open-invisible 'origami-isearch-show)
(overlay-put ov 'isearch-open-invisible-temporary
(lambda (ov hide-p) (if hide-p (origami-hide-overlay ov)
(origami-show-overlay ov))))
;; We create a header overlay even when disabled; this could be avoided,
;; especially if we called origami-reset for each buffer if customizations
;; changed.
(let* ((range (origami-header-overlay-range ov))
(header-ov (make-overlay (car range) (cdr range) buffer
nil))) ;; no front advance
(overlay-put header-ov 'creator 'origami)
(overlay-put header-ov 'fold-overlay ov)
(overlay-put header-ov 'modification-hooks '(origami-header-modify-hook))
(overlay-put ov 'header-ov header-ov))
ov)))
(defun origami-hide-overlay (ov)
(overlay-put ov 'invisible 'origami)
(overlay-put ov 'display origami-fold-replacement)
(overlay-put ov 'face 'origami-fold-replacement-face)
(if origami-show-fold-header
(origami-activate-header (overlay-get ov 'header-ov))))
(defun origami-show-overlay (ov)
(overlay-put ov 'invisible nil)
(overlay-put ov 'display nil)
(overlay-put ov 'face nil)
(origami-deactivate-header (overlay-get ov 'header-ov)))
(defun origami-hide-node-overlay (node)
(-when-let (ov (origami-fold-data node))
(origami-hide-overlay ov)))
(defun origami-show-node-overlay (node)
(-when-let (ov (origami-fold-data node))
(origami-show-overlay ov)))
(defun origami-activate-header (ov)
;; Reposition the header overlay. Since it extends before the folded area, it
;; may no longer cover the appropriate locations.
(origami-header-overlay-reset-position ov)
(overlay-put ov 'origami-header-active t)
(overlay-put ov 'face 'origami-fold-header-face)
(overlay-put ov 'before-string
(propertize
"…"
'display
'(left-fringe empty-line origami-fold-fringe-face))))
(defun origami-deactivate-header (ov)
(overlay-put ov 'origami-header-active nil)
(overlay-put ov 'face nil)
(overlay-put ov 'before-string nil)
(overlay-put ov 'after-string nil))
(defun origami-isearch-show (ov)
(origami-show-node (current-buffer) (point)))
(defun origami-hide-overlay-from-fold-tree-fn (node)
(origami-fold-postorder-each node 'origami-hide-node-overlay))
(defun origami-show-overlay-from-fold-tree-fn (node)
(origami-fold-postorder-each node 'origami-show-node-overlay))
(defun origami-change-overlay-from-fold-node-fn (old new)
(if (origami-fold-open? new)
(origami-show-node-overlay old)
(origami-hide-node-overlay new)))
(defun origami-remove-all-overlays (buffer)
(with-current-buffer buffer
(remove-overlays (point-min) (point-max) 'creator 'origami)))
;;; fold structure
(defun origami-fold-node (beg end offset open &optional children data)
(let ((sorted-children (-sort (lambda (a b)
(or (< (origami-fold-beg a) (origami-fold-beg b))
(and (= (origami-fold-beg a) (origami-fold-beg b))
(< (origami-fold-end a) (origami-fold-end b)))))
(remove nil children))))
;; ensure invariant: no children overlap
(when (-some? (lambda (pair)
(let ((a (car pair))
(b (cadr pair)))
(when b ;for the odd numbered case - there may be a single item
;; the < function doesn't support varargs
(or (>= (origami-fold-beg a) (origami-fold-end a))
(>= (origami-fold-end a) (origami-fold-beg b))
(>= (origami-fold-beg b) (origami-fold-end b))))))
(-partition-all-in-steps 2 1 sorted-children))
(error "Tried to construct a node where the children overlap or are not distinct regions: %s"
sorted-children))
;; ensure invariant: parent encompases children
(let ((beg-children (origami-fold-beg (car sorted-children)))
(end-children (origami-fold-end (-last-item sorted-children))))
(if (and beg-children (or (> beg beg-children) (< end end-children)))
(error "Node does not overlap children in range. beg=%s end=%s beg-children=%s end-children=%s"
beg end beg-children end-children)
(if (> (+ beg offset) end)
(error "Offset is not within the range of the node: beg=%s end=%s offset=%s" beg end offset)
(vector beg end offset open sorted-children data))))))
(defun origami-fold-root-node (&optional children)
"Create a root container node."
(origami-fold-node 1 most-positive-fixnum 0 t children 'root))
(defun origami-fold-is-root-node? (node) (eq (origami-fold-data node) 'root))
(defun origami-fold-beg (node)
(when node
(if (origami-fold-is-root-node? node)
(aref node 0)
(- (overlay-start (origami-fold-data node)) (origami-fold-offset node)))))
(defun origami-fold-end (node)
(when node
(if (origami-fold-is-root-node? node)
(aref node 1)
(overlay-end (origami-fold-data node)))))
(defun origami-fold-offset (node) (when node (aref node 2)))
(defun origami-fold-open? (node) (when node (aref node 3)))
(defun origami-fold-open-set (node value)
(when node
(if (origami-fold-is-root-node? node)
node
(origami-fold-node (origami-fold-beg node)
(origami-fold-end node)
(origami-fold-offset node)
value
(origami-fold-children node)
(origami-fold-data node)))))
(defun origami-fold-children (node) (when node (aref node 4)))
(defun origami-fold-children-set (node children)
(when node
(origami-fold-node (origami-fold-beg node)
(origami-fold-end node)
(origami-fold-offset node)
(origami-fold-open? node)
children
(origami-fold-data node))))
(defun origami-fold-data (node) (when node (aref node 5)))
;;; fold structure utils
(defun origami-fold-range-equal (a b)
(and (equal (origami-fold-beg a) (origami-fold-beg b))
(equal (origami-fold-end a) (origami-fold-end b))))
(defun origami-fold-state-equal (a b)
(equal (origami-fold-open? a) (origami-fold-open? b)))
(defun origami-fold-add-child (node new)
(origami-fold-children-set node
(cons new (origami-fold-children node))))
(defun origami-fold-replace-child (node old new)
(origami-fold-children-set node
(cons new (remove old (origami-fold-children node)))))
(defun origami-fold-assoc (path f)
"Rewrite the tree, replacing the node referenced by PATH with
F applied to the leaf."
(cdr
(-reduce-r-from (lambda (node acc)
(destructuring-bind (old-node . new-node) acc
(cons node (origami-fold-replace-child node old-node new-node))))
(let ((leaf (-last-item path))) (cons leaf (funcall f leaf)))
(butlast path))))
(defun origami-fold-diff (old new on-add on-remove on-change)
(cl-labels ((diff-children (old-children new-children)
(let ((old (car old-children))
(new (car new-children)))
(cond ((null old) (-each new-children on-add))
((null new) (-each old-children on-remove))
((and (null old) (null new)) nil)
((origami-fold-range-equal old new)
(origami-fold-diff old new on-add on-remove on-change)
(diff-children (cdr old-children) (cdr new-children)))
((<= (origami-fold-beg old) (origami-fold-beg new))
(funcall on-remove old)
(diff-children (cdr old-children) new-children))
(t (funcall on-add new)
(diff-children old-children (cdr new-children)))))))
(unless (origami-fold-range-equal old new)
(error "Precondition invalid: old must have the same range as new."))
(unless (origami-fold-state-equal old new)
(funcall on-change old new))
(diff-children (origami-fold-children old)
(origami-fold-children new))))
(defun origami-fold-postorder-each (node f)
(-each (origami-fold-children node) f)
(funcall f node))
(defun origami-fold-map (f tree)
"Map F over the tree. Replacing each node with the result of (f
node). The children cannot be manipulated using f as the map will
replace them. This cannot change the structure of the tree, just
the state of each node."
(origami-fold-children-set
(funcall f tree)
(-map (lambda (node) (origami-fold-map f node))
(origami-fold-children tree))))
(defun origami-fold-path-map (f path)
"Map F over the nodes in path. As with `origami-fold-map',
children cannot be manipulated."
(cond ((null path) nil)
((cdr path) (funcall f (origami-fold-replace-child (car path)
(cadr path)
(origami-fold-path-map f (cdr path)))))
(t (funcall f (car path)))))
(defun origami-fold-find-deepest (tree pred)
(when tree
(when (funcall pred tree)
(-if-let (child (-first pred (origami-fold-children tree)))
(cons tree (origami-fold-find-deepest child pred))
(list tree)))))
(defun origami-fold-find-path-containing-range (tree beg end)
(origami-fold-find-deepest tree
(lambda (node)
(and (>= beg (origami-fold-beg node))
(<= end (origami-fold-end node))))))
(defun origami-fold-find-path-with-range (tree beg end)
"Return the path to the most specific (deepest) node that has
exactly the range BEG-END, or null."
(-when-let (path (origami-fold-find-path-containing-range tree beg end))
(let ((last (-last-item path)))
(when (and (= beg (origami-fold-beg last))
(= end (origami-fold-end last)))
path))))
(defun origami-fold-find-path-containing (tree point)
"Return the path to the most specific (deepest) node that
contains point, or null."
(origami-fold-find-deepest tree
(lambda (node)
(and (<= (origami-fold-beg node) point)
(>= (origami-fold-end node) point)))))
(defun origami-fold-preorder-reduce (tree f initial-state)
"Reduce the tree by doing a preorder traversal. F is applied
with the current state and the current node at each iteration."
(-reduce-from (lambda (state node) (origami-fold-preorder-reduce node f state))
(funcall f initial-state tree)
(origami-fold-children tree)))
(defun origami-fold-postorder-reduce (tree f initial-state)
"Reduce the tree by doing a postorder traversal. F is applied
with the current state and the current node at each iteration."
(funcall f (-reduce-from (lambda (state node) (origami-fold-postorder-reduce node f state))
initial-state
(origami-fold-children tree))
tree))
(defun origami-fold-node-recursively-closed? (node)
(origami-fold-postorder-reduce node (lambda (acc node)
(and acc (not (origami-fold-open? node)))) t))
(defun origami-fold-node-recursively-open? (node)
(origami-fold-postorder-reduce node (lambda (acc node)
(and acc (origami-fold-open? node))) t))
(defun origami-fold-shallow-merge (tree1 tree2)
"Shallow merge the children of TREE2 in to TREE1."
(-reduce-from (lambda (tree node)
(origami-fold-assoc (origami-fold-find-path-containing-range tree
(origami-fold-beg node)
(origami-fold-end node))
(lambda (leaf)
(origami-fold-add-child leaf node))))
tree1 (origami-fold-children tree2)))
(defun origami-fold-parent (path)
(-last-item (-butlast path)))
(defun origami-fold-prev-sibling (siblings node)
(->> siblings
(-partition-in-steps 2 1)
(-drop-while (lambda (pair) (not (equal (cadr pair) node))))
caar))
(defun origami-fold-next-sibling (siblings node)
(->> siblings
(-drop-while (lambda (n) (not (equal n node))))
cadr))
;;; linear history structure
(defun origami-h-new (present)
"Create a new history structure."
(vector nil present nil))
(defun origami-h-push (h new)
"Create a new history structure with new as the present value."
(when new
(let ((past (aref h 0))
(present (aref h 1)))
(vector (cons present (-take 19 past)) new nil))))
(defun origami-h-undo (h)
(let ((past (aref h 0))
(present (aref h 1))
(future (aref h 2)))
(if (null past) h
(vector (cdr past) (car past) (cons present future)))))
(defun origami-h-redo (h)
(let ((past (aref h 0))
(present (aref h 1))
(future (aref h 2)))
(if (null future) h
(vector (cons present past) (car future) (cdr future)))))
(defun origami-h-present (h)
(when h (aref h 1)))
;;; interactive utils
(defun origami-setup-local-vars (buffer)
(with-current-buffer buffer
(set (make-local-variable 'origami-history)
(origami-h-new (origami-fold-root-node)))
(set (make-local-variable 'origami-tree-tick) 0)))
(defun origami-get-cached-tree (buffer)
(or (local-variable-p 'origami-history buffer)
(error "Necessary local variables were not available"))
(origami-h-present (buffer-local-value 'origami-history buffer)))
(defun origami-store-cached-tree (buffer tree)
(or (and (local-variable-p 'origami-history buffer)
(local-variable-p 'origami-tree-tick buffer))
(error "Necessary local variables were not available"))
(with-current-buffer buffer
(setq origami-tree-tick (buffer-modified-tick))
(setq origami-history (origami-h-push origami-history tree)))
tree)
(defun origami-update-history (buffer f)
(or (local-variable-p 'origami-history buffer)
(error "Necessary local variables were not available"))
(with-current-buffer buffer
(setq origami-history (funcall f origami-history))))
(defun origami-rebuild-tree? (buffer)
"Determines if the tree needs to be rebuilt for BUFFER since it
was last built."
(not (= (buffer-local-value 'origami-tree-tick buffer)
(buffer-modified-tick buffer))))
(defun origami-build-tree (buffer parser)
(when parser
(with-current-buffer buffer
(let ((contents (buffer-string)))
(-> parser
(funcall contents)
origami-fold-root-node)))))
(defun origami-get-parser (buffer)
(let* ((cached-tree (origami-get-cached-tree buffer))
(create (lambda (beg end offset children)
(let ((previous-fold (-last-item (origami-fold-find-path-with-range cached-tree beg end))))
(origami-fold-node beg end offset
(if previous-fold (origami-fold-open? previous-fold) t)
children
(or (-> (origami-fold-find-path-with-range
(origami-get-cached-tree buffer) beg end)
-last-item
origami-fold-data)
(origami-create-overlay beg end offset buffer)))))))
(-when-let (parser-gen (or (cdr (assoc (if (local-variable-p 'origami-fold-style)
(buffer-local-value 'origami-fold-style buffer)
(buffer-local-value 'major-mode buffer))
origami-parser-alist))
'origami-indent-parser))
(funcall parser-gen create))))
(defun origami-get-fold-tree (buffer)
"Facade. Build the tree if it hasn't already been built
otherwise fetch cached tree."
(when origami-mode
(if (origami-rebuild-tree? buffer)
(origami-build-tree buffer (origami-get-parser buffer))
(origami-get-cached-tree buffer))))
(defun origami-apply-new-tree (buffer old-tree new-tree)
(when new-tree
(origami-fold-diff old-tree new-tree
'origami-hide-overlay-from-fold-tree-fn
'origami-show-overlay-from-fold-tree-fn
'origami-change-overlay-from-fold-node-fn)))
(defun origami-search-forward-for-path (buffer point)
(let (end)
(with-current-buffer buffer
(save-excursion
(goto-char point)
(setq end (line-end-position))))
(-when-let (tree (origami-get-fold-tree buffer))
(-when-let (path (origami-fold-find-path-containing tree point))
(let ((forward-node (-first (lambda (node)
(and (>= (origami-fold-beg node) point)
(<= (origami-fold-beg node) end)))
(origami-fold-children (-last-item path)))))
(if forward-node (append path (list forward-node)) path))))))
;;; commands
(defun origami-open-node (buffer point)
"Open the fold node at POINT in BUFFER. The fold node opened
will be the deepest nested at POINT."
(interactive (list (current-buffer) (point)))
(-when-let (tree (origami-get-fold-tree buffer))
(-when-let (path (origami-fold-find-path-containing tree point))
(origami-apply-new-tree buffer tree (origami-store-cached-tree
buffer
(origami-fold-assoc path (lambda (node)
(origami-fold-open-set node t))))))))
(defun origami-open-node-recursively (buffer point)
"Open the fold node and all of its children at POINT in BUFFER.
The fold node opened will be the deepest nested at POINT."
(interactive (list (current-buffer) (point)))
(-when-let (tree (origami-get-fold-tree buffer))
(-when-let (path (origami-fold-find-path-containing tree point))
(origami-apply-new-tree
buffer tree (origami-store-cached-tree
buffer
(origami-fold-assoc path
(lambda (node)
(origami-fold-map (lambda (node)
(origami-fold-open-set node t))
node))))))))
(defun origami-show-node (buffer point)
"Like `origami-open-node' but also opens parent fold nodes
recursively so as to ensure the position where POINT is is
visible."
(interactive (list (current-buffer) (point)))
(-when-let (tree (origami-get-fold-tree buffer))
(-when-let (path (origami-fold-find-path-containing tree point))
(origami-apply-new-tree buffer tree (origami-store-cached-tree
buffer
(origami-fold-path-map
(lambda (node)
(origami-fold-open-set node t))
path))))))
(defun origami-close-node (buffer point)
"Close the fold node at POINT in BUFFER. The fold node closed
will be the deepest nested at POINT."
(interactive (list (current-buffer) (point)))
(-when-let (tree (origami-get-fold-tree buffer))
(-when-let (path (origami-fold-find-path-containing tree point))
(origami-apply-new-tree buffer tree (origami-store-cached-tree
buffer
(origami-fold-assoc
path (lambda (node)
(origami-fold-open-set node nil))))))))
(defun origami-close-node-recursively (buffer point)
"Close the fold node and all of its children at POINT in BUFFER.
The fold node closed will be the deepest nested at POINT."
(interactive (list (current-buffer) (point)))
(-when-let (tree (origami-get-fold-tree buffer))
(-when-let (path (origami-fold-find-path-containing tree point))
(origami-apply-new-tree
buffer tree (origami-store-cached-tree
buffer
(origami-fold-assoc path
(lambda (node)
(origami-fold-map (lambda (node)
(origami-fold-open-set node nil))
node))))))))
(defun origami-toggle-node (buffer point)
"Toggle the fold node at POINT in BUFFER open or closed. The
fold node opened or closed will be the deepest nested at POINT."
(interactive (list (current-buffer) (point)))
(-when-let (tree (origami-get-fold-tree buffer))
(-when-let (path (origami-fold-find-path-containing tree point))
(origami-apply-new-tree buffer tree (origami-store-cached-tree
buffer
(origami-fold-assoc
path (lambda (node)
(origami-fold-open-set
node (not (origami-fold-open?
(-last-item path)))))))))))
(defun origami-forward-toggle-node (buffer point)
"Like `origami-toggle-node' but search forward in BUFFER for a
fold node. If a fold node is found after POINT and before the
next line break, this will be toggled. Otherwise, behave exactly
as `origami-toggle-node'."
(interactive (list (current-buffer) (point)))
(-when-let (tree (origami-get-fold-tree buffer))
(-when-let (path (origami-search-forward-for-path buffer point))
(origami-apply-new-tree buffer tree (origami-store-cached-tree
buffer
(origami-fold-assoc
path (lambda (node)
(origami-fold-open-set
node (not (origami-fold-open?
(-last-item path)))))))))))
(defun origami-recursively-toggle-node (buffer point)
"Cycle a fold node between recursively closed, open and
recursively open depending on its current state. The fold node
acted upon is searched for forward in BUFFER from POINT. If a
fold node is found after POINT and before the next line break,
this will be toggled otherwise the fold node nested deepest at
POINT will be acted upon.
This command will only work if bound to a key. For those familiar
with org-mode heading opening and collapsing, this will feel
familiar. It's easiest to grasp this just by giving it a go."
(interactive (list (current-buffer) (point)))
(-when-let (path (origami-search-forward-for-path buffer point))
(let ((node (-last-item path)))
(if (eq last-command 'origami-recursively-toggle-node)
(cond ((origami-fold-node-recursively-open? node)
(origami-close-node-recursively buffer (origami-fold-beg node)))
((origami-fold-node-recursively-closed? node)
(origami-toggle-node buffer (origami-fold-beg node)))
(t (origami-open-node-recursively buffer (origami-fold-beg node))))
(origami-forward-toggle-node buffer point)))))
(defun origami-open-all-nodes (buffer)
"Recursively open every fold node in BUFFER."
(interactive (list (current-buffer)))
(-when-let (tree (origami-get-fold-tree buffer))
(origami-apply-new-tree buffer tree (origami-store-cached-tree
buffer
(origami-fold-map
(lambda (node)
(origami-fold-open-set node t))
tree)))))
(defun origami-close-all-nodes (buffer)
"Recursively close every fold node in BUFFER."
(interactive (list (current-buffer)))
(-when-let (tree (origami-get-fold-tree buffer))
(origami-apply-new-tree buffer tree (origami-store-cached-tree
buffer
(origami-fold-map
(lambda (node)
(origami-fold-open-set node nil))
tree)))))
(defun origami-toggle-all-nodes (buffer)
"Toggle all fold nodes in the buffer recursively open or
recursively closed."
(interactive (list (current-buffer)))
(-when-let (tree (origami-get-fold-tree buffer))
;; use the first child as root is always open
(if (-> tree origami-fold-children car origami-fold-open?)
(origami-close-all-nodes buffer)
(origami-open-all-nodes buffer))))
(defun origami-show-only-node (buffer point)
"Close all fold nodes in BUFFER except for those necessary to
make POINT visible. Very useful for quickly collapsing everything
in the buffer other than what you are looking at."
(interactive (list (current-buffer) (point)))
(origami-close-all-nodes buffer)
(origami-show-node buffer point))
(defun origami-previous-fold (buffer point)
"Move point to the beginning of the fold before POINT. If POINT
is in a fold, move to the beginning of the fold that POINT is
in."
(interactive (list (current-buffer) (point)))
(-when-let (tree (origami-get-fold-tree buffer))
(push-mark)
(-> tree
(origami-fold-preorder-reduce (lambda (state n)
(cons (origami-fold-beg n) state)) nil)
(->> (-reduce (lambda (state pos)
(if (< state point) state pos))))
goto-char)))
(defun origami-next-fold (buffer point)
"Move point to the end of the fold after POINT. If POINT is in
a fold, move to the end of the fold that POINT is in."
(interactive (list (current-buffer) (point)))
(-when-let (tree (origami-get-fold-tree buffer))
(push-mark)
(-> tree
(origami-fold-postorder-reduce (lambda (state n)
(cons (origami-fold-end n) state)) nil)
(->> (-last (lambda (pos) (> pos point))))
goto-char)))
(defun origami-forward-fold (buffer point)
"Move point to the beginning of the first fold in the BUFFER
after POINT."
(interactive (list (current-buffer) (point)))
(-when-let (tree (origami-get-fold-tree buffer))
(push-mark)
(-> tree
(origami-fold-preorder-reduce (lambda (state n)
(cons (origami-fold-beg n) state)) nil)
(->> (-last (lambda (pos) (> pos point))))
goto-char)))
(defun origami-forward-fold-same-level (buffer point)
"Move point to the beginning of the next fold in the buffer
that is a sibling of the fold the point is currently in."
(interactive (list (current-buffer) (point)))
(-when-let (tree (origami-get-fold-tree buffer))
(-when-let (path (origami-fold-find-path-containing tree point))
(push-mark)
(-when-let (c (-> (origami-fold-next-sibling (origami-fold-children
(origami-fold-parent path))
(-last-item path))
origami-fold-beg))
(goto-char c)))))
(defun origami-backward-fold-same-level (buffer point)
"Move point to the beginning of the previous fold in the buffer
that is a sibling of the fold the point is currently in."
(interactive (list (current-buffer) (point)))
(-when-let (tree (origami-get-fold-tree buffer))
(-when-let (path (origami-fold-find-path-containing tree point))
(push-mark)
(-when-let (c (-> (origami-fold-prev-sibling (origami-fold-children
(origami-fold-parent path))
(-last-item path))
origami-fold-beg))
(goto-char c)))))
(defun origami-undo (buffer)
"Undo the last folding operation applied to BUFFER. Undo
history is linear. If you undo some fold operations and then
perform a new fold operation you will lose the history of
operations undone."
(interactive (list (current-buffer)))
(let ((current-tree (origami-get-cached-tree buffer)))
(origami-update-history buffer (lambda (h) (origami-h-undo h)))
(let ((old-tree (origami-get-cached-tree buffer)))
(origami-apply-new-tree buffer current-tree old-tree))))
(defun origami-redo (buffer)
"Redo the last folding operation applied to BUFFER. You can
only redo undone operations while a new folding operation hasn't
been performed to BUFFER."
(interactive (list (current-buffer)))
(let ((current-tree (origami-get-cached-tree buffer)))
(origami-update-history buffer (lambda (h) (origami-h-redo h)))
(let ((new-tree (origami-get-cached-tree buffer)))
(origami-apply-new-tree buffer current-tree new-tree))))
(defun origami-reset (buffer)
"Remove all folds from BUFFER and reset all origami state
associated with this buffer. Useful during development or if you
uncover any bugs."
(interactive (list (current-buffer)))
(origami-setup-local-vars buffer)
(origami-remove-all-overlays buffer))
;;; minor mode
(defvar origami-mode-map
(let ((map (make-sparse-keymap)))
map)
"Keymap for `origami-mode'.")
(defcustom origami-mode-hook nil
"Hook called when origami minor mode is activated or deactivated."
:type 'hook
:group 'origami)
(defun origami-find-occurrence-show-node ()
(call-interactively 'origami-show-node))
;;;###autoload
(define-minor-mode origami-mode
"Minor mode to selectively hide/show text in the current buffer.
With a prefix argument ARG, enable the mode if ARG is positive,
and disable it otherwise. If called from Lisp, enable the mode
if ARG is omitted or nil.
Lastly, the normal hook `origami-mode-hook' is run using
`run-hooks'.
Key bindings:
\\{origami-mode-map}"
:group 'origami
:lighter nil
:keymap origami-mode-map
:init-value nil
(if origami-mode
(progn
(add-hook 'occur-mode-find-occurrence-hook
'origami-find-occurrence-show-node nil t)
(setq next-error-move-function (lambda (ignored pos)
(goto-char pos)
(call-interactively 'origami-show-node)))
(add-hook 'clone-indirect-buffer-hook
(lambda () (origami-reset (current-buffer)))))
(remove-hook 'occur-mode-find-occurrence-hook
'origami-find-occurrence-show-node t)
(setq next-error-move-function nil))
(origami-reset (current-buffer)))
;;;###autoload
(define-global-minor-mode global-origami-mode origami-mode
(lambda () (origami-mode 1)))
(provide 'origami)
;;; origami.el ends here