home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Source Code 1993 July / THE_SOURCE_CODE_CD_ROM.iso / languages / elisp / packages / dmacro-2.0 / dmacro.el < prev    next >
Encoding:
Text File  |  1991-11-15  |  32.2 KB  |  1,076 lines

  1. ;;; Copyright (c) 1991 Wayne Mesard.  May be redistributed only under the
  2. ;;; terms of the GNU Emacs General Public License.
  3.  
  4. ;;; 
  5. ;;; Dynamic MACRO
  6. ;;; 
  7.  
  8. ;;; COMMANDS
  9. ;;    insert-dmacro        Control-c d
  10. ;;    dmacro-wrap-line        Control-c l
  11. ;;    dmacro-wrap-region    Control-c r
  12. ;;    dmacro-fill-in-blanks    Control-c f
  13. ;;; PUBLIC VARIABLES
  14. ;;    dont-bind-my-keys
  15. ;;    auto-dmacro-alist
  16. ;;    dmacro-prefix-char
  17. ;;    dmacro-month-names
  18. ;;    dmacro-rank-in-initials
  19. ;;    dmacro-prompt
  20. ;;    dmacro-on-abbrev
  21. ;;; PUBLIC FUNCTIONS
  22. ;;    add-dmacros
  23. ;;    define-dmacro
  24. ;;    define-dmacro-table
  25. ;;    dmacro-command
  26. ;;    def-dmacro-function
  27. ;;    def-dmacro-alias
  28. ;;    dmacro-expand
  29. ;;    dmacro-indent
  30.  
  31. ;;; HISTORY
  32. ;;    p01 wmesard - Nov 12, 1991: Added decl for dont-bind-my-keys
  33. ;;                  made :sexp work!
  34. ;;    2.0 wmesard - Oct 31, 1991: Too many changes to list.
  35. ;;    1.5 wmesard - Apr 11, 1991.
  36.  
  37. ;;; AUTHOR
  38. ;;    Wayne Mesard, WMesard@Oracle.com
  39.  
  40. ;;; 
  41. ;;; KEY BINDINGS
  42. ;;; 
  43.  
  44. (defvar dont-bind-my-keys)
  45. (if (not (and (boundp 'dont-bind-my-keys) dont-bind-my-keys))
  46.     (progn
  47.       (global-set-key "\C-cd" 'insert-dmacro)
  48.       (global-set-key "\C-cl" 'dmacro-wrap-line)
  49.       (global-set-key "\C-cr" 'dmacro-wrap-region)
  50.       (global-set-key "\C-cf" 'dmacro-fill-in-blanks)
  51.       ))
  52.  
  53. ;;; 
  54. ;;; RELATED COMMANDS
  55. ;;; 
  56.  
  57. (autoload 'build-dmacro "dmacro-bld" 
  58.       "Interactively build a new dmacro." t nil)
  59. (autoload 'write-dmacro-file "dmacro-bld" 
  60.       "Save all dmacros and abbrevs to FILE." t nil)
  61.  
  62. ;;; 
  63. ;;; USER PARAMETERS
  64. ;;; 
  65.  
  66. (defvar auto-dmacro-alist nil
  67.   "*An alist of filename patterns and corresponding dmacro names.  Each
  68. element looks like (REGEXP . DMACRO-SYMBOL) just like auto-mode-alist.
  69. Visiting a new file whose name matches REGEXP causes the dmacro to be
  70. inserted into the buffer.
  71.   This facility is a functional super-duper-set of autoinsert.el.")
  72.  
  73.  
  74. (defvar dmacro-prefix-char "~"
  75.   "*The character searched for by dmacro-expand-region when looking for
  76. text to modify.  The value of this variable must be a string containting
  77. a single character.")
  78.  
  79.  
  80. (defconst dmacro-month-names 
  81.   '("January" "February" "March" "April" "May" "June" "July" "August" 
  82.     "September" "October" "November" "December")
  83.   "*Used by the macro ~(month). Change these to suit your language or tastes.")
  84.  
  85.  
  86. (defvar dmacro-rank-in-initials nil
  87.   "*If non-nil the ~(user-initials) macro will include (Jr, Sr, II, etc...)
  88. when such a rank is present in the user's name.")
  89.  
  90.  
  91. (defvar dmacro-prompt t
  92.   "*When this variable is t, Dmacro prompts the user in the minibuffer
  93. when expanding interactive dmacros (i.e. dmacros containing the
  94. ~(prompt) function.  If this variable is nil, it won't do anything
  95. with the blanks until the user types the to-be-substituted text in the
  96. buffer and invokes \\[dmacro-fill-in-blanks].
  97.   If this variable is not t and not nil, Dmacro will grab the words
  98. immediately preceding point.  So if you forget to type them before
  99. invoking the dmacro, it will blindly use whatever it finds in the
  100. buffer.")
  101.  
  102.  
  103. (defvar dmacro-on-abbrev nil
  104.   "*Dmacro is an overgrown hack built on top of Emacs' Abbrev Mode.
  105. Some people like to use both abbrevs and dmacros at the same time, but
  106. don't want their dmacros auto-expanded.  If DMACRO-ON-ABBREV is nil,
  107. dmacros will only be expanded if they were accessed through one of the
  108. Dmacro commands: \\[insert-dmacro], \\[dmacro-wrap-line] or \\[dmacro-wrap-region].")
  109.  
  110.  
  111. ;;; 
  112. ;;; PRIVATE VARIABLES
  113. ;;; 
  114.  
  115. ;; Used by dmacro-minibuffer-read to detect when user has asked for 
  116. ;; completion twice in a row.  When this happens it displays the documentation
  117. ;; or expansion for each dmacro name.
  118. (defvar dmacro-verbose-list)
  119.  
  120. ;; Used by dmacro-minibuffer-read to hold the value of local-abbrev-table.
  121. ;; This is because local-abbrev-table itself gets changed when the minibuffer
  122. ;; becomes active.
  123. (defvar dmacro-table)
  124.  
  125. ;; Used by the "~(point)" function (and company) to keep track of where the
  126. ;; cursor should be left when expansion is complete.
  127. (defvar dmacro-point nil)
  128.  
  129. ;; Used by the "~(mark)" function (and company).
  130. (defvar dmacro-marks nil)
  131.  
  132. ;; Used to flag the fact that we are, in fact, expanding a dmacro,
  133. ;; as opposed to expanding an abbrev.  We need to know the difference
  134. ;; when DMACRO-ON-ABBREV is nil.
  135. (defvar dmacro-expanding nil)
  136.  
  137. ;; Used during dmacro expansion.  Holds the name of the last prompt used
  138. ;; so that dmacro builders can just say ~(prompt) instead of ~(prompt foo)
  139. ;; if they mean "the same one as last time".
  140. (defvar dmacro-last-prompt)
  141.  
  142. ;; Used during dmacro expansion.  Holds the value returned by
  143. ;; (current-time-string) so that a single dmacro only has to call this 
  144. ;; function once.  See the function by the same name for details.
  145. (defvar dmacro-ts)
  146.  
  147. ;; Used during dmacro expansion.  Holds the value returned by
  148. ;; (buffer-file-name)so that a single dmacro only has to call this 
  149. ;; function once.  See the function by the same name for details.
  150. (defvar dmacro-fn)
  151.  
  152. ;; Used by dmacro-wrap-region to pass the to-be-wrapped text in to 
  153. ;; dmacro-expand-region.  It needs to be inserted in dmacro-expand-region
  154. ;; (as opposed to after we get back to dmacro-wrap-region) because it must 
  155. ;; happen before any indenting (so it can get indented too).
  156. (defvar dmacro-extra nil)
  157.  
  158. ;; Used by dmacro-wrap-region to indicate to dmacro-expand-region
  159. ;; that the extra text should be inserted at a mark instead of point.
  160. ;; See dmacro-wrap-region for details.
  161. (defvar dmacro-goto-mark nil)
  162.  
  163. ;; The plist of this symbol holds the prompt info during expansion.
  164. ;; See dmacro-save-string for format of the plist.
  165. (defvar dmacro-strings nil)
  166.  
  167.  
  168. ;; If non-nil, means we're in a recursive expansion (i.e. a ~(dmacro)
  169. ;; command).  This means (among other things) don't process dmacro-strings
  170. ;; or dmacro-marks when expansion is done.
  171. (defvar dmacro-recurse nil)
  172.  
  173. ;; A hack to get around a bug in c-indent-command wherein marks near the
  174. ;; beginning of a line don't get positioned correctly.  See the comments in
  175. ;; dmacro-indent for more details.
  176. (defvar dmacro-fix-marks nil)
  177. (defvar dmacro-fix-marks-on nil)
  178.  
  179. ;; An alist of the documentation strings.
  180. (defvar dmacro-doclist nil)
  181.  
  182. ;; The list of all dmacro-functions.  dmacro-parse looks up things in here.
  183. ;; def-dmacro-alias and def-dmacro-function add things to here.
  184. (defvar dmacro-functions
  185.   (list 
  186.    '(@      :alias point)
  187.    '(year   :alias (chron) 20)
  188.    '(mon    :alias (chron) 4 7)
  189.    '(date   :alias (chron) 8 10)
  190.    '(day    :alias (chron) 0 3)
  191.    '(hour24 :alias (chron) 11 13)
  192.    '(min    :alias (chron) 14 16)
  193.    '(sec    :alias (chron) 17 19)
  194.    (cons '~
  195.      (function (lambda () dmacro-prefix-char)))
  196.    (cons 'prompt
  197.      (function (lambda (mods &optional itemname &rest args)
  198.              ;; if no itemname specified, default to previous one.
  199.              ;; if there wasn't a previous one, use the word "prompt".
  200.              (if (null itemname)
  201.              (setq itemname dmacro-last-prompt)
  202.                (setq dmacro-last-prompt itemname))
  203.              (dmacro-save-string t mods itemname args)
  204.              (concat "<" (symbol-name itemname) ">"))
  205.            ))
  206.  
  207.    (cons 'if
  208.      (function (lambda (mods expr then &optional else)
  209.              (if (eq 'prompt (car expr))
  210.              (progn
  211.                (dmacro-save-string 
  212.                 nil (list then else mods)
  213.                 (or
  214.                  (car (cdr expr)) dmacro-last-prompt)
  215.                 (cdr (cdr expr)))
  216.                nil)
  217.                (if (dmacro-funcall expr)
  218.                (dmacro-funcall then)
  219.              (dmacro-funcall else))
  220.                ))
  221.            ))
  222.    (cons 'dmacro
  223.      (function (lambda (tem &optional pointP) 
  224.              (let ((dmacro-recurse (or pointP 'protect-point)))
  225.                (insert-dmacro (symbol-name tem))
  226.                )
  227.              nil)
  228.            ))
  229.  
  230.    '(chron . dmacro-ts)
  231.    (cons 'month-num
  232.      (function (lambda () (format "%2d" (dmacro-month-num)))))
  233.    (cons 'month
  234.      (function (lambda () 
  235.              (nth (1- (dmacro-month-num)) dmacro-month-names))))
  236.    (cons 'hour
  237.      (function (lambda ()
  238.              (let* ((r (string-to-int (substring (dmacro-ts) 11 13)))
  239.                 (h (if (zerop (% r 12)) 12 (% r 12))))
  240.                (format "%2d" h)))))
  241.    (cons 'ampm
  242.      (function (lambda ()
  243.              (if (<= 12 (string-to-int (substring (dmacro-ts) 11 13)))
  244.              "pm"
  245.                "am"))))
  246.  
  247.    '(file-long . dmacro-fn)
  248.    (cons 'file
  249.      (function (lambda () (file-name-nondirectory (dmacro-fn)))))
  250.    (cons 'file-dir
  251.      (function (lambda () (file-name-directory (dmacro-fn)))))
  252.    (cons 'file-name
  253.      (function (lambda ()
  254.              (let ((fn (file-name-nondirectory (dmacro-fn))))
  255.                (substring fn 0 (string-match "\\.[^.]*$" fn))
  256.                ))
  257.            ))
  258.    (cons 'file-ext
  259.      (function (lambda ()
  260.              (let* ((fn (file-name-nondirectory (dmacro-fn)))
  261.                 (i (string-match "\\.[^.]*$" fn)))
  262.                (if i
  263.                (substring fn (1+ i))
  264.              )))))
  265.    (cons 'insert-file
  266.      (function (lambda (x)
  267.              (condition-case data
  268.              (save-excursion
  269.                (set-buffer (get-buffer-create " dmacro-temp"))
  270.                (erase-buffer)
  271.                (insert-file x)
  272.                (setq x (buffer-substring (point-min) (point-max)))
  273.                (erase-buffer))
  274.                (error
  275.             (if (eq 'file-error (car data))
  276.                 (message "Warning: couldn't read file: %s" x)
  277.               (signal 'error data))
  278.             (setq x nil)
  279.             ))
  280.              x)))
  281.    '(user-id . user-login-name)
  282.    '(user-name . user-full-name)
  283.    '(user-initials . dmacro-initials)
  284.    (cons 'point
  285.      (function (lambda () 
  286.              (if (not (eq dmacro-recurse 'protect-point))
  287.              (setq dmacro-point (point))
  288.                (dmacro-push-mark))
  289.              nil)))
  290.    '(mark . dmacro-push-mark)
  291.    (cons 'shell
  292.      (function (lambda (cmd)
  293.              (save-excursion
  294.                (set-buffer (get-buffer-create " dmacro-temp"))
  295.                (erase-buffer)
  296.                (shell-command cmd t)
  297.                (setq cmd 
  298.                  (buffer-substring (point-min) (point-max)))
  299.                )
  300.              cmd)))
  301.    (cons 'eval
  302.      (function (lambda (form)
  303.              (let ((res (eval form)))
  304.                (if (or (null res) (stringp res))
  305.                res
  306.              (prin1-to-string res))
  307.                ))
  308.            ))
  309.    ))
  310.  
  311.  
  312. ;;; 
  313. ;;; COMMANDS
  314. ;;; 
  315.  
  316. (defun insert-dmacro (name)
  317.   "Insert the dmacro NAME.  It prompts for NAME.
  318. When called from Lisp programs, NAME is a string; if NAME is not a valid
  319. dmacro in the current buffer, then NAME itself is inserted."
  320.   (interactive (list (dmacro-minibuffer-read nil t)))
  321.   (let ((executing-macro t)
  322.     (dmacro-expanding t))
  323.     (abbrev-prefix-mark)
  324.     (insert name)
  325.     (expand-abbrev)
  326.     ))
  327.  
  328. (defun dmacro-wrap-region (dmacro marker beg end)
  329.   "Put the text between point and mark at the point location in DMACRO.
  330. E.g., if the selected text is \"abc\" and the dmacro expands to \"{ <p> }\",
  331. where <p> is the location of the cursor, the result would be \"{ abc }\".
  332. With a prefix argument, put the text at a marker location instead of point.
  333. The marker used is the number of the marker indicated by the prefix argument.
  334. If there aren't that many markers in the dmacro, the first one is used."
  335.   (interactive (list (dmacro-minibuffer-read nil t)
  336.              current-prefix-arg
  337.              (region-beginning) (region-end) 
  338.              ))
  339.   (let ((dmacro-extra (buffer-substring beg end))
  340.     (dmacro-goto-mark marker))
  341.     (delete-region beg end)
  342.     (insert-dmacro dmacro)
  343.     ))
  344.  
  345. (defun dmacro-wrap-line (dmacro marker)
  346.  "Put the text on the current line at the point location in DMACRO.
  347. E.g., if the line contains \"abc\" and the dmacro expands to \"{ <p> }\",
  348. (where <p> is the location of the cursor), the result would be \"{ abc }\".
  349. With a prefix argument, put the text at a marker location instead of point.
  350. The marker used is the number of the marker indicated by the prefix argument.
  351. If there aren't that many markers in the dmacro, the first one is used."
  352.   (interactive (list (dmacro-minibuffer-read nil t)
  353.              current-prefix-arg))
  354.   (let* ((end (save-excursion (end-of-line) (point)))
  355.      (loc (- end (point))))
  356.     (dmacro-wrap-region dmacro 
  357.               marker
  358.               (save-excursion (forward-to-indentation 0) (point))
  359.               end)
  360.     (goto-char (- (point) loc))
  361.     ))
  362.  
  363. (defun dmacro-fill-in-blanks ()
  364.   "When DMACRO-PROMPT is nil, users invoke this function after inserting a
  365. dmacro.  It then backward deletes the appropriate number of sexps from 
  366. the buffer and fills in the blanks in the dmacro."
  367.   (interactive)
  368.   (if (null (symbol-plist 'dmacro-strings))
  369.       (error "No blanks to fill in from the last dmacro."))
  370.   (let ((last-buff (marker-buffer 
  371.             (car (car (car (cdr (symbol-plist 'dmacro-strings))))))))
  372.     (if (not (eq last-buff (current-buffer)))
  373.     (error "Error: Last dmacro expansion was in %s." 
  374.            (buffer-name last-buff))))
  375.   (dmacro-process-strings (dmacro-get-words (point))))
  376.  
  377.  
  378. ;;; 
  379. ;;; PUBLIC FUNCTIONS
  380. ;;; 
  381.  
  382. ;; Example of use:
  383. ;;  (add-dmacros 'c-mode-abbrev-table
  384. ;;    '(("def" "#define ")
  385. ;;      ("day" "today is ~day" dmacro-expand "the day of the week")
  386. ;;      ))
  387.  
  388. (defun add-dmacros (tabname definitions)
  389.   "Just like define-abbrev-table, except: existing abbrevs are not destroyed
  390. and if HOOK (see below) is unspecifed, \"dmacro-expand\" is assumed.
  391.   TABNAME is a symbol, DEFINITIONS is a list of elements of the form (NAME 
  392. TEXT &optional EXPANSION-QUALIFIER DOCUMENTATION).  Valid values for EXPANSION-
  393. QUALIFIER are: nil, expand and indent."
  394.   (mapcar (function (lambda (d)
  395.               (define-dmacro 
  396.             (symbol-value tabname)
  397.             (nth 0 d)
  398.             (nth 1 d)
  399.             (nth 2 d)
  400.             (nth 3 d))
  401.               ))
  402.       definitions)
  403.   ;; No need for this to return a big ugly list.
  404.   nil)
  405.  
  406.  
  407. (defun define-dmacro (table name text expansion-qualifier doc)
  408.   "Define a single dmacro.  Takes 5 args.  TABLE is the
  409. abbrev-table to define it in.  NAME is a string, the name of the
  410. dmacro.  TEXT is the actual dmacro text string. EXPANSION-QUALIFIER
  411. is the hook to run on the text (valid values are dmacro-expand,
  412. dmacro-indent or nil (which is the same as dmacro-expand)).  DOC,
  413. if non-nil, is a string describing the dmacro."
  414.   ;; protect the global value of abbrevs-changed from getting modified.
  415.   (let* ((abbrevs-changed nil)
  416.      ;; define the abbrev
  417.      (sym (abbrev-symbol (define-abbrev table name text
  418.                    (if (eq expansion-qualifier 'indent)
  419.                    'dmacro-indent
  420.                  (if (or (not expansion-qualifier)
  421.                      (eq expansion-qualifier 'expand))
  422.                      'dmacro-expand
  423.                    expansion-qualifier)))
  424.                  table))
  425.      ;; check for an existing doc string.
  426.      (doccons (assq sym dmacro-doclist)))
  427.     ;; store the doc string in the alist.
  428.     (if doccons
  429.     (setcdr doccons doc)
  430.       ;; write-dmacro-file needs this done even if doc is nil.
  431.       (setq dmacro-doclist (cons (cons sym doc) dmacro-doclist)
  432.     ))
  433.     ))
  434.  
  435.  
  436. (defun define-dmacro-table (tabname definitions)
  437.   "Just like add-dmacros, but it makes sure the table is empty first.
  438. We recommend that you use add-dmacros so that you can define dmacros in
  439. multiple files and not worry about one trashing the other."
  440.   ;; protect the global value of abbrevs-changed from getting modified.
  441.   (let ((abbrevs-changed nil))
  442.     (clear-abbrev-table (symbol-value tabname))
  443.     (add-dmacros tabname definitions)
  444.     ))
  445.  
  446.  
  447. (defun dmacro-command (TEM1 &optional TEM2 FUNCNAME)
  448.   "In true Lisp fashion, this is a function building function.
  449. It generates a function that inserts and expands a dmacro, TEM1.  If optional 
  450. second arg TEM2 is specified, then the generated function will also insert and
  451. expand TEM2 when preceded by \\[universal-argument].  If optional third arg FUNCNAME, a symbol,
  452. is specified, then a real live function is generated suitable for use with
  453. \\[describe-function], \\[execute-extended-command], etc.
  454.  
  455. DMACRO-COMMAND is intended to bind dmacros to keys.  E.g.:
  456.  (global-set-key \"\\C-ct\" 
  457.                  (dmacro-command \"dstamp\" \"dtstamp\" 'insert-timestamp)))
  458.  (define-key c-mode-map \"\\C-cf\" (dmacro-command \"fordown\" \"forup\"))"
  459.   (let* ((docdef 
  460.       (if FUNCNAME
  461.           (apply 
  462.            (function concat)
  463.            "Insert and expand the dmacro named \"" TEM1 "\"."
  464.            (if TEM2
  465.            (list "\nWith a prefix arg, use \"" TEM2 "\" instead.")))
  466.         ))
  467.      (fundef 
  468.       (if TEM2
  469.           (list 'lambda '(arg) docdef '(interactive "P")
  470.             (list 'insert-dmacro
  471.               (list 'if 'arg TEM2 TEM1)))
  472.         (list 'lambda () docdef '(interactive)
  473.           (list 'insert-dmacro TEM1)
  474.           ))
  475.       ))
  476.     (if FUNCNAME
  477.     (progn
  478.       (fset FUNCNAME fundef)
  479.       FUNCNAME)
  480.       fundef)))
  481.  
  482. ;; For compatibility with v1.5.  This will go away someday. -wsm9/3/91.
  483. (fset 'dmacro-function (symbol-function 'dmacro-command))
  484.  
  485.  
  486. (defmacro def-dmacro-function (name &rest body)
  487.   (list 'setq 'dmacro-functions
  488.     (list 'cons
  489.           (list 'cons (list 'quote name)
  490.             (if (= 1 (length body))
  491.             (list 'quote (car body))
  492.               (list 'function (cons 'lambda body))
  493.               ))
  494.           'dmacro-functions)
  495.     ))
  496.  
  497. (defmacro def-dmacro-alias (&rest args)
  498.   (list 'setq 'dmacro-functions
  499.     (list 'append
  500.           (let ((new nil))
  501.         (while args
  502.           (setq new (cons 
  503.                  (cons (car args) (cons ':alias (car (cdr args))))
  504.                  new)
  505.             args (cdr (cdr args))))
  506.         (list 'quote new))
  507.           'dmacro-functions)
  508.     ))
  509.  
  510. ;;
  511. ;; Abbrev hooks
  512. ;;
  513.  
  514. ;; These are semi-public functions.  The user doesn't invoke them directly.
  515. ;; They are used as the hook in the dmacro definition.
  516.  
  517. (defun dmacro-expand ()
  518.   "Passed in as the HOOK argument to define-dmacro.
  519. Causes the dmacro to be expanded."
  520.   (if (or dmacro-on-abbrev dmacro-expanding)
  521.       (progn
  522.     (dmacro-expand-region last-abbrev-location (point))
  523.     (dmacro-fix-marks-hack))
  524.     (unexpand-abbrev)
  525.     ))
  526.  
  527. (defun dmacro-indent ()
  528.   "Passed in as the HOOK argument to define-dmacro.
  529. Causes the dmacro to be expanded and then each line of the expanded
  530. text to be indented in a way appropriate to the buffer's major mode."
  531.   (if (or dmacro-on-abbrev dmacro-expanding)
  532.       (let* ((endpt (point-marker))
  533.          (boln (save-excursion
  534.              (goto-char last-abbrev-location)
  535.              (beginning-of-line)
  536.              (point)))
  537.          (dmacro-fix-marks-on t))
  538.     (dmacro-expand-region last-abbrev-location endpt)
  539.     ;; Use boln instead of last-abbrev-location to make sure that the first
  540.     ;; line gets indented first.  (This would burn "case" abbrev.)
  541.     (indent-region boln endpt nil)
  542.     ;; The next call is just to be sure point does the right thing.  Else:
  543.     ;; Inserting this:     would leave point here:     instead of here:
  544.     ;;
  545.     ;;      {                         {                        {
  546.     ;;                                p                           p
  547.     ;;      }                         }                        }
  548.     (indent-according-to-mode)
  549.     ;; And this does the same thing for all the marks that might need it.
  550.     (dmacro-fix-marks-hack))
  551.     (unexpand-abbrev)
  552.     ))
  553.  
  554.  
  555. ;;; 
  556. ;;; PRIVATE FUNCTIONS
  557. ;;; 
  558.  
  559. ;; 
  560. ;; Minibuffer prompting for dmacro name
  561. ;; 
  562.  
  563. ;; Read the name of a dmacro from the user.
  564. ;; PROMPT can be nil (in which case a default prompt is used).
  565. ;; If CONFIRM is t then the user-specified string must be the name of an
  566. ;; existing dmacro.
  567.  
  568. (defun dmacro-minibuffer-read (prompt confirm)
  569.   (let ((dmacro-table local-abbrev-table)
  570.     (dmacro-verbose-list nil)
  571.     res)
  572.     ;; The while loop prevents an empty string from being entered.
  573.     (while (zerop (length res))
  574.       (if res                ; There was an error
  575.       (beep t))
  576.       (setq res (completing-read (or prompt "Dmacro: ") 
  577.                  'dmacro-internal nil confirm nil))
  578.       )
  579.     res))
  580.  
  581.   
  582. ;; Helper function for dmacro-minibuffer-read.
  583.  
  584. (defun dmacro-internal (str ignore action)
  585.   (cond 
  586.    ;; Find the matches in both tables, return t if an 
  587.    ;; exact match in either, else return the shorter
  588.    ;; of the two (non-nil) common prefixes.
  589.    ((null action)            ; ACTION = Complete
  590.     (let ((try1 (if dmacro-table 
  591.             (try-completion str dmacro-table)))
  592.       (try2 (try-completion str global-abbrev-table)))
  593.       (or (eq t try1) (eq t try2)
  594.       (dmacro-common-prefix try1 try2)
  595.       (if (or (null try2) 
  596.           (and try1 (< (length try1) (length try2))))
  597.           try1
  598.         try2))
  599.       ))
  600.    ;; Look it up.
  601.    ((eq action 'lambda)            ; ACTION = Verify
  602.     (let ((local-abbrev-table dmacro-table))
  603.       (abbrev-symbol str)
  604.       ))
  605.                     ; ACTION = List matches
  606.    ;; List all matches (and maybe some other helpful information).
  607.    ((let ((lis (append (if dmacro-table (all-completions str dmacro-table))
  608.                (all-completions str global-abbrev-table)))
  609.       (local-abbrev-table dmacro-table))
  610.       (if (and (stringp dmacro-verbose-list)
  611.            (string-equal str dmacro-verbose-list))
  612.       ;; 2nd time through list dmacro names and their documentation
  613.       ;; (or expansion of documentation is nil).
  614.       (mapcar 
  615.        '(lambda (x) 
  616.           (format "\n%s:\t%s" x
  617.               (or
  618.                (let ((sym (abbrev-symbol x)))
  619.              (and sym (cdr (assq sym dmacro-doclist))))
  620.                (abbrev-expansion x)
  621.                )))
  622.        lis)
  623.     ;; 1st time through, just list dmacro names.
  624.     (progn
  625.       (setq dmacro-verbose-list str)
  626.       lis))
  627.       ))
  628.     ))
  629.  
  630.  
  631. (defun dmacro-common-prefix (s1 s2)
  632.  ;; If one's nil, the other wins.
  633.   (if (not (and s1 s2))
  634.       (or s1 s2))
  635.   (let ((len (min (length s1) (length s2)))
  636.     (i 0))
  637.     (while (and (< i len) (= (elt s1 i) (elt s2 i)))
  638.       (setq i (1+ i)))
  639.     ;; If no common prefix, return nil
  640.     (if (zerop i)
  641.     nil
  642.       (substring s1 0 i))
  643.     ))
  644.     
  645.  
  646. (defun dmacro-fix-marks-hack ()
  647.   ;; (save-excursion saves mark ring too, so we have to save point by hand)
  648.   (if (and (not dmacro-recurse) dmacro-fix-marks)
  649.       (let ((my-mark-list (cons (mark-marker) mark-ring))
  650.         savep)
  651.     (setq savep (point))
  652.     (mapcar (function (lambda (m)
  653.                 (goto-char m)
  654.                 (skip-chars-forward " \t")
  655.                 (let ((badm (dmacro-member m my-mark-list)))
  656.                   (if badm
  657.                   (set-marker badm (point))
  658.                 ))
  659.                 ))
  660.         dmacro-fix-marks)
  661.     (goto-char savep)
  662.     )))
  663.  
  664.  
  665. ;; Just like memq except: comparison is done with equal not eq;
  666. ;; returns the element, not the tail of the list whose care is ELT
  667. ;; Like (car (member ...)) in Common Lisp.
  668. (defun dmacro-member (elt list)
  669.   (catch 'got-it
  670.     (mapcar (function (lambda (x)
  671.             (if (equal x elt)
  672.                 (throw 'got-it x))
  673.             ))
  674.         list)
  675.     nil))
  676.   
  677.  
  678. ;; 
  679. ;; Auto dmacros
  680. ;; 
  681.  
  682. (setq find-file-hooks
  683.       (cons 'auto-dmacro find-file-hooks))
  684.  
  685. (defun auto-dmacro ()
  686.   (if (and (not buffer-read-only)
  687.        (zerop (buffer-size)))
  688.       (let ((alist auto-dmacro-alist)
  689.         (fn (file-name-sans-versions buffer-file-name))
  690.         )
  691.     (while (and alist
  692.             (if (and (string-match (car (car alist)) fn)
  693.                  (abbrev-symbol (symbol-name (cdr (car alist)))))
  694.             (progn
  695.               (insert-dmacro (symbol-name (cdr (car alist))))
  696.               (set-buffer-modified-p nil)
  697.               (message "New file. Inserted dmacro: %s"
  698.                    (symbol-name (cdr (car alist))))
  699.               nil)
  700.               (setq alist (cdr alist))
  701.               ))
  702.       ))
  703.     ))
  704.  
  705.  
  706. (defun dmacro-expand-region (start end)
  707.   ;; reset the prompt data list, unless we've specifically asked not to.
  708.   (if (null dmacro-recurse)
  709.       (progn
  710.     (setplist 'dmacro-strings nil)
  711.     (setq dmacro-point nil
  712.           dmacro-marks nil
  713.           dmacro-fix-marks nil)
  714.     ))
  715.   (let ((endm (set-marker (make-marker) end))
  716.     (dmacro-ts nil)
  717.     (dmacro-fn nil)
  718.     (dmacro-last-prompt 'your-text))
  719.     (goto-char start)
  720.     (while (and (< (point) endm)
  721.         (search-forward dmacro-prefix-char endm t nil))
  722.       (let* ((cmdbeg (point))
  723.          ;; parsed command (<func-name> <func-pointer> <arglist> <modlist>)
  724.          (cmd (dmacro-parse
  725.            (cond ((= ?\( (char-after cmdbeg))
  726.               ;; It's a macro (with possible modifiers)
  727.               (read (current-buffer)))
  728.              ((= ?w (char-syntax (char-after cmdbeg)))
  729.               (forward-word 1)
  730.               (car (read-from-string
  731.                 (buffer-substring cmdbeg (point))
  732.                  ))
  733.               )
  734.              (t (forward-char 1)
  735.                 (car (read-from-string (char-to-string 
  736.                             (char-after cmdbeg))))
  737.                 ))
  738.            nil nil))
  739.          (text
  740.           (if (nth 1 cmd)
  741.           (progn
  742.             (delete-region (1- cmdbeg) (point))
  743.             (dmacro-run cmd)
  744.             )))
  745.          )
  746.     (if text (insert text))
  747.     ))
  748.     (if (not dmacro-recurse)
  749.     (progn
  750.       (if (null dmacro-point) (setq dmacro-point endm))
  751.       ;; If the user wants the extra text inserted at a mark instead of
  752.       ;; point, we have to swap values of point and the specified mark.
  753.       (if (and dmacro-goto-mark dmacro-marks)
  754.           (let* ((marknum (- (length dmacro-marks)
  755.                  (prefix-numeric-value dmacro-goto-mark)))
  756.              (ourmark (nthcdr
  757.                    (if (> 0 marknum) 
  758.                    (1- (length dmacro-marks)) marknum)
  759.                    dmacro-marks))
  760.              (newpoint (car ourmark)))
  761.         (setcar ourmark (copy-marker dmacro-point))
  762.         (setq dmacro-point newpoint)))
  763.       (mapcar
  764.        (function (lambda (m)
  765.                (push-mark m t)
  766.                (set-marker m nil) ; null it so it doesn't slow editting
  767.                ))
  768.        dmacro-marks)
  769.       ;; If there was no point set, AND we started at the end of the
  770.       ;; region AND we wound up after the original point marker,
  771.       ;; then the very last thing in the region was a command and it
  772.       ;; got expanded after the marker.  Therefore, we should leave
  773.       ;; the point alone and not move it back.  Example: Today is
  774.       ;; Wed Wrong^ ^Correct
  775.       (if (not (and (= dmacro-point endm)
  776.             (> (point) endm)))
  777.           (goto-char dmacro-point))
  778.       (if dmacro-extra (insert-before-markers dmacro-extra))
  779.       ;; Fill in the blanks
  780.       (if dmacro-prompt
  781.           (dmacro-process-strings (if (not (eq t dmacro-prompt))
  782.                         (dmacro-get-words start))))
  783.       ))
  784.     ))
  785.  
  786.  
  787. (defun dmacro-parse (cmd args mods)
  788.   (if (and (listp cmd)
  789.        (listp (car cmd)))
  790.       ;; the cdr is definitely a modlist
  791.       (dmacro-parse (car cmd) nil (cons (cdr cmd) mods))
  792.     (let (func lookup)
  793.       (if (listp cmd)
  794.       (setq func (car cmd)
  795.         args (or args (cdr cmd)))
  796.     (setq func cmd)
  797.     )
  798.       (setq lookup (cdr (assq func dmacro-functions)))
  799.       (if (and (listp lookup)
  800.            (eq ':alias (car lookup)))
  801.       (dmacro-parse (cdr lookup) args mods)
  802.     (list func lookup args mods)
  803.     ))
  804.     ))
  805.  
  806.  
  807. ;; parsed command (<func-name> <func-pointer> <arglist> <modlist>)
  808. (defun dmacro-run (cmd)
  809.   (dmacro-apply-modifiers
  810.    (if (or (eq 'prompt (car cmd))
  811.        (eq 'if (car cmd)))
  812.        ;; must remember mods for post-prompt processing
  813.        (apply (nth 1 cmd) (nth 3 cmd) (nth 2 cmd))
  814.      (apply (nth 1 cmd) (nth 2 cmd)))
  815.    (nth 3 cmd)
  816.    (eq 'prompt (car cmd))
  817.    ))
  818.  
  819.  
  820. ;; Process the modifiers
  821. (defun dmacro-apply-modifiers (text modlist forbid-trunc)
  822.   (if (null text)
  823.       ""
  824.     (while modlist
  825.       (let ((modifiers (car modlist))
  826.         (pad ?\ )
  827.         caser
  828.         mod-start mod-end sexps)
  829.     (while modifiers
  830.       (cond ((numberp (car modifiers))
  831.          ;; substring
  832.          (if mod-start
  833.              (setq mod-end (car modifiers))
  834.            (setq mod-start (car modifiers))))
  835.         ;; sub-expressions, not characters
  836.         ((eq ':sexp (car modifiers))
  837.          (setq sexps t))
  838.         ;; left-padding
  839.         ((eq ':pad (car modifiers))
  840.          (setq modifiers (cdr modifiers)
  841.                pad (car modifiers)))
  842.         ;; upper/lower/capitalized
  843.         ((setq caser (cdr (assq (car modifiers)
  844.                     '((:up . upcase)
  845.                       (:down . downcase)
  846.                       (:cap . capitalize)))))
  847.          ))
  848.       (setq modifiers (cdr modifiers)))
  849.     (if (and mod-start (not forbid-trunc))
  850.         (condition-case nil
  851.         (setq text 
  852.               (if sexps
  853.               (substring text
  854.                      (dmacro-sexp-pos text mod-start t)
  855.                      (dmacro-sexp-pos text mod-end nil))
  856.             (substring text mod-start mod-end)))
  857.           (error))
  858.       )
  859.     (if (and (not (eq pad ?\ ))
  860.          (string-match "^\\s-+" text))
  861.         (setq text
  862.           (concat (if pad
  863.                   (make-string (- (match-end 0)
  864.                           (match-beginning 0))
  865.                        pad))
  866.               (substring text (match-end 0))
  867.               ))
  868.            )
  869.     (if caser
  870.         (setq text (funcall caser text)))
  871.     )
  872.       (setq modlist (cdr modlist)))
  873.     text))
  874.  
  875.  
  876. (defun dmacro-sexp-pos (text count startP)
  877.   (if count
  878.       (save-excursion
  879.     (set-buffer (get-buffer-create " dmacro-temp"))
  880.     (erase-buffer)
  881.     (let ((emacs-lisp-mode-hook nil))
  882.       (emacs-lisp-mode))
  883.     (insert text)
  884.     (if (< count 0)
  885.         (goto-char (point-max))
  886.       (goto-char (point-min)))
  887.     (forward-sexp count)
  888.     (if (and (not startP) (< count 0))
  889.         (forward-sexp 1)
  890.       (if (>= count 0)
  891.           (progn          
  892.         (forward-sexp 1)
  893.         (if startP
  894.             (backward-sexp 1)
  895.           ))
  896.         ))
  897.     (- (point) (point-min)))
  898.     ))
  899.  
  900.  
  901.  
  902. (defun dmacro-process-strings (words)
  903.   (save-excursion
  904.     (let ((fillin (symbol-plist 'dmacro-strings))
  905.       ;; set to nil so read functions will do their stuff
  906.       (executing-macro nil))
  907.       (while fillin
  908.     (let* ((blanks (reverse (car (cdr fillin))))
  909.            (prompt (car blanks))
  910.            (str (if words
  911.             (car words)
  912.               (apply (or (car (cdr prompt)) 'read-string) 
  913.                  (or (car prompt) 
  914.                  (concat
  915.                   (capitalize (symbol-name (car fillin))) ": "))
  916.                  (cdr (cdr prompt))))
  917.             ))
  918.       (while (setq blanks (cdr blanks))
  919.         (goto-char (car (car blanks)))
  920.                     ; Remove the "<foo>"
  921.         (insert-before-markers
  922.          (if (car (cdr (car blanks)))
  923.          ;; It's a modifier list
  924.          (progn
  925.            (delete-char (+ 2 (length (symbol-name (car fillin)))))
  926.            (dmacro-apply-modifiers str
  927.                          (cdr (cdr (car blanks)))
  928.                          nil)
  929.            )
  930.            ;; It's the then/else clauses from a conditional expression
  931.            (dmacro-apply-modifiers
  932.         (if (or (null str) (string= "" str))
  933.             (dmacro-funcall (nth 3 (car blanks)))
  934.           (dmacro-funcall (nth 2 (car blanks))))
  935.         (nth 4 (car blanks))
  936.         nil)
  937.            ))
  938.         ))
  939.     (setq fillin (cdr (cdr fillin))
  940.           words (cdr words))
  941.     ))
  942.     (setplist 'dmacro-strings nil)
  943.     ))
  944.  
  945. (defun dmacro-get-words (end)
  946.   (save-excursion
  947.     (save-restriction
  948.       (goto-char end)
  949.       (narrow-to-region (point-min) end)
  950.       (let ((cnt (/ (length (symbol-plist 'dmacro-strings)) 2))
  951.         (loc end)
  952.         (lis nil))
  953.     (while (not (zerop cnt))
  954.       (backward-sexp)
  955.       (let ((s (point))
  956.         (trim (if (= ?\" (char-after (point)))
  957.               1 0)))
  958.         (forward-sexp)
  959.         (setq lis (cons (buffer-substring (+ s trim) (- (point) trim))
  960.                 lis))
  961.         )
  962.       (backward-sexp)
  963.       (setq cnt (1- cnt))
  964.       )
  965.     (delete-region (point) end)
  966.     lis)
  967.       ))
  968.   )
  969.  
  970.  
  971. ;; 
  972. ;; Macro helpers
  973. ;; 
  974.  
  975. ;; Most dmacros won't need to know the current time or the filename, so
  976. ;; we don't want to compute it each time we do a dmacro-expand-region.
  977. ;; On the other hand, we don't want to compute it each time it's asked for
  978. ;; since that would be inefficient if a dmacro needed it several times
  979. ;; (e.g. "~hour:~min:~sec") (not to mention the fact that the time string
  980. ;; could change during expansion and inconsistent information from two
  981. ;; different times strings.
  982. ;;   Anyway, these functions compute the information once and then save it
  983. ;; for the extent of the current expansion.
  984.  
  985. (defun dmacro-ts ()
  986.   (or dmacro-ts (setq dmacro-ts (current-time-string))))
  987. (defun dmacro-fn ()
  988.   (or dmacro-fn (setq dmacro-fn (or (buffer-file-name) (buffer-name)))))
  989.  
  990.  
  991. ;; ...   name ((prompt label prompter args) [(loc . caser)...]) ...
  992. ;; E.g.: function (("Enter function: " "<function>" 'read-string nil) 
  993. ;;                 (120 . capitalize) (140 . identity))
  994.  
  995. (defun dmacro-save-string (typeflag val itemname args)
  996.   (if (null (get 'dmacro-strings itemname))
  997.       (put 'dmacro-strings itemname (list args)))
  998.   (put 'dmacro-strings itemname
  999.        (cons (cons (point-marker) (cons typeflag val))
  1000.          (get 'dmacro-strings itemname)))
  1001.   )
  1002.  
  1003.  
  1004. ;; Used by the "if" macro and other things to evaluate its args.
  1005. ;; Returns SEXP if it's a string or nil, otherwise it treats SEXP as a
  1006. ;; dmacro command and parses and runs it and returns the resulting
  1007. ;; string (or nil if the result was nil or the empty string).
  1008.  
  1009. (defun dmacro-funcall (sexp)
  1010.   (if (stringp sexp)
  1011.       sexp
  1012.     (if (null sexp)
  1013.     nil
  1014.       (let* ((res (dmacro-run (dmacro-parse sexp nil nil))))
  1015.     (if (string= "" res)
  1016.         nil
  1017.       res)
  1018.     ))
  1019.     ))
  1020.  
  1021.  
  1022. (defun dmacro-month-num ()
  1023.   (1+ (/ (string-match 
  1024.       (substring (dmacro-ts) 4 7)
  1025.       "JanFebMarAprMayJunJulAugSepOctNovDec")
  1026.      3)))
  1027.  
  1028.  
  1029. ;; Thanks to Dean Norris (William Dean Norris II) at UFL for insisting
  1030. ;; that this be added.
  1031.  
  1032. (defun dmacro-initials ()
  1033.   "Given a user name, return a string containing the user's initials.
  1034. See also the description of the variable DMACRO-RANK-IN-INITIALS, which 
  1035. affects the behavior of this function."
  1036.   (let ((fullname (user-full-name))
  1037.     (index -1)
  1038.     (res nil))
  1039.     (while index
  1040.       (setq index (string-match "\\<" fullname (1+ index)))
  1041.       (if index (setq res (concat res (substring fullname index (1+ index)))))
  1042.       )
  1043.     ;; If the last word was a rank, either add the rest of the word, or
  1044.     ;; delete the first char that was processed (depending on the value of
  1045.     ;; dmacro-rank-in-initials).
  1046.     (if (and (> (length res) 1)
  1047.          (string-match "\\([IVX]+\\|[JS]R\\)\\.?$" fullname (match-end 0)))
  1048.     (if dmacro-rank-in-initials
  1049.         (concat res (substring fullname 
  1050.                    (1+ (match-beginning 1))
  1051.                    (match-end 1)))
  1052.       (substring res 0 -1)
  1053.       )
  1054.       res)
  1055.     ))
  1056.       
  1057. (defun dmacro-push-mark ()
  1058.   ;; If we're indenting, notice all marks which appear on a line with
  1059.   ;; only whitespace to their left.  They will have to be indented by
  1060.   ;; hand, since c-indent (among others) doesn't do the right thing.
  1061.   (if (and dmacro-fix-marks-on
  1062.        (not (bolp))
  1063.        (save-excursion
  1064.          (skip-chars-backward " \t")
  1065.          (bolp)))
  1066.       (setq dmacro-fix-marks (cons (copy-marker (point)) dmacro-fix-marks)))
  1067.   (setq dmacro-marks (cons (copy-marker (point)) dmacro-marks))
  1068.   nil)
  1069.  
  1070.  
  1071. ;;; 
  1072. ;;; MODULE NAME
  1073. ;;; 
  1074.  
  1075. (provide 'dmacro)
  1076.