]> code.delx.au - gnu-emacs/blob - lisp/cedet/srecode/fields.el
1b7715c39d3cb895f9b8b2290261acd7b38f1d3c
[gnu-emacs] / lisp / cedet / srecode / fields.el
1 ;;; srecode/fields.el --- Handling type-in fields in a buffer.
2 ;;
3 ;; Copyright (C) 2009-2016 Free Software Foundation, Inc.
4 ;;
5 ;; Author: Eric M. Ludlam <eric@siege-engine.com>
6
7 ;; This file is part of GNU Emacs.
8
9 ;; GNU Emacs is free software: you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation, either version 3 of the License, or
12 ;; (at your option) any later version.
13
14 ;; GNU Emacs is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 ;; GNU General Public License for more details.
18
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
21
22 ;;; Commentary:
23 ;;
24 ;; Idea courtesy of yasnippets.
25 ;;
26 ;; If someone prefers not to type unknown dictionary entries into
27 ;; mini-buffer prompts, it could instead use in-buffer fields.
28 ;;
29 ;; A template-region specifies an area in which the fields exist. If
30 ;; the cursor exits the region, all fields are cleared.
31 ;;
32 ;; Each field is independent, but some are linked together by name.
33 ;; Typing in one will cause the matching ones to change in step.
34 ;;
35 ;; Each field has 2 overlays. The second overlay allows control in
36 ;; the character just after the field, but does not highlight it.
37
38 ;; @TODO - Cancel an old field array if a new one is about to be created!
39
40 ;; Keep this library independent of SRecode proper.
41 (require 'eieio)
42 (require 'cl-generic)
43
44 ;;; Code:
45 (defvar srecode-field-archive nil
46 "While inserting a set of fields, collect in this variable.
47 Once an insertion set is done, these fields will be activated.")
48
49 \f
50 ;;; Customization
51 ;;
52
53 (defface srecode-field-face
54 '((((class color) (background dark))
55 (:underline "green"))
56 (((class color) (background light))
57 (:underline "green4")))
58 "*Face used to specify editable fields from a template."
59 :group 'semantic-faces)
60
61 (defcustom srecode-fields-exit-confirmation nil
62 "Ask for confirmation before leaving field editing mode."
63 :group 'srecode
64 :type 'boolean)
65
66 ;;; BASECLASS
67 ;;
68 ;; Fields and the template region share some basic overlay features.
69
70 (defclass srecode-overlaid ()
71 ((overlay :documentation
72 "Overlay representing this field.
73 The overlay will crossreference this object.")
74 )
75 "An object that gets automatically bound to an overlay.
76 Has virtual :start and :end initializers.")
77
78 (cl-defmethod initialize-instance ((olaid srecode-overlaid) &optional args)
79 "Initialize OLAID, being sure it archived."
80 ;; Extract :start and :end from the olaid list.
81 (let ((newargs nil)
82 (olay nil)
83 start end
84 )
85
86 (while args
87 (cond ((eq (car args) :start)
88 (setq args (cdr args))
89 (setq start (car args))
90 (setq args (cdr args))
91 )
92 ((eq (car args) :end)
93 (setq args (cdr args))
94 (setq end (car args))
95 (setq args (cdr args))
96 )
97 (t
98 (push (car args) newargs)
99 (setq args (cdr args))
100 (push (car args) newargs)
101 (setq args (cdr args)))
102 ))
103
104 ;; Create a temporary overlay now. We have to use an overlay and
105 ;; not a marker because of the in-front insertion rules. The rules
106 ;; are backward from what is wanted while typing.
107 (setq olay (make-overlay start end (current-buffer) t nil))
108 (overlay-put olay 'srecode-init-only t)
109
110 (oset olaid overlay olay)
111 (cl-call-next-method olaid (nreverse newargs))
112
113 ))
114
115 (cl-defmethod srecode-overlaid-activate ((olaid srecode-overlaid))
116 "Activate the overlaid area."
117 (let* ((ola (oref olaid overlay))
118 (start (overlay-start ola))
119 (end (overlay-end ola))
120 ;; Create a new overlay here.
121 (ol (make-overlay start end (current-buffer) nil t)))
122
123 ;; Remove the old one.
124 (delete-overlay ola)
125
126 (overlay-put ol 'srecode olaid)
127
128 (oset olaid overlay ol)
129
130 ))
131
132 (cl-defmethod srecode-delete ((olaid srecode-overlaid))
133 "Delete the overlay from OLAID."
134 (delete-overlay (oref olaid overlay))
135 (slot-makeunbound olaid 'overlay)
136 )
137
138 (cl-defmethod srecode-empty-region-p ((olaid srecode-overlaid))
139 "Return non-nil if the region covered by OLAID is of length 0."
140 (= 0 (srecode-region-size olaid)))
141
142 (cl-defmethod srecode-region-size ((olaid srecode-overlaid))
143 "Return the length of region covered by OLAID."
144 (let ((start (overlay-start (oref olaid overlay)))
145 (end (overlay-end (oref olaid overlay))))
146 (- end start)))
147
148 (cl-defmethod srecode-point-in-region-p ((olaid srecode-overlaid))
149 "Return non-nil if point is in the region of OLAID."
150 (let ((start (overlay-start (oref olaid overlay)))
151 (end (overlay-end (oref olaid overlay))))
152 (and (>= (point) start) (<= (point) end))))
153
154 (defun srecode-overlaid-at-point (class)
155 "Return a list of overlaid fields of type CLASS at point."
156 (let ((ol (overlays-at (point)))
157 (ret nil))
158 (while ol
159 (let ((tmp (overlay-get (car ol) 'srecode)))
160 (when (and tmp (object-of-class-p tmp class))
161 (setq ret (cons tmp ret))))
162 (setq ol (cdr ol)))
163 (car (nreverse ret))))
164
165 (cl-defmethod srecode-overlaid-text ((olaid srecode-overlaid) &optional set-to)
166 "Return the text under OLAID.
167 If SET-TO is a string, then replace the text of OLAID wit SET-TO."
168 (let* ((ol (oref olaid overlay))
169 (start (overlay-start ol)))
170 (if (not (stringp set-to))
171 ;; Just return it.
172 (buffer-substring-no-properties start (overlay-end ol))
173 ;; Replace it.
174 (save-excursion
175 (delete-region start (overlay-end ol))
176 (goto-char start)
177 (insert set-to)
178 (move-overlay ol start (+ start (length set-to))))
179 nil)))
180
181 ;;; INSERTED REGION
182 ;;
183 ;; Managing point-exit, and flushing fields.
184
185 (defclass srecode-template-inserted-region (srecode-overlaid)
186 ((fields :documentation
187 "A list of field overlays in this region.")
188 (active-region :allocation :class
189 :initform nil
190 :documentation
191 "The template region currently being handled.")
192 )
193 "Manage a buffer region in which fields exist.")
194
195 (cl-defmethod initialize-instance ((ir srecode-template-inserted-region)
196 &rest args)
197 "Initialize IR, capturing the active fields, and creating the overlay."
198 ;; Fill in the fields
199 (oset ir fields srecode-field-archive)
200 (setq srecode-field-archive nil)
201
202 ;; Initialize myself first.
203 (cl-call-next-method)
204 )
205
206 (cl-defmethod srecode-overlaid-activate ((ir srecode-template-inserted-region))
207 "Activate the template area for IR."
208 ;; Activate all our fields
209
210 (dolist (F (oref ir fields))
211 (srecode-overlaid-activate F))
212
213 ;; Activate our overlay.
214 (cl-call-next-method)
215
216 ;; Position the cursor at the first field
217 (let ((first (car (oref ir fields))))
218 (goto-char (overlay-start (oref first overlay))))
219
220 ;; Set ourselves up as 'active'
221 (oset ir active-region ir)
222
223 ;; Setup the post command hook.
224 (add-hook 'post-command-hook 'srecode-field-post-command t t)
225 )
226
227 (cl-defmethod srecode-delete ((ir srecode-template-inserted-region))
228 "Call into our base, but also clear out the fields."
229 ;; Clear us out of the baseclass.
230 (oset ir active-region nil)
231 ;; Clear our fields.
232 (mapc 'srecode-delete (oref ir fields))
233 ;; Call to our base
234 (cl-call-next-method)
235 ;; Clear our hook.
236 (remove-hook 'post-command-hook 'srecode-field-post-command t)
237 )
238
239 (defsubst srecode-active-template-region ()
240 "Return the active region for template fields."
241 (oref-default 'srecode-template-inserted-region active-region))
242
243 (defun srecode-field-post-command ()
244 "Srecode field handler in the post command hook."
245 (let ((ar (srecode-active-template-region))
246 )
247 (if (not ar)
248 ;; Find a bug and fix it.
249 (remove-hook 'post-command-hook 'srecode-field-post-command t)
250 (if (srecode-point-in-region-p ar)
251 nil ;; Keep going
252 ;; We moved out of the template. Cancel the edits.
253 (srecode-delete ar)))
254 ))
255
256 ;;; FIELDS
257
258 (defclass srecode-field (srecode-overlaid)
259 ((tail :documentation
260 "Overlay used on character just after this field.
261 Used to provide useful keybindings there.")
262 (name :initarg :name
263 :documentation
264 "The name of this field.
265 Usually initialized from the dictionary entry name that
266 the users needs to edit.")
267 (prompt :initarg :prompt
268 :documentation
269 "A prompt string to use if this were in the minibuffer.
270 Display when the cursor enters this field.")
271 (read-fcn :initarg :read-fcn
272 :documentation
273 "A function that would be used to read a string.
274 Try to use this to provide useful completion when available.")
275 )
276 "Representation of one field.")
277
278 (defvar srecode-field-keymap
279 (let ((km (make-sparse-keymap)))
280 (define-key km "\C-i" 'srecode-field-next)
281 (define-key km "\M-\C-i" 'srecode-field-prev)
282 (define-key km "\C-e" 'srecode-field-end)
283 (define-key km "\C-a" 'srecode-field-start)
284 (define-key km "\M-m" 'srecode-field-start)
285 (define-key km "\C-c\C-c" 'srecode-field-exit-ask)
286 km)
287 "Keymap applied to field overlays.")
288
289 (cl-defmethod initialize-instance ((field srecode-field) &optional args)
290 "Initialize FIELD, being sure it archived."
291 (add-to-list 'srecode-field-archive field t)
292 (cl-call-next-method)
293 )
294
295 (cl-defmethod srecode-overlaid-activate ((field srecode-field))
296 "Activate the FIELD area."
297 (cl-call-next-method)
298
299 (let* ((ol (oref field overlay))
300 (end nil)
301 (tail nil))
302 (overlay-put ol 'face 'srecode-field-face)
303 (overlay-put ol 'keymap srecode-field-keymap)
304 (overlay-put ol 'modification-hooks '(srecode-field-mod-hook))
305 (overlay-put ol 'insert-behind-hooks '(srecode-field-behind-hook))
306 (overlay-put ol 'insert-in-front-hooks '(srecode-field-mod-hook))
307
308 (setq end (overlay-end ol))
309 (setq tail (make-overlay end (+ end 1) (current-buffer)))
310
311 (overlay-put tail 'srecode field)
312 (overlay-put tail 'keymap srecode-field-keymap)
313 (overlay-put tail 'face 'srecode-field-face)
314 (oset field tail tail)
315 )
316 )
317
318 (cl-defmethod srecode-delete ((olaid srecode-field))
319 "Delete our secondary overlay."
320 ;; Remove our spare overlay
321 (delete-overlay (oref olaid tail))
322 (slot-makeunbound olaid 'tail)
323 ;; Do our baseclass work.
324 (cl-call-next-method)
325 )
326
327 (defvar srecode-field-replication-max-size 100
328 "Maximum size of a field before canceling replication.")
329
330 (defun srecode-field-mod-hook (ol after start end &optional pre-len)
331 "Modification hook for the field overlay.
332 OL is the overlay.
333 AFTER is non-nil if it is called after the change.
334 START and END are the bounds of the change.
335 PRE-LEN is used in the after mode for the length of the changed text."
336 (when (and after (not undo-in-progress))
337 (let* ((field (overlay-get ol 'srecode))
338 (inhibit-point-motion-hooks t)
339 (inhibit-modification-hooks t)
340 )
341 ;; Sometimes a field is deleted, but we might still get a stray
342 ;; event. Let's just ignore those events.
343 (when (slot-boundp field 'overlay)
344 ;; First, fixup the two overlays, in case they got confused.
345 (let ((main (oref field overlay))
346 (tail (oref field tail)))
347 (move-overlay main
348 (overlay-start main)
349 (1- (overlay-end tail)))
350 (move-overlay tail
351 (1- (overlay-end tail))
352 (overlay-end tail)))
353 ;; Now capture text from the main overlay, and propagate it.
354 (let* ((new-text (srecode-overlaid-text field))
355 (region (srecode-active-template-region))
356 (allfields (when region (oref region fields)))
357 (name (oref field name)))
358 (dolist (F allfields)
359 (when (and (not (eq F field))
360 (string= name (oref F name)))
361 (if (> (length new-text) srecode-field-replication-max-size)
362 (message "Field size too large for replication.")
363 ;; If we find other fields with the same name, then keep
364 ;; then all together. Disable change hooks to make sure
365 ;; we don't get a recursive edit.
366 (srecode-overlaid-text F new-text)
367 ))))
368 ))))
369
370 (defun srecode-field-behind-hook (ol after start end &optional pre-len)
371 "Modification hook for the field overlay.
372 OL is the overlay.
373 AFTER is non-nil if it is called after the change.
374 START and END are the bounds of the change.
375 PRE-LEN is used in the after mode for the length of the changed text."
376 (when after
377 (let* ((field (overlay-get ol 'srecode))
378 )
379 (move-overlay ol (overlay-start ol) end)
380 (srecode-field-mod-hook ol after start end pre-len))
381 ))
382
383 (cl-defmethod srecode-field-goto ((field srecode-field))
384 "Goto the FIELD."
385 (goto-char (overlay-start (oref field overlay))))
386
387 (defun srecode-field-next ()
388 "Move to the next field."
389 (interactive)
390 (let* ((f (srecode-overlaid-at-point 'srecode-field))
391 (tr (srecode-overlaid-at-point 'srecode-template-inserted-region))
392 )
393 (when (not f) (error "Not in a field"))
394 (when (not tr) (error "Not in a template region"))
395
396 (let ((fields (oref tr fields)))
397 (while fields
398 ;; Loop over fields till we match. Then move to the next one.
399 (when (eq f (car fields))
400 (if (cdr fields)
401 (srecode-field-goto (car (cdr fields)))
402 (srecode-field-goto (car (oref tr fields))))
403 (setq fields nil)
404 )
405 (setq fields (cdr fields))))
406 ))
407
408 (defun srecode-field-prev ()
409 "Move to the prev field."
410 (interactive)
411 (let* ((f (srecode-overlaid-at-point 'srecode-field))
412 (tr (srecode-overlaid-at-point 'srecode-template-inserted-region))
413 )
414 (when (not f) (error "Not in a field"))
415 (when (not tr) (error "Not in a template region"))
416
417 (let ((fields (reverse (oref tr fields))))
418 (while fields
419 ;; Loop over fields till we match. Then move to the next one.
420 (when (eq f (car fields))
421 (if (cdr fields)
422 (srecode-field-goto (car (cdr fields)))
423 (srecode-field-goto (car (oref tr fields))))
424 (setq fields nil)
425 )
426 (setq fields (cdr fields))))
427 ))
428
429 (defun srecode-field-end ()
430 "Move to the end of this field."
431 (interactive)
432 (let* ((f (srecode-overlaid-at-point 'srecode-field)))
433 (goto-char (overlay-end (oref f overlay)))))
434
435 (defun srecode-field-start ()
436 "Move to the end of this field."
437 (interactive)
438 (let* ((f (srecode-overlaid-at-point 'srecode-field)))
439 (goto-char (overlay-start (oref f overlay)))))
440
441 (defun srecode-field-exit-ask ()
442 "Ask if the user wants to exit field-editing mini-mode."
443 (interactive)
444 (when (or (not srecode-fields-exit-confirmation)
445 (y-or-n-p "Exit field-editing mode? "))
446 (srecode-delete (srecode-active-template-region))))
447
448
449 (provide 'srecode/fields)
450
451 ;; Local variables:
452 ;; generated-autoload-load-name: "srecode/fields"
453 ;; End:
454
455 ;;; srecode/fields.el ends here