aboutsummaryrefslogtreecommitdiffhomepage
path: root/lib/holes.el
blob: 9a45037ccc1818035de14fe65d840f114c8b0981 (plain)
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
;;; holes.el --- a little piece of elisp to define holes in your buffer

;; This file is part of Proof General.

;; Portions © Copyright 1994-2012  David Aspinall and University of Edinburgh
;; Portions © Copyright 2003, 2012, 2014  Free Software Foundation, Inc.
;; Portions © Copyright 2001-2017  Pierre Courtieu
;; Portions © Copyright 2010, 2016  Erik Martin-Dorel
;; Portions © Copyright 2011-2013, 2016-2017  Hendrik Tews
;; Portions © Copyright 2015-2017  Clément Pit-Claudel

;; This file uses spans, an interface for extent (XEmacs) and overlays
;; (emacs), by Healfdene Goguen for the proofgeneral mode.
;;
;; Credits also to Stefan Monnier for great help in making this file
;; cleaner.
;;
;; Further cleanups by David Aspinall.

;; This software is free software; you can redistribute it and/or
;; modify it under the terms of the GNU General Public
;; License version 2, as published by the Free Software Foundation.
;;
;; This software 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 version 2 for more details
;; (enclosed in the file GPL).

;;; Commentary:
;;
;; See documentation in variable holes-short-doc.
;;
;; See documentation of `holes-mode'.

;;; Code:
(require 'span)
(require 'cl)

;;;
;;; initialization
;;;

(defvar holes-default-hole
  (let ((ol (make-overlay 0 0)))
    (delete-overlay ol) ol)
  "An empty detached hole used as the default hole.
You should not use this variable.")

(defvar holes-active-hole holes-default-hole
  "The current active hole.
There can be only one active hole at a time,
and this is this one.  This is not buffer local.")


;;;
;;; Customizable
;;;

(defgroup holes nil
  "Customization for Holes minor mode."
  :prefix "holes-"
  :group 'editing)

(defcustom holes-empty-hole-string "#"
  "String to be inserted for empty hole (don't put an empty string)."
  :type 'string
  :group 'holes)

(defcustom holes-empty-hole-regexp "#\\|@{\\([^{}]*\\)}"
  "Regexp denoting a hole in abbrevs.
Subgroup 1 is treated specially: if it matches, it is assumed that
everything before it and after it in the regexp matches delimiters
which should be removed when making the text into a hole."
  :type 'regexp
  :group 'holes)


;(defcustom holes-search-limit 1000
;  "Number of chars to look forward when looking for the next hole, unused for now.")
;unused for the moment

;; The following is customizable by a command of the form:
;;for dark background
;;(custom-set-faces
;; '(holes-active-hole-face
;;   ((((type x) (class color) (background light))
;;     (:background "paleVioletRed")))
;;   )
;; )

(defface active-hole-face
  '((((class grayscale) (background light)) :background "dimgrey")
    (((class grayscale) (background dark))  :background "LightGray")
    (((class color) (background dark))
     :background "darkred" :foreground "white")
    (((class color) (background light))
     :background "paleVioletRed" :foreground "black"))
  "Font Lock face used to highlight the active hole."
  :group 'holes)

(defface inactive-hole-face
  '((((class grayscale) (background light)) :background "lightgrey")
    (((class grayscale) (background dark))  :background "Grey")
    (((class color) (background dark))
     :background "mediumblue" :foreground "white")
    (((class color) (background light))
     :background "lightsteelblue" :foreground "black"))
  "Font Lock  face used to highlight the active hole."
  :group 'holes)

;;;
;;; Keymaps
;;;

(defvar hole-map
  (let ((map (make-sparse-keymap)))
    (define-key map [(mouse-1)] 'holes-mouse-set-active-hole)
    (define-key map [(mouse-3)] 'holes-mouse-destroy-hole)
    (define-key map [(mouse-2)] 'holes-mouse-forget-hole)
    map)
  "Keymap to use on the holes's overlays.
This keymap is used only when point is on a hole.
See `holes-mode-map' for the keymap of `holes-mode'.")

(defvar holes-mode-map
  (let ((map (make-sparse-keymap)))
    (define-key map [(control c) (h)] 'holes-set-make-active-hole)
    (define-key map [(control c) (control y)] 'holes-replace-update-active-hole)
    (define-key map [(control meta down-mouse-1)]
      'holes-mouse-set-make-active-hole)
    (define-key map [(control meta shift down-mouse-1)]
      'holes-mouse-replace-active-hole)
    (define-key map [(control c) (control j)]
      'holes-set-point-next-hole-destroy)
    map)
  "Keymap of `holes-mode'.

This one is active whenever we are on a buffer where `holes-mode' is active.

This is not the keymap used on holes's overlay (see `hole-map' instead).")

(easy-menu-define nil (list holes-mode-map)
  "Menu used in Holes minor mode."
  '("Holes"
     ;; da: I tidied this menu a bit.  I personally think this "trick"
     ;; of inserting strings to add documentation looks like a real
     ;; mess in menus ... I've removed it for the three below since
     ;; the docs below appear in popup in messages anyway.
     ;;
     ;; "Make a hole active   click on it"
     ;; "Disable a hole   click on it (button 2)"
     ;; "Destroy a hole   click on it (button 3)"
     ["Make Hole At Point"  holes-set-make-active-hole t]
     ["Make Selection A Hole"  holes-set-make-active-hole t]
     ["Replace Active Hole By Selection"  holes-replace-update-active-hole t]
     ["Jump To Active Hole"  holes-set-point-next-hole-destroy t]
     ["Forget All Holes"  holes-clear-all-buffer-holes t]
     ;; look a bit better at the bottom
     "---"
     ["About Holes" holes-show-doc t]
     "Hint: make hole with mouse: C-M-select"
     "Hint: replace hole with mouse: C-M-Shift-select"
     ))



;;;
;;; Utility functions
;;;

(defun holes-region-beginning-or-nil ()
  "Return the beginning of the acitve region, or nil."
  (and mark-active (region-beginning)))

(defun holes-region-end-or-nil ()
  "Return the end of the acitve region, or nil."
  (and mark-active (region-end)))

(defun holes-copy-active-region ()
  "Copy and retuurn the active region."
  (assert mark-active nil "the region is not active now.")
  (copy-region-as-kill (region-beginning) (region-end))
  (car kill-ring))

(defun holes-is-hole-p (span)
  "Non-nil if SPAN is a HOLE."
  (span-property span 'hole))

(defun holes-hole-start-position (hole)
  "Return start position of HOLE."
  (assert (holes-is-hole-p hole) t
	  "holes-hole-start-position: %s is not a hole")
  (span-start hole))

(defun holes-hole-end-position (hole)
  "Return end position of HOLE."
  (assert (holes-is-hole-p hole) t "holes-hole-end-position: %s is not a hole")
  (span-end hole))

(defun holes-hole-buffer (hole)
  "Return the buffer of HOLE."
  "Internal."
  (assert (holes-is-hole-p hole) t "holes-hole-buffer: %s is not a hole")
  (span-buffer hole))

(defun holes-hole-at (&optional pos)
  "Return the hole (a span) at POS in current buffer.
If pos is not in a hole raises an error."
  (span-at (or pos (point)) 'hole))

(defun holes-active-hole-exist-p ()
  "Return t if the active hole exists and is not empty (ie detached).
Use this to know if the active hole is set and usable (don't use the
active-hole-marker variable)."
  (not (span-detached-p holes-active-hole)))


(defun holes-active-hole-start-position ()
  "Return the position of the start of the active hole.
See `active-hole-buffer' to get its buffer.  Returns an error if
active hole doesn't exist (the marker is set to nothing)."
  (assert (holes-active-hole-exist-p) t
	  "holes-active-hole-start-position: no active hole")
  (holes-hole-start-position holes-active-hole))

(defun holes-active-hole-end-position ()
  "Return the position of the start of the active hole.
See `active-hole-buffer' to get its buffer.  Returns an error if
active hole doesn't exist (the marker is set to nothing)."
  (assert (holes-active-hole-exist-p) t
	  "holes-active-hole-end-position: no active hole")
  (holes-hole-end-position holes-active-hole))


(defun holes-active-hole-buffer ()
  "Return the buffer containing the active hole.
Raise an error if the active hole is not set.  Don't care if the
active hole is empty."
  (assert (holes-active-hole-exist-p) t
	  "holes-active-hole-buffer: no active hole")
  (holes-hole-buffer holes-active-hole))

(defun holes-goto-active-hole ()
  "Set point to active hole.
Raises an error if active-hole is not set."
  (interactive)
  (assert (holes-active-hole-exist-p) t
	  "holes-goto-active-hole: no active hole")
  (goto-char (holes-active-hole-start-position)))


(defun holes-highlight-hole-as-active (hole)
  "Highlight a HOLE with the `active-hole-face'.
DON'T USE this as it would break synchronization (non active hole
highlighted)."
  (assert (holes-is-hole-p hole) t
	  "holes-highlight-hole-as-active: %s is not a hole")
  (set-span-face hole 'active-hole-face))

(defun holes-highlight-hole (hole)
  "Highlight a HOLE with the not active face.
DON'T USE this as it would break synchronization (active hole non
highlighted)."
  (assert (holes-is-hole-p hole) t
	  "holes-highlight-hole: %s is not a hole")
  (set-span-face hole 'inactive-hole-face))

(defun holes-disable-active-hole ()
  "Disable the active hole.
The goal remains but is not the active one anymore.  Does nothing if
the active hole is already disable."
  (if (not (holes-active-hole-exist-p))
      ()
    ;; HACK: normal hole color, this way undo will show this hole
    ;; normally and not as active hole.  Ideally, undo should restore
    ;; the active hole, but it doesn't, so we put the 'not active'
    ;; color.
    (holes-highlight-hole holes-active-hole)
    (setq holes-active-hole holes-default-hole)))

(defun holes-set-active-hole (hole)
  "Set active hole to HOLE.
Error if HOLE is not a hole."
  (assert (holes-is-hole-p hole) t
	  "holes-set-active-hole: %s is not a hole")
  (if (holes-active-hole-exist-p)
      (holes-highlight-hole holes-active-hole))
  (setq holes-active-hole hole)
  (holes-highlight-hole-as-active holes-active-hole))

(defun holes-is-in-hole-p (&optional pos)
  "Return non-nil if POS (default: point) is in a hole, nil otherwise."
  (holes-hole-at pos))

(defun holes-make-hole (start end)
  "Make and return an (span) hole from START to END."
  (let ((ext (span-make start end)))
    (set-span-properties
     ext `(hole t
	   mouse-face highlight
	   priority 100 ;; what should I put here? I want big priority
	   face secondary-selection
	   start-open nil
	   end-open t
	   duplicable t
	   evaporate t  ;; really disappear if empty
	   ;; pointer frame-icon-glyph
	   keymap ,hole-map
	   help-echo "this is a \"hole\", button 2 to forget, button 3 to destroy, button 1 to make active"
	   'balloon-help "this is a \"hole\", button 2 to forget, button 3 to destroy, button 1 to make active"))
    ext))

(defun holes-make-hole-at (&optional start end)
  "Make a hole from START to END.
If no arg default hole after point.  If only one arg: error.  Return
the span."
  (interactive)
  (let* ((rstart (or start (holes-region-beginning-or-nil) (point)))
	 (rend (or end (holes-region-end-or-nil) (point))))
    (if (eq rstart rend)
	(progn
	  (goto-char rstart)
	  (insert holes-empty-hole-string)
	  (setq rend (point))))
    (holes-make-hole rstart rend)))

(defun holes-clear-hole (hole)
  "Clear the HOLE."
  (assert (holes-is-hole-p hole) t
	  "holes-clear-hole: %s is not a hole")
  (if (and (holes-active-hole-exist-p)
	   (eq holes-active-hole hole))
      (holes-disable-active-hole))
  (span-delete hole))

(defun holes-clear-hole-at (&optional pos)
  "Clear hole at POS (default=point)."
  (interactive)
  (if (not (holes-is-in-hole-p (or pos (point))))
      (error "Holes-clear-hole-at: no hole here"))
  (holes-clear-hole (holes-hole-at (or pos (point)))))


(defun holes-map-holes (function &optional buffer from to)
  "Map function FUNCTION across holes in buffer BUFFER.
Operate between character positions FROM and TO."
  (fold-spans function buffer from to nil nil 'hole))

(defun holes-clear-all-buffer-holes (&optional start end)
  "Clear all holes leaving their contents.
Operate betwenn START and END if non nil."
  (interactive)
  (holes-disable-active-hole)
  (span-mapcar-spans
   'holes-clear-hole (or start (point-min)) (or end (point-max))
   'hole))

;; limit ?
(defun holes-next (pos buffer)
  "Return the first hole after POS in BUFFER.
Or after the hole at pos if there is one (default pos=point).  If no
hole found, return nil."
  (holes-map-holes
   (lambda (h x) (and (holes-is-hole-p h) h)) buffer pos))

(defun holes-next-after-active-hole ()
  "Internal."
  (assert (holes-active-hole-exist-p) t
	  "next-active-hole: no active hole")
  (holes-next (holes-active-hole-end-position)
	      (holes-active-hole-buffer)))

(defun holes-set-active-hole-next (&optional buffer pos)
  "Set the active hole in BUFFER to the first hole after POS.
Default pos = point and buffer = current."
  (interactive)
  (let ((nxthole (holes-next (or pos (point))
			     (or buffer (current-buffer)))))
    (if nxthole
	(holes-set-active-hole nxthole)
      (holes-disable-active-hole))))

;;;(defun holes-set-active-hole-next-after-active ()
;;  "sets the active hole to the first hole after active
;;  hole.";;;;

;;;  (interactive)
;;  (holes-next-after-active-hole)
;;)


(defun holes-replace-segment (start end str &optional buffer)
  "Erase chars between START and END, and replace them with STR.
Operate in buffer BUFFER."
  (with-current-buffer (or buffer (current-buffer))
    (goto-char end)
    ;; Insert before deleting, so the markers at `start' and `end'
    ;; don't get mixed up together.
    (insert str)
    (delete-region start end)))

(defun holes-replace (str &optional thehole)
  "Replace the current hole by STR, replace THEHOLE instead if given.
Do not use this, it breaks the right colorization of the active
goal(FIXME?).  Use `replace-active-hole' instead."
  (if (and (not thehole)
	   (not (holes-active-hole-exist-p)))
      (error "No hole to fill")
    ;; defensive: replacing the hole should make it detached and
    ;; therefore inexistent.  other reason: this is a hack: unhighlight
    ;; so that undo wont show it highlighted)
    (if (and (holes-active-hole-exist-p)
	     thehole
	     (eq holes-active-hole thehole))
	(holes-disable-active-hole)
      )
    (let ((exthole (or thehole holes-active-hole)))
      (holes-replace-segment (holes-hole-start-position exthole)
			     (holes-hole-end-position exthole)
			     (or str (car kill-ring)) ;kill ring?
			     (span-buffer exthole)
			     )
      (span-detach exthole) ;; this seems necessary for span overlays,
      ;; where the buffer attached to the span is not removed
      ;; automatically by the fact that the span is removed from the
      ;; buffer (holes-replace-segment should perhaps take care of
      ;; that)
      )))

(defun holes-replace-active-hole (&optional str)
  "Replace the active hole by STR, if no str is given, then put the selection instead."
  (if (not (holes-active-hole-exist-p)) nil
    (holes-replace
     (or str (current-kill 0) (error "Nothing to put in hole"))
     holes-active-hole)))

(defun holes-replace-update-active-hole (&optional str)
  "Replace the active hole by STR.
Like `holes-replace-active-hole', but then sets the active hole to the
following hole if it exists."
  (interactive)
  (assert (holes-active-hole-exist-p) t
	  "holes-replace-update-active-hole: no active hole")
  (if (holes-active-hole-exist-p)
      (let ((nxthole (holes-next-after-active-hole)))
	(holes-replace-active-hole
	 (or str
	     (and mark-active
		  (holes-copy-active-region))
	     (current-kill 0)
	     (error "Nothing to put in hole")))
	(if nxthole (holes-set-active-hole nxthole)
	  (setq holes-active-hole holes-default-hole)))))

(defun holes-delete-update-active-hole ()
  "Deletes the active hole and supresses its content.
Sets `holes-active-hole' to the next hole if it exists."
  (interactive)
  (holes-replace-update-active-hole ""))


;;;###autoload
(defun holes-set-make-active-hole (&optional start end)
  "Make a new hole between START and END or at point, and make it active."

  (interactive)
  (holes-set-active-hole (holes-make-hole-at start end)))


;; mouse stuff, I want to make something close to `mouse-track-insert'
;; of XEmacs, but with modifier ctrl-meta and ctrl-meta-shift

;; Emacs and XEmacs have different ways of dealing with mouse
;; selection, but `mouse-track'(XEmacs) mouse-drag-region(Emacs)
;; have nearly the same meaning for me.  So I define this
;; track-mouse-selection.

(defalias 'holes-track-mouse-selection 'mouse-drag-track)
(defsubst holes-track-mouse-clicks ()
  "See `mouse-track-click-count'."
  (+ mouse-selection-click-count 1))

(defun holes-mouse-replace-active-hole (event)
  "Replace the active hole with one under mouse EVENT."
  (interactive "*e")
  (holes-track-mouse-selection event)
  (save-excursion
    ;;HACK: nothing if one click (but a second is perhaps coming)
    (if (and (eq (holes-track-mouse-clicks) 1)
	     (not mark-active))
	()
      (if (not mark-active)
	  (error "Nothing to put in hole")
	(holes-replace-update-active-hole (current-kill 0))
	(message "hole replaced")))))

(defun holes-destroy-hole (&optional span)
  "Destroy the hole SPAN."
  (interactive)
  (let* ((sp (or span (holes-hole-at (point))
		 (error "No hole to destroy"))))
    (save-excursion
      (if (and (holes-active-hole-exist-p)
	       (eq sp holes-active-hole))
	  (holes-disable-active-hole))
      (holes-replace "" sp)
      (span-detach sp))
    (message "hole killed")))


(defsubst holes-hole-at-event (event)
  "Return the hole at EVENT."
  (span-at-event event 'hole))

(defun holes-mouse-destroy-hole (event)
  "Destroy the hole at EVENT."
  (interactive "*e")
  (holes-destroy-hole (holes-hole-at-event event)))

;;;(span-at-event EVENT &optional PROPERTY BEFORE AT-FLAG)
;;comprend pas??
(defun holes-mouse-forget-hole (event)
  "Delete and deactivate the hole at EVENT."
  (interactive "*e")
  (save-excursion
    (let ((ext (holes-hole-at-event event)))
      (if (eq ext holes-active-hole)
	  (holes-disable-active-hole))
      (span-detach ext)))
  (message "hole deleted"))

(defun holes-mouse-set-make-active-hole (event)
  "Make a new hole at EVENT click activate it."
  (interactive "*e")
  (holes-track-mouse-selection event)

  (if (and (eq (holes-track-mouse-clicks) 1)
	   (not mark-active))
      (holes-set-make-active-hole (point) (point))

    (if mark-active
	(holes-set-make-active-hole)
      (let ((ext (holes-hole-at-event event)))
	(if (and ext (holes-is-hole-p ext))
	    (error "Already a hole here")
	  (holes-set-active-hole (holes-make-hole-at)))))))

(defun holes-mouse-set-active-hole (event)
  "Make the hole at EVENT click active."
  (interactive "*e")
  (let ((ext (holes-hole-at-event event)))
    (if (and ext (holes-is-hole-p ext))
	(holes-set-active-hole ext)
      (error "No hole here"))))


(defun holes-set-point-next-hole-destroy ()
  "Move the point to current active hole (if any and if in current buffer).
Destroy it and makes the next hole active if any."
  (interactive)
  (assert (holes-active-hole-exist-p) nil "no active hole")
  (assert (eq (current-buffer) (holes-active-hole-buffer)) nil
	  "active hole not in this buffer")
  (holes-goto-active-hole)
  (holes-delete-update-active-hole))


;; utilities to be used in conjunction with abbrevs.
;; The idea is to put abbrevs of the form:
;;(define-abbrev-table 'tuareg-mode-abbrev-table
;;      '(
;;	("l" "let # = # in" replace-#-after-abbrev2 0)
;;	)
;;      )
;; where replace-#-after-abbrev2 should be a function which replace the
;; two #'s (two occurences going backward from abbrev expantion point)
;; by holes and leave point at the first # (deleting
;; it).  holes-set-point-next-hole-destroy allow to go to the next hole.

;;following function allow to replace occurrences of a string by a
;;hole.

(defun holes-replace-string-by-holes-backward (limit)
  "Change each occurrence of REGEXP into a hole.
Sets the active hole to the last created hole and unsets it if no hole is
created.  Return the number of holes created.
The LIMIT argument bounds the search; it is a buffer position.
  The match found must not begin before that position.  A value of nil
  means search to the beginning of the accessible portion of the buffer."
  (holes-disable-active-hole)
  (let ((n 0))
    (save-excursion
      (while (re-search-backward holes-empty-hole-regexp limit t)
	(incf n)
	(if (not (match-end 1))
	    (holes-make-hole (match-beginning 0) (match-end 0))
	  (holes-make-hole (match-beginning 1) (match-end 1))
	  ;; delete end first to avoid shifting of marks
	  (delete-region (match-end 1) (match-end 0))
	  (delete-region (match-beginning 0) (match-beginning 1)))
	(holes-set-active-hole-next)))
    n))

(defun holes-skeleton-end-hook ()
  "Default hook after a skeleton insertion: put holes at each interesting position."
  ;; Not all versions of skeleton provide `skeleton-positions' and the
  ;; corresponding @ operation :-(
  (when (boundp 'skeleton-positions)
    (dolist (pos skeleton-positions)	;; put holes here
      (holes-set-make-active-hole pos pos))))

(defconst holes-jump-doc
  (concat "Hit \\[holes-set-point-next-hole-destroy] to jump "
	  "to active hole.  C-h v holes-doc to see holes doc.")
  "Shortcut reminder string for jumping to active hole.")



(defun holes-replace-string-by-holes-backward-jump (pos &optional noindent alwaysjump)
  "Put holes between POS and point, backward, indenting.
\"#\" and \"@{..}\" between this positions will become holes.
If NOINDENT is non-nil, skip the indenting step.
If ALWAYSJUMP is non-nil, jump to the first hole even if more than one."
  (unless noindent (save-excursion (indent-region pos (point) nil)))
  (let ((n (holes-replace-string-by-holes-backward pos)))
    (case n
      (0 nil)				; no hole, stay here.
      (1
       (goto-char pos)
       (holes-set-point-next-hole-destroy)) ; if only one hole, go to it.
      (t
       (goto-char pos)
       (when alwaysjump (holes-set-point-next-hole-destroy))
       (unless (active-minibuffer-window) ; otherwise minibuffer gets hidden
	 (message (substitute-command-keys
		   "\\[holes-set-point-next-hole-destroy] to jump to active hole.  \\[holes-short-doc] to see holes doc.")))))))


;;;###autoload
(define-minor-mode holes-mode
  "Toggle Holes minor mode.
With arg, turn Outline minor mode on if arg is positive, off otherwise.

The mode `holes-mode' is meant to help program editing.  It is
useful to build complicated expressions by copy pasting several
peices of text from different parts of a buffer (or even from
different buffers).

HOLES

A hole is a piece of (highlighted) text that may be replaced by
another part of text later.  There is no information stored on the
file for holes, so you can save and modify files containing holes with
no harm... You can even insert or delete characters inside holes like
any other characters.

USE

At any time only one particular hole, called \"active\", can be
\"filled\".  Holes can be in several buffers but there is always one or
zero active hole globally.  It is highlighted with a different color.

Functions described below have default shortcuts when `holes-mode' is
on that you can customize.

TO DEFINE A HOLE, two methods:

 o Select a region with keyboard or mouse, then use
   \\[holes-set-make-active-hole].  If the selected region is empty,
   then a hole containing # is created at point.

 o Select text with mouse while pressing ctrl and meta (`C-M-select').
   If the selected region is empty (i.e. if you just click while
   pressing ctrl+meta), then a hole containing # is created.

TO ACTIVATE A HOLE, click on it with the button 1 of your mouse.  The
previous active hole will be deactivated.

TO FORGET A HOLE without deleting its text, click on it with the
button 2 (middle) of your mouse.

TO DESTROY A HOLE and delete its text, click on it with the button 3
of your mouse.

TO FILL A HOLE with a text selection, first make sure it is active,
then two methods:

 o Select text with keyboard or mouse and hit
   \\[holes-replace-update-active-hole]

 o Select text with mouse while pressing ctrl, meta and shift
   (`C-M-S-select').  This is a
   generalization of the `mouse-track-insert' feature of XEmacs.  This
   method allows you to fill different holes faster than with the usual
   copy-paste method.

After replacement the next hole is automatically made active so you
can fill it immediately by hitting again
\\[holes-replace-update-active-hole] or `C-M-S-select'.

TO JUMP TO THE ACTIVE HOLE, just hit
\\[holes-set-point-next-hole-destroy].  You must
be in the buffer containing the active hole.  the point will move to
the active hole, and the active hole will be destroyed so you can type
something to put at its place.  The following hole is automatically
made active, so you can hit \\[holes-set-point-next-hole-destroy]
again.

It is useful in combination with abbreviations.  For example in
`coq-mode' \"fix\" is an abbreviation for Fixpoint # (# : #) {struct #} :
# := #, where each # is a hole. Then hitting
\\[holes-set-point-next-hole-destroy] goes from one hole to the
following and you can fill-in each hole very quickly.

COMBINING HOLES AND SKELETONS

`holes' minor mode is made to work with minor mode `skeleton' minor
mode.

KNOWN BUGS

 o Don't try to make overlapping holes, it doesn't work. (what would
it mean anyway?)

 o Cutting or pasting a hole will not produce new holes, and
undoing on holes cannot make holes re-appear."
  nil " Holes" holes-mode-map
  :group 'holes
  (if holes-mode
      (add-hook 'skeleton-end-hook 'holes-skeleton-end-hook nil t)
    (remove-hook 'skeleton-end-hook 'holes-skeleton-end-hook t)
    (holes-clear-all-buffer-holes)))

;;;###autoload
(defun holes-abbrev-complete ()
  "Complete abbrev by putting holes and indenting.
Moves point at beginning of expanded text.  Put this function as
call-back for your abbrevs, and just expanded \"#\" and \"@{..}\" will
become holes."
  (if holes-mode
      (holes-replace-string-by-holes-backward-jump last-abbrev-location)))


;;;###autoload
(defun holes-insert-and-expand (s)
  "Insert S, expand it and replace #s and @{]s by holes."
  ;; insert the expansion of abbrev s, and replace #s by holes.  It was
  ;; possible to implement it with a simple ((insert s) (expand-abbrev))
  ;; but undo would show the 2 steps, which is bad.
  (let ((pos (point))
	(ins (abbrev-expansion s)))
    (insert (or ins s))
    (setq last-abbrev-location pos)
    (holes-abbrev-complete)))

(provide 'holes)

;;; holes.el ends here