]> code.delx.au - gnu-emacs/blob - lisp/cedet/srecode/insert.el
Merge from origin/emacs-25
[gnu-emacs] / lisp / cedet / srecode / insert.el
1 ;;; srecode/insert.el --- Insert srecode templates to an output stream.
2
3 ;; Copyright (C) 2005, 2007-2016 Free Software Foundation, Inc.
4
5 ;; Author: Eric M. Ludlam <zappo@gnu.org>
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 ;; Define and implements specific inserter objects.
25 ;;
26 ;; Manage the insertion process for a template.
27 ;;
28
29 (eval-when-compile
30 (require 'cl)) ;; for `lexical-let'
31
32 (require 'srecode/compile)
33 (require 'srecode/find)
34 (require 'srecode/dictionary)
35 (require 'srecode/args)
36 (require 'srecode/filters)
37
38 (declare-function srecode-overlaid-activate "srecode/fields")
39 (declare-function srecode-template-inserted-region "srecode/fields")
40
41 ;;; Code:
42
43 (defcustom srecode-insert-ask-variable-method 'ask
44 "Determine how to ask for a dictionary value when inserting a template.
45 Only the ASK style inserter will query the user for a value.
46 Dictionary value references that ask begin with the ? character.
47 Possible values are:
48 `ask' - Prompt in the minibuffer as the value is inserted.
49 `field' - Use the dictionary macro name as the inserted value,
50 and place a field there. Matched fields change together.
51
52 NOTE: The field feature does not yet work with XEmacs."
53 :group 'srecode
54 :type '(choice (const :tag "Ask" ask)
55 (const :tag "Field" field)))
56
57 (defvar srecode-insert-with-fields-in-progress nil
58 "Non-nil means that we are actively inserting a template with fields.")
59
60 ;;; INSERTION COMMANDS
61 ;;
62 ;; User level commands for inserting stuff.
63 (defvar srecode-insertion-start-context nil
64 "The context that was at point at the beginning of the template insertion.")
65
66 (defun srecode-insert-again ()
67 "Insert the previously inserted template (by name) again."
68 (interactive)
69 (let ((prev (car srecode-read-template-name-history)))
70 (if prev
71 (srecode-insert prev)
72 (call-interactively 'srecode-insert))))
73
74 ;;;###autoload
75 (defun srecode-insert (template-name &rest dict-entries)
76 "Insert the template TEMPLATE-NAME into the current buffer at point.
77 DICT-ENTRIES are additional dictionary values to add."
78 (interactive (list (srecode-read-template-name "Template Name: ")))
79 (if (not (srecode-table))
80 (error "No template table found for mode %s" major-mode))
81 (let ((newdict (srecode-create-dictionary))
82 (temp (srecode-template-get-table (srecode-table) template-name))
83 (srecode-insertion-start-context (srecode-calculate-context))
84 )
85 (if (not temp)
86 (error "No Template named %s" template-name))
87 (while dict-entries
88 (srecode-dictionary-set-value newdict
89 (car dict-entries)
90 (car (cdr dict-entries)))
91 (setq dict-entries (cdr (cdr dict-entries))))
92 (srecode-insert-fcn temp newdict)
93 ;; Don't put code here. We need to return the end-mark
94 ;; for this insertion step.
95 ))
96
97 (defun srecode-insert-fcn (template dictionary &optional stream skipresolver)
98 "Insert TEMPLATE using DICTIONARY into STREAM.
99 Optional SKIPRESOLVER means to avoid refreshing the tag list,
100 or resolving any template arguments. It is assumed the caller
101 has set everything up already."
102 ;; Perform the insertion.
103 (let ((standard-output (or stream (current-buffer)))
104 (end-mark nil))
105 ;; Merge any template entries into the input dictionary.
106 (when (slot-boundp template 'dictionary)
107 (srecode-dictionary-merge dictionary (oref template dictionary)))
108
109 (unless skipresolver
110 ;; Make sure the semantic tags are up to date.
111 (semantic-fetch-tags)
112 ;; Resolve the arguments
113 (srecode-resolve-arguments template dictionary))
114 ;; Insert
115 (if (bufferp standard-output)
116 ;; If there is a buffer, turn off various hooks. This will cause
117 ;; the mod hooks to be buffered up during the insert, but
118 ;; prevent tools like font-lock from fontifying mid-template.
119 ;; Especially important during insertion of complex comments that
120 ;; cause the new font-lock to comment-color stuff after the inserted
121 ;; comment.
122 ;;
123 ;; I'm not sure about the motion hooks. It seems like a good
124 ;; idea though.
125 ;;
126 ;; Borrowed these concepts out of font-lock.
127 ;;
128 ;; I tried `combine-after-change-calls', but it did not have
129 ;; the effect I wanted.
130 (let ((start (point)))
131 (let ((inhibit-point-motion-hooks t)
132 (inhibit-modification-hooks t)
133 )
134 (srecode--insert-into-buffer template dictionary)
135 )
136 ;; Now call those after change functions.
137 (run-hook-with-args 'after-change-functions
138 start (point) 0)
139 )
140 (srecode-insert-method template dictionary))
141 ;; Handle specialization of the POINT inserter.
142 (when (and (bufferp standard-output)
143 (slot-boundp 'srecode-template-inserter-point 'point)
144 )
145 (set-buffer standard-output)
146 (setq end-mark (point-marker))
147 (goto-char (oref-default 'srecode-template-inserter-point point)))
148 (oset-default 'srecode-template-inserter-point point eieio-unbound)
149
150 ;; Return the end-mark.
151 (or end-mark (point)))
152 )
153
154 (defun srecode--insert-into-buffer (template dictionary)
155 "Insert a TEMPLATE with DICTIONARY into a buffer.
156 Do not call this function yourself. Instead use:
157 `srecode-insert' - Inserts by name.
158 `srecode-insert-fcn' - Insert with objects.
159 This function handles the case from one of the above functions when
160 the template is inserted into a buffer. It looks
161 at `srecode-insert-ask-variable-method' to decide if unbound dictionary
162 entries ask questions or insert editable fields.
163
164 Buffer based features related to change hooks is handled one level up."
165 ;; This line prevents the field archive from being let bound
166 ;; while the field insert tool is loaded via autoloads during
167 ;; the insert.
168 (when (eq srecode-insert-ask-variable-method 'field)
169 (require 'srecode/fields))
170
171 (let ((srecode-field-archive nil) ; Prevent field leaks during insert
172 (start (point)) ; Beginning of the region.
173 )
174 ;; This sub-let scopes the 'in-progress' piece so we know
175 ;; when to setup the end-template.
176 (let ((srecode-insert-with-fields-in-progress
177 (if (eq srecode-insert-ask-variable-method 'field) t nil))
178 )
179 (srecode-insert-method template dictionary)
180 )
181 ;; If we are not in-progress, and we insert fields, then
182 ;; create the end-template with fields editable area.
183 (when (and (not srecode-insert-with-fields-in-progress)
184 (eq srecode-insert-ask-variable-method 'field) ; Only if user asked
185 srecode-field-archive ; Only if there were fields created
186 )
187 (let ((reg
188 ;; Create the field-driven editable area.
189 (srecode-template-inserted-region
190 "TEMPLATE" :start start :end (point))))
191 (srecode-overlaid-activate reg))
192 )
193 ;; We return with 'point being the end of the template insertion
194 ;; area. Return value is not important.
195 ))
196
197 (declare-function data-debug-new-buffer "data-debug" (name))
198 (declare-function data-debug-insert-stuff-list "data-debug" (stufflist prefix))
199 (declare-function data-debug-insert-thing "data-debug"
200 (thing prefix prebuttontext &optional parent))
201
202 (defun srecode-insert-show-error-report (dictionary format &rest args)
203 "Display an error report based on DICTIONARY, FORMAT and ARGS.
204 This is intended to diagnose problems with failed template
205 insertions."
206 (with-current-buffer (data-debug-new-buffer "*SRECODE INSERTION ERROR*")
207 (erase-buffer)
208 ;; Insert the stack of templates that are currently being
209 ;; inserted.
210 (insert (propertize "Template Stack" 'face '(:weight bold))
211 (propertize " (most recent at bottom)" 'face '(:slant italic))
212 ":\n")
213 (data-debug-insert-stuff-list
214 (reverse (oref-default 'srecode-template active)) "> ")
215 ;; Show the current dictionary.
216 (insert (propertize "Dictionary" 'face '(:weight bold)) "\n")
217 (data-debug-insert-thing dictionary "" "> ")
218 ;; Show the error message.
219 (insert (propertize "Error" 'face '(:weight bold)) "\n")
220 (insert (apply #'format-message format args))
221 (pop-to-buffer (current-buffer))))
222
223 (defun srecode-insert-report-error (dictionary format &rest args)
224 ;; TODO only display something when inside an interactive call?
225 (srecode-insert-show-error-report dictionary format args)
226 (apply #'error format args))
227
228 ;;; TEMPLATE ARGUMENTS
229 ;;
230 ;; Some templates have arguments. Each argument is associated with
231 ;; a function that can resolve the inputs needed.
232 (defun srecode-resolve-arguments (temp dict)
233 "Resolve all the arguments needed by the template TEMP.
234 Apply anything learned to the dictionary DICT."
235 (srecode-resolve-argument-list (oref temp args) dict temp))
236
237 (defun srecode-resolve-argument-list (args dict &optional temp)
238 "Resolve arguments in the argument list ARGS.
239 ARGS is a list of symbols, such as :blank, or :file.
240 Apply values to DICT.
241 Optional argument TEMP is the template that is getting its arguments resolved."
242 (let ((fcn nil))
243 (while args
244 (setq fcn (intern-soft (concat "srecode-semantic-handle-"
245 (symbol-name (car args)))))
246 (if (not fcn)
247 (error "Error resolving template argument %S" (car args)))
248 (if temp
249 (condition-case nil
250 ;; Allow some to accept a 2nd argument optionally.
251 ;; They throw an error if not available, so try again.
252 (funcall fcn dict temp)
253 (wrong-number-of-arguments (funcall fcn dict)))
254 (funcall fcn dict))
255 (setq args (cdr args)))
256 ))
257
258 ;;; INSERTION STACK & METHOD
259 ;;
260 ;; Code managing the top-level insert method and the current
261 ;; insertion stack.
262 ;;
263 (cl-defmethod srecode-push ((st srecode-template))
264 "Push the srecoder template ST onto the active stack."
265 (oset st active (cons st (oref st active))))
266
267 (cl-defmethod srecode-pop ((st srecode-template))
268 "Pop the srecoder template ST onto the active stack."
269 (oset st active (cdr (oref st active))))
270
271 (cl-defmethod srecode-peek ((st srecode-template))
272 "Fetch the topmost active template record."
273 (car (oref st active)))
274
275 (cl-defmethod srecode-insert-method ((st srecode-template) dictionary)
276 "Insert the srecoder template ST."
277 ;; Merge any template entries into the input dictionary.
278 ;; This may happen twice since some templates arguments need
279 ;; these dictionary values earlier, but these values always
280 ;; need merging for template inserting in other templates.
281 (when (slot-boundp st 'dictionary)
282 (srecode-dictionary-merge dictionary (oref st dictionary)))
283 ;; Do an insertion.
284 (unwind-protect
285 (let ((c (oref st code)))
286 (srecode-push st)
287 (srecode-insert-code-stream c dictionary))
288 ;; Popping the stack is protected.
289 (srecode-pop st)))
290
291 (defun srecode-insert-code-stream (code dictionary)
292 "Insert the CODE from a template into `standard-output'.
293 Use DICTIONARY to resolve any macros."
294 (while code
295 (cond ((stringp (car code))
296 (princ (car code)))
297 (t
298 (srecode-insert-method (car code) dictionary)))
299 (setq code (cdr code))))
300
301 ;;; INSERTERS
302 ;;
303 ;; Specific srecode inserters.
304 ;; The base class is from srecode-compile.
305 ;;
306 ;; Each inserter handles various macro codes from the template.
307 ;; The `code' slot specifies a character used to identify which
308 ;; inserter is to be created.
309 ;;
310 (defclass srecode-template-inserter-newline (srecode-template-inserter)
311 ((key :initform "\n"
312 :allocation :class
313 :documentation
314 "The character code used to identify inserters of this style.")
315 (hard :initform nil
316 :initarg :hard
317 :documentation
318 "Is this a hard newline (always inserted) or optional?
319 Optional newlines don't insert themselves if they are on a blank line
320 by themselves.")
321 )
322 "Insert a newline, and possibly do indenting.
323 Specify the :indent argument to enable automatic indentation when newlines
324 occur in your template.")
325
326 (cl-defmethod srecode-insert-method ((sti srecode-template-inserter-newline)
327 dictionary)
328 "Insert the STI inserter."
329 ;; To be safe, indent the previous line since the template will
330 ;; change what is there to indent
331 (let ((i (srecode-dictionary-lookup-name dictionary "INDENT"))
332 (inbuff (bufferp standard-output))
333 (doit t)
334 (pm (point-marker)))
335 (when (and inbuff (not (oref sti hard)))
336 ;; If this is not a hard newline, we need do the calculation
337 ;; and set "doit" to nil.
338 (beginning-of-line)
339 (save-restriction
340 (narrow-to-region (point) pm)
341 (when (looking-at "\\s-*$")
342 (setq doit nil)))
343 (goto-char pm)
344 )
345 ;; Do indentation regardless of the newline.
346 (when (and (eq i t) inbuff)
347 (indent-according-to-mode)
348 (goto-char pm))
349
350 (when doit
351 (princ "\n")
352 ;; Indent after the newline, particularly for numeric indents.
353 (cond ((and (eq i t) (bufferp standard-output))
354 ;; WARNING - indent according to mode requires that standard-output
355 ;; is a buffer!
356 ;; @todo - how to indent in a string???
357 (setq pm (point-marker))
358 (indent-according-to-mode)
359 (goto-char pm))
360 ((numberp i)
361 (princ (make-string i " ")))
362 ((stringp i)
363 (princ i))))))
364
365 (cl-defmethod srecode-dump ((ins srecode-template-inserter-newline) _indent)
366 "Dump the state of the SRecode template inserter INS."
367 (cl-call-next-method)
368 (when (oref ins hard)
369 (princ " : hard")
370 ))
371
372 (defclass srecode-template-inserter-blank (srecode-template-inserter)
373 ((key :initform "\r"
374 :allocation :class
375 :documentation
376 "The character representing this inserter style.
377 Can't be blank, or it might be used by regular variable insertion.")
378 (where :initform 'begin
379 :initarg :where
380 :documentation
381 "This should be `begin' or `end', indicating where to insert a CR.
382 When `begin', insert a CR if not at 'bol'.
383 When `end', insert a CR if not at 'eol'.")
384 ;; @TODO - Add slot and control for the number of blank
385 ;; lines before and after point.
386 )
387 "Insert a newline before and after a template, and possibly do indenting.
388 Specify the :blank argument to enable this inserter.")
389
390 (cl-defmethod srecode-insert-method ((sti srecode-template-inserter-blank)
391 dictionary)
392 "Make sure there is no text before or after point."
393 (let ((i (srecode-dictionary-lookup-name dictionary "INDENT"))
394 (inbuff (bufferp standard-output))
395 (pm (point-marker)))
396 (when (and inbuff
397 ;; Don't do this if we are not the active template.
398 (= (length (oref-default 'srecode-template active)) 1))
399
400 (when (and (eq i t) inbuff (not (eq (oref sti where) 'begin)))
401 (indent-according-to-mode)
402 (goto-char pm))
403
404 (cond ((and (eq (oref sti where) 'begin) (not (bolp)))
405 (princ "\n"))
406 ((eq (oref sti where) 'end)
407 ;; If there is whitespace after pnt, then clear it out.
408 (when (looking-at "\\s-*$")
409 (delete-region (point) (point-at-eol)))
410 (when (not (eolp))
411 (princ "\n")))
412 )
413 (setq pm (point-marker))
414 (when (and (eq i t) inbuff (not (eq (oref sti where) 'end)))
415 (indent-according-to-mode)
416 (goto-char pm))
417 )))
418
419 (defclass srecode-template-inserter-comment (srecode-template-inserter)
420 ((key :initform ?!
421 :allocation :class
422 :documentation
423 "The character code used to identify inserters of this style.")
424 )
425 "Allow comments within template coding. This inserts nothing.")
426
427 (cl-defmethod srecode-inserter-prin-example ((_ins (subclass srecode-template-inserter-comment))
428 escape-start escape-end)
429 "Insert an example using inserter INS.
430 Arguments ESCAPE-START and ESCAPE-END are the current escape sequences in use."
431 (princ " ")
432 (princ escape-start)
433 (princ "! Miscellaneous text commenting in your template. ")
434 (princ escape-end)
435 (terpri)
436 )
437
438 (cl-defmethod srecode-insert-method ((_sti srecode-template-inserter-comment)
439 _dictionary)
440 "Don't insert anything for comment macros in STI."
441 nil)
442
443
444 (defclass srecode-template-inserter-variable (srecode-template-inserter)
445 ((key :initform nil
446 :allocation :class
447 :documentation
448 "The character code used to identify inserters of this style."))
449 "Insert the value of a dictionary entry.
450 If there is no entry, insert nothing.")
451
452 (defvar srecode-inserter-variable-current-dictionary nil
453 "The active dictionary when calling a variable filter.")
454
455 (cl-defmethod srecode-insert-variable-secondname-handler
456 ((sti srecode-template-inserter-variable) dictionary value secondname)
457 "For VALUE handle SECONDNAME behaviors for this variable inserter.
458 Return the result as a string.
459 By default, treat as a function name.
460 If SECONDNAME is nil, return VALUE."
461 (if secondname
462 (let ((fcnpart (read secondname)))
463 (if (fboundp fcnpart)
464 (let ((srecode-inserter-variable-current-dictionary dictionary))
465 (funcall fcnpart value))
466 ;; Else, warn.
467 (srecode-insert-report-error
468 dictionary
469 "Variable inserter %s: second argument `%s' is not a function"
470 (object-print sti) secondname)))
471 value))
472
473 (cl-defmethod srecode-insert-method ((sti srecode-template-inserter-variable)
474 dictionary)
475 "Insert the STI inserter."
476 ;; Convert the name into a name/fcn pair
477 (let* ((name (oref sti :object-name))
478 (fcnpart (oref sti :secondname))
479 (val (srecode-dictionary-lookup-name
480 dictionary name))
481 (do-princ t)
482 )
483 ;; Alert if a macro wasn't found.
484 (when (not val)
485 (message "Warning: macro %S was not found in the dictionary." name)
486 (setq val ""))
487 ;; If there was a functional part, call that function.
488 (cond ;; Strings
489 ((stringp val)
490 (setq val (srecode-insert-variable-secondname-handler
491 sti dictionary val fcnpart)))
492 ;; Compound data value
493 ((cl-typep val 'srecode-dictionary-compound-value)
494 ;; Force FCN to be a symbol
495 (when fcnpart (setq fcnpart (read fcnpart)))
496 ;; Convert compound value to a string with the fcn.
497 (setq val (srecode-compound-toString val fcnpart dictionary))
498 ;; If the value returned is nil, then it may be a special
499 ;; field inserter that requires us to set do-princ to nil.
500 (when (not val)
501 (setq do-princ nil)))
502
503 ;; Dictionaries... not allowed in this style
504 ((cl-typep val 'srecode-dictionary)
505 (srecode-insert-report-error
506 dictionary
507 "Macro %s cannot insert a dictionary - use section macros instead"
508 name))
509
510 ;; Other stuff... convert
511 (t
512 (srecode-insert-report-error
513 dictionary
514 "Macro %s cannot insert arbitrary data" name)))
515 ;; Output the dumb thing unless the type of thing specifically
516 ;; did the inserting for us.
517 (when do-princ
518 (princ val))))
519
520 (defclass srecode-template-inserter-ask (srecode-template-inserter-variable)
521 ((key :initform ??
522 :allocation :class
523 :documentation
524 "The character code used to identify inserters of this style.")
525 (prompt :initarg :prompt
526 :initform nil
527 :documentation
528 "The prompt used to query for this dictionary value.")
529 (defaultfcn :initarg :defaultfcn
530 :initform nil
531 :documentation
532 "The function which can calculate a default value.")
533 (read-fcn :initarg :read-fcn
534 :initform 'read-string
535 :documentation
536 "The function used to read in the text for this prompt.")
537 )
538 "Insert the value of a dictionary entry.
539 If there is no entry, prompt the user for the value to use.
540 The prompt text used is derived from the previous PROMPT command in the
541 template file.")
542
543 (cl-defmethod srecode-inserter-apply-state
544 ((ins srecode-template-inserter-ask) STATE)
545 "For the template inserter INS, apply information from STATE.
546 Loop over the prompts to see if we have a match."
547 (let ((prompts (oref STATE prompts))
548 )
549 (while prompts
550 (when (string= (semantic-tag-name (car prompts))
551 (oref ins :object-name))
552 (oset ins :prompt
553 (semantic-tag-get-attribute (car prompts) :text))
554 (oset ins :defaultfcn
555 (semantic-tag-get-attribute (car prompts) :default))
556 (oset ins :read-fcn
557 (or (semantic-tag-get-attribute (car prompts) :read)
558 'read-string))
559 )
560 (setq prompts (cdr prompts)))
561 ))
562
563 (cl-defmethod srecode-insert-method ((sti srecode-template-inserter-ask)
564 dictionary)
565 "Insert the STI inserter."
566 (let ((val (srecode-dictionary-lookup-name
567 dictionary (oref sti :object-name))))
568 (if val
569 ;; Does some extra work. Oh well.
570 (cl-call-next-method)
571
572 ;; How is our -ask value determined?
573 (if srecode-insert-with-fields-in-progress
574 ;; Setup editable fields.
575 (setq val (srecode-insert-method-field sti dictionary))
576 ;; Ask the question...
577 (setq val (srecode-insert-method-ask sti dictionary)))
578
579 ;; After asking, save in the dictionary so that
580 ;; the user can use the same name again later.
581 (srecode-dictionary-set-value
582 (srecode-root-dictionary dictionary)
583 (oref sti :object-name) val)
584
585 ;; Now that this value is safely stowed in the dictionary,
586 ;; we can do what regular inserters do.
587 (cl-call-next-method))))
588
589 (cl-defmethod srecode-insert-ask-default ((sti srecode-template-inserter-ask)
590 dictionary)
591 "Derive the default value for an askable inserter STI.
592 DICTIONARY is used to derive some values."
593 (let ((defaultfcn (oref sti :defaultfcn)))
594 (cond
595 ((stringp defaultfcn)
596 defaultfcn)
597
598 ((functionp defaultfcn)
599 (funcall defaultfcn))
600
601 ((and (listp defaultfcn)
602 (eq (car defaultfcn) 'macro))
603 (srecode-dictionary-lookup-name
604 dictionary (cdr defaultfcn)))
605
606 ((null defaultfcn)
607 "")
608
609 (t
610 (srecode-insert-report-error
611 dictionary
612 "Unknown default for prompt: %S" defaultfcn)))))
613
614 (cl-defmethod srecode-insert-method-ask ((sti srecode-template-inserter-ask)
615 dictionary)
616 "Do the \"asking\" for the template inserter STI.
617 Use DICTIONARY to resolve values."
618 (let* ((prompt (oref sti prompt))
619 (default (srecode-insert-ask-default sti dictionary))
620 (reader (oref sti :read-fcn))
621 (val nil)
622 )
623 (cond ((eq reader 'y-or-n-p)
624 (if (y-or-n-p (or prompt
625 (format "%s? "
626 (oref sti :object-name))))
627 (setq val default)
628 (setq val "")))
629 ((eq reader 'read-char)
630 (setq val (format
631 "%c"
632 (read-char (or prompt
633 (format "Char for %s: "
634 (oref sti :object-name))))))
635 )
636 (t
637 (save-excursion
638 (setq val (funcall reader
639 (or prompt
640 (format "Specify %s: "
641 (oref sti :object-name)))
642 default
643 )))))
644 ;; Return our derived value.
645 val)
646 )
647
648 (cl-defmethod srecode-insert-method-field ((sti srecode-template-inserter-ask)
649 dictionary)
650 "Create an editable field for the template inserter STI.
651 Use DICTIONARY to resolve values."
652 (let* ((default (srecode-insert-ask-default sti dictionary))
653 (compound-value
654 (srecode-field-value (oref sti :object-name)
655 :firstinserter sti
656 :defaultvalue default))
657 )
658 ;; Return this special compound value as the thing to insert.
659 ;; This special compound value will repeat our asked question
660 ;; across multiple locations.
661 compound-value))
662
663 (cl-defmethod srecode-dump ((ins srecode-template-inserter-ask) _indent)
664 "Dump the state of the SRecode template inserter INS."
665 (cl-call-next-method)
666 (princ " : \"")
667 (princ (oref ins prompt))
668 (princ "\"")
669 )
670
671 (defclass srecode-template-inserter-width (srecode-template-inserter-variable)
672 ((key :initform ?|
673 :allocation :class
674 :documentation
675 "The character code used to identify inserters of this style.")
676 )
677 "Inserts the value of a dictionary variable with a specific width.
678 The second argument specifies the width, and a pad, separated by a colon.
679 Thus a specification of `10:left' will insert the value of A
680 to 10 characters, with spaces added to the left. Use `right' for adding
681 spaces to the right.")
682
683 (cl-defmethod srecode-insert-variable-secondname-handler
684 ((_sti srecode-template-inserter-width) dictionary value width)
685 "For VALUE handle WIDTH behaviors for this variable inserter.
686 Return the result as a string.
687 By default, treat as a function name."
688 ;; Cannot work without width.
689 (unless width
690 (srecode-insert-report-error
691 dictionary
692 "Width not specified for variable/width inserter"))
693
694 ;; Trim or pad to new length
695 (let* ((split (split-string width ":"))
696 (width (string-to-number (nth 0 split)))
697 (second (nth 1 split))
698 (pad (cond
699 ((or (null second) (string= "right" second))
700 'right)
701 ((string= "left" second)
702 'left)
703 (t
704 (srecode-insert-report-error
705 dictionary
706 "Unknown pad type %s" second)))))
707 (if (>= (length value) width)
708 ;; Simple case - too long.
709 (substring value 0 width)
710 ;; We need to pad on one side or the other.
711 (let ((padchars (make-string (- width (length value)) ? )))
712 (if (eq pad 'left)
713 (concat padchars value)
714 (concat value padchars))))))
715
716 (cl-defmethod srecode-inserter-prin-example ((_ins (subclass srecode-template-inserter-width))
717 escape-start escape-end)
718 "Insert an example using inserter INS.
719 Arguments ESCAPE-START and ESCAPE-END are the current escape sequences in use."
720 (princ " ")
721 (princ escape-start)
722 (princ "|A:10:right")
723 (princ escape-end)
724 (terpri)
725 )
726
727 (defvar srecode-template-inserter-point-override nil
728 "Point-positioning method for the SRecode template inserter.
729 When nil, perform normal point-positioning behavior.
730 When the value is a cons cell (DEPTH . FUNCTION), call FUNCTION
731 instead, unless the template nesting depth, measured
732 by (length (oref srecode-template active)), is greater than
733 DEPTH.")
734
735
736 (defclass srecode-template-inserter-point (srecode-template-inserter)
737 ((key :initform ?^
738 :allocation :class
739 :documentation
740 "The character code used to identify inserters of this style.")
741 (point :type (or null marker)
742 :allocation :class
743 :documentation
744 "Record the value of (point) in this class slot.
745 It is the responsibility of the inserter algorithm to clear this
746 after a successful insertion."))
747 "Record the value of (point) when inserted.
748 The cursor is placed at the ^ macro after insertion.
749 Some inserter macros, such as `srecode-template-inserter-include-wrap'
750 will place text at the ^ macro from the included macro.")
751
752 (cl-defmethod srecode-inserter-prin-example ((_ins (subclass srecode-template-inserter-point))
753 escape-start escape-end)
754 "Insert an example using inserter INS.
755 Arguments ESCAPE-START and ESCAPE-END are the current escape sequences in use."
756 (princ " ")
757 (princ escape-start)
758 (princ "^")
759 (princ escape-end)
760 (terpri)
761 )
762
763 (cl-defmethod srecode-insert-method ((sti srecode-template-inserter-point)
764 dictionary)
765 "Insert the STI inserter.
766 Save point in the class allocated `point' slot.
767 If `srecode-template-inserter-point-override' non-nil then this
768 generalized marker will do something else. See
769 `srecode-template-inserter-include-wrap' as an example."
770 ;; If `srecode-template-inserter-point-override' is non-nil, its car
771 ;; is the maximum template nesting depth for which the override is
772 ;; valid. Compare this to the actual template nesting depth and
773 ;; maybe use the override function which is stored in the cdr.
774 (if (and srecode-template-inserter-point-override
775 (<= (length (oref-default 'srecode-template active))
776 (car srecode-template-inserter-point-override)))
777 ;; Disable the old override while we do this.
778 (let ((over (cdr srecode-template-inserter-point-override))
779 (srecode-template-inserter-point-override nil))
780 (funcall over dictionary))
781 (oset sti point (point-marker))
782 ))
783
784 (defclass srecode-template-inserter-subtemplate (srecode-template-inserter)
785 ()
786 "Wrap a section of a template under the control of a macro."
787 :abstract t)
788
789 (cl-defmethod srecode-inserter-prin-example ((_ins (subclass srecode-template-inserter-subtemplate))
790 escape-start escape-end)
791 "Insert an example using inserter INS.
792 Arguments ESCAPE-START and ESCAPE-END are the current escape sequences in use."
793 (cl-call-next-method)
794 (princ " Template Text to control")
795 (terpri)
796 (princ " ")
797 (princ escape-start)
798 (princ "/VARNAME")
799 (princ escape-end)
800 (terpri)
801 )
802
803 (cl-defmethod srecode-insert-subtemplate ((sti srecode-template-inserter-subtemplate)
804 dict slot)
805 "Insert a subtemplate for the inserter STI with dictionary DICT."
806 ;; Make sure that only dictionaries are used.
807 (unless (cl-typep dict 'srecode-dictionary)
808 (srecode-insert-report-error
809 dict
810 "Only section dictionaries allowed for `%s'"
811 (eieio-object-name-string sti)))
812
813 ;; Output the code from the sub-template.
814 (srecode-insert-method (slot-value sti slot) dict))
815
816 (cl-defmethod srecode-insert-method-helper ((sti srecode-template-inserter-subtemplate)
817 dictionary slot)
818 "Do the work for inserting the STI inserter.
819 Loops over the embedded CODE which was saved here during compilation.
820 The template to insert is stored in SLOT."
821 (let ((dicts (srecode-dictionary-lookup-name
822 dictionary (oref sti :object-name))))
823 (when (not (listp dicts))
824 (srecode-insert-report-error
825 dictionary
826 "Cannot insert section %S from non-section variable."
827 (oref sti :object-name)))
828 ;; If there is no section dictionary, then don't output anything
829 ;; from this section.
830 (while dicts
831 (when (not (srecode-dictionary-p (car dicts)))
832 (srecode-insert-report-error
833 dictionary
834 "Cannot insert section %S from non-section variable."
835 (oref sti :object-name)))
836 (srecode-insert-subtemplate sti (car dicts) slot)
837 (setq dicts (cdr dicts)))))
838
839 (cl-defmethod srecode-insert-method ((sti srecode-template-inserter-subtemplate)
840 dictionary)
841 "Insert the STI inserter.
842 Calls back to `srecode-insert-method-helper' for this class."
843 (srecode-insert-method-helper sti dictionary 'template))
844
845
846 (defclass srecode-template-inserter-section-start (srecode-template-inserter-subtemplate)
847 ((key :initform ?#
848 :allocation :class
849 :documentation
850 "The character code used to identify inserters of this style.")
851 (template :initarg :template
852 :documentation
853 "A template used to frame the codes from this inserter.")
854 )
855 "Apply values from a sub-dictionary to a template section.
856 The dictionary saved at the named dictionary entry will be
857 applied to the text between the section start and the
858 `srecode-template-inserter-section-end' macro.")
859
860 (cl-defmethod srecode-parse-input ((ins srecode-template-inserter-section-start)
861 tag input STATE)
862 "For the section inserter INS, parse INPUT.
863 Shorten input until the END token is found.
864 Return the remains of INPUT."
865 (let* ((out (srecode-compile-split-code tag input STATE
866 (oref ins :object-name))))
867 (oset ins template (srecode-template
868 (eieio-object-name-string ins)
869 :context nil
870 :args nil
871 :code (cdr out)))
872 (car out)))
873
874 (cl-defmethod srecode-dump ((ins srecode-template-inserter-section-start) indent)
875 "Dump the state of the SRecode template inserter INS."
876 (cl-call-next-method)
877 (princ "\n")
878 (srecode-dump-code-list (oref (oref ins template) code)
879 (concat indent " "))
880 )
881
882 (defclass srecode-template-inserter-section-end (srecode-template-inserter)
883 ((key :initform ?/
884 :allocation :class
885 :documentation
886 "The character code used to identify inserters of this style.")
887 )
888 "All template segments between the section-start and section-end
889 are treated specially.")
890
891 (cl-defmethod srecode-insert-method ((_sti srecode-template-inserter-section-end)
892 _dictionary)
893 "Insert the STI inserter."
894 )
895
896 (cl-defmethod srecode-match-end ((ins srecode-template-inserter-section-end) name)
897
898 "For the template inserter INS, do I end a section called NAME?"
899 (string= name (oref ins :object-name)))
900
901 (defclass srecode-template-inserter-include (srecode-template-inserter-subtemplate)
902 ((key :initform ?>
903 :allocation :class
904 :documentation
905 "The character code used to identify inserters of this style.")
906 (includedtemplate
907 :initarg :includedtemplate
908 :documentation
909 "The template included for this inserter."))
910 "Include a different template into this one.
911 The included template will have additional dictionary entries from the subdictionary
912 stored specified by this macro.")
913
914 (cl-defmethod srecode-inserter-prin-example ((_ins (subclass srecode-template-inserter-include))
915 escape-start escape-end)
916 "Insert an example using inserter INS.
917 Arguments ESCAPE-START and ESCAPE-END are the current escape sequences in use."
918 (princ " ")
919 (princ escape-start)
920 (princ ">DICTNAME:contextname:templatename")
921 (princ escape-end)
922 (terpri)
923 )
924
925 (cl-defmethod srecode-insert-include-lookup ((sti srecode-template-inserter-include)
926 dictionary)
927 "For the template inserter STI, lookup the template to include.
928 Finds the template with this macro function part and stores it in
929 this template instance."
930 (let ((templatenamepart (oref sti :secondname)))
931 ;; If there was no template name, throw an error.
932 (unless templatenamepart
933 (srecode-insert-report-error
934 dictionary
935 "Include macro `%s' needs a template name"
936 (oref sti :object-name)))
937
938 ;; NOTE: We used to cache the template and not look it up a second time,
939 ;; but changes in the template tables can change which template is
940 ;; eventually discovered, so now we always lookup that template.
941
942 ;; Calculate and store the discovered template
943 (let ((tmpl (srecode-template-get-table (srecode-table)
944 templatenamepart))
945 (active (oref-default 'srecode-template active))
946 ctxt)
947 (when (not tmpl)
948 ;; If it isn't just available, scan back through
949 ;; the active template stack, searching for a matching
950 ;; context.
951 (while (and (not tmpl) active)
952 (setq ctxt (oref (car active) context))
953 (setq tmpl (srecode-template-get-table (srecode-table)
954 templatenamepart
955 ctxt))
956 (when (not tmpl)
957 (when (slot-boundp (car active) 'table)
958 (let ((app (oref (oref (car active) table) application)))
959 (when app
960 (setq tmpl (srecode-template-get-table
961 (srecode-table)
962 templatenamepart
963 ctxt app)))
964 )))
965 (setq active (cdr active)))
966 (when (not tmpl)
967 ;; If it wasn't in this context, look to see if it
968 ;; defines its own context
969 (setq tmpl (srecode-template-get-table (srecode-table)
970 templatenamepart)))
971 )
972
973 ;; Store the found template into this object for later use.
974 (oset sti :includedtemplate tmpl))
975
976 (unless (oref sti includedtemplate)
977 ;; @todo - Call into a debugger to help find the template in question.
978 (srecode-insert-report-error
979 dictionary
980 "No template \"%s\" found for include macro `%s'"
981 templatenamepart (oref sti :object-name)))))
982
983 (cl-defmethod srecode-insert-method ((sti srecode-template-inserter-include)
984 dictionary)
985 "Insert the STI inserter.
986 Finds the template with this macro function part, and inserts it
987 with the dictionaries found in the dictionary."
988 (srecode-insert-include-lookup sti dictionary)
989 ;; Insert the template.
990 ;; Our baseclass has a simple way to do this.
991 (if (srecode-dictionary-lookup-name dictionary (oref sti :object-name))
992 ;; If we have a value, then call the next method
993 (srecode-insert-method-helper sti dictionary 'includedtemplate)
994 ;; If we don't have a special dictionary, then just insert with the
995 ;; current dictionary.
996 (srecode-insert-subtemplate sti dictionary 'includedtemplate))
997 )
998
999 ;;
1000 ;; This template combines the include template and the sectional template.
1001 ;; It will first insert the included template, then insert the embedded
1002 ;; template wherever the $^$ in the included template was.
1003 ;;
1004 ;; Since it uses dual inheritance, it will magically get the end-matching
1005 ;; behavior of #, with the including feature of >.
1006 ;;
1007 (defclass srecode-template-inserter-include-wrap (srecode-template-inserter-include srecode-template-inserter-section-start)
1008 ((key :initform ?<
1009 :allocation :class
1010 :documentation
1011 "The character code used to identify inserters of this style.")
1012 )
1013 "Include a different template into this one, and add text at the ^ macro.
1014 The included template will have additional dictionary entries from the subdictionary
1015 stored specified by this macro. If the included macro includes a ^ macro,
1016 then the text between this macro and the end macro will be inserted at
1017 the ^ macro.")
1018
1019 (cl-defmethod srecode-inserter-prin-example ((_ins (subclass srecode-template-inserter-include-wrap))
1020 escape-start escape-end)
1021 "Insert an example using inserter INS.
1022 Arguments ESCAPE-START and ESCAPE-END are the current escape sequences in use."
1023 (princ " ")
1024 (princ escape-start)
1025 (princ "<DICTNAME:contextname:templatename")
1026 (princ escape-end)
1027 (terpri)
1028 (princ " Template Text to insert at ^ macro")
1029 (terpri)
1030 (princ " ")
1031 (princ escape-start)
1032 (princ "/DICTNAME")
1033 (princ escape-end)
1034 (terpri)
1035 )
1036
1037 (cl-defmethod srecode-insert-method ((sti srecode-template-inserter-include-wrap)
1038 dictionary)
1039 "Insert the template STI.
1040 This will first insert the include part via inheritance, then
1041 insert the section it wraps into the location in the included
1042 template where a ^ inserter occurs."
1043 ;; Step 1: Look up the included inserter
1044 (srecode-insert-include-lookup sti dictionary)
1045 ;; Step 2: Temporarily override the point inserter.
1046 ;; We bind `srecode-template-inserter-point-override' to a cons cell
1047 ;; (DEPTH . FUNCTION) that has the maximum template nesting depth,
1048 ;; for which the override is valid, in DEPTH and a lambda function
1049 ;; which implements the wrap insertion behavior in FUNCTION. The
1050 ;; maximum valid nesting depth is just the current depth + 1.
1051 (let ((srecode-template-inserter-point-override
1052 (lexical-let ((inserter1 sti))
1053 (cons
1054 ;; DEPTH
1055 (+ (length (oref-default 'srecode-template active)) 1)
1056 ;; FUNCTION
1057 (lambda (dict)
1058 (let ((srecode-template-inserter-point-override nil))
1059 (if (srecode-dictionary-lookup-name
1060 dict (oref inserter1 :object-name))
1061 ;; Insert our sectional part with looping.
1062 (srecode-insert-method-helper
1063 inserter1 dict 'template)
1064 ;; Insert our sectional part just once.
1065 (srecode-insert-subtemplate
1066 inserter1 dict 'template))))))))
1067 ;; Do a regular insertion for an include, but with our override in
1068 ;; place.
1069 (cl-call-next-method)))
1070
1071 (provide 'srecode/insert)
1072
1073 ;; Local variables:
1074 ;; generated-autoload-file: "loaddefs.el"
1075 ;; generated-autoload-load-name: "srecode/insert"
1076 ;; End:
1077
1078 ;;; srecode/insert.el ends here