home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Source Code 1993 July / THE_SOURCE_CODE_CD_ROM.iso / languages / elisp / interfaces / hyperbutton.shar / hyperbutton.el next >
Encoding:
Text File  |  1991-07-03  |  17.5 KB  |  468 lines

  1. ;; ========================================================================
  2. ;; hyperbutton.el -- Hypertext-style buttons
  3. ;; Author          : Mike Williams <mike-w@cs.aukuni.ac.nz>
  4. ;; Created On      : Thu Mar 28 13:42:44 1991
  5. ;; Last Modified By: Mike Williams
  6. ;; Last Modified On: Tue Jun 25 15:42:31 1991
  7. ;; RCS Info        : $Revision: 1.14 $ $Locker:  $
  8. ;; ========================================================================
  9. ;; [[ CheckMeOut ]] [[ CheckMeIn ]]
  10. ;; 
  11. ;; NOTE: this file must be recompiled if changed.
  12. ;;
  13. ;; Copyright (C) Mike Williams <mike-w@cs.aukuni.ac.nz> 1991
  14. ;;
  15. ;; This file is not part of GNU Emacs, but is made available under the
  16. ;; same conditions.
  17. ;;
  18. ;; GNU Emacs is distributed in the hope that it will be useful, but
  19. ;; WITHOUT ANY WARRANTY.  No author or distributor accepts responsibility
  20. ;; to anyone for the consequences of using it or for whether it serves
  21. ;; any particular purpose or works at all, unless he says so in writing.
  22. ;; Refer to the GNU Emacs General Public License for full details.
  23. ;;
  24. ;; Everyone is granted permission to copy, modify and redistribute GNU
  25. ;; Emacs, but only under the conditions described in the GNU Emacs
  26. ;; General Public License.  A copy of this license is supposed to have
  27. ;; been given to you along with GNU Emacs so you can know your rights and
  28. ;; responsibilities.  It should be in a file named COPYING.  Among other
  29. ;; things, the copyright notice and this notice must be preserved on all
  30. ;; copies.
  31.  
  32. ;; This package provides support for hypertext-like buttons.  The user may
  33. ;; define `button handlers' to handle hyperbutton `events'.  Handlers will
  34. ;; usually return nil, indicating that the preconditions for their
  35. ;; selection have not been fulfilled -- however, when conditions are right,
  36. ;; they may return a lisp form to be evaluated.  A list of handlers is
  37. ;; maintained ... the 'determine-hyperbutton-form function will iterate
  38. ;; over these until one returns a form to be evaluated.  The functions
  39. ;; hyperbutton:{add,remove}-{global,local}-handler make installing and
  40. ;; uninstalling handlers easier, eg;
  41. ;;
  42. ;;   (hyperbutton:remove-global-handler 'hyperbutton:view-file)
  43. ;;
  44. ;; One standard hyperbutton handler, 'hyperbutton:lookup-button, provides
  45. ;; an easier and more efficient (although less general) interface for
  46. ;; defining buttons.  This function uses an association list of regular
  47. ;; expressions to lisp forms to search for a form to evaluate.  See the
  48. ;; documentation for variable 'hyperbutton:global-button-alist for details.
  49. ;; 
  50. ;; The functions {define,undefine}-{global,local}-hyperbutton make
  51. ;; manipulating (adding to and deleting from) the button-alists a bit
  52. ;; easier, eg.  
  53. ;;
  54. ;;   (define-local-hyperbutton 'word "CheckMeIn" '(rcs-ci-buffer))
  55. ;;   (undefine-local-hyperbutton 'word "CheckMeIn")
  56.  
  57. ;; Many thanks to Rick Mugridge <rick@cs.aukuni.ac.nz> for the idea.
  58.  
  59. (require 'backquote)
  60. (require 'thing-at-point)
  61.  
  62. (provide 'hyperbutton)
  63.  
  64. ;; LCD Archive Entry:
  65. ;; hyperbutton|Mike Williams|mike-w@cs.aukuni.ac.nz
  66. ;; |Run lisp code when arbitrary text patterns are clicked on
  67. ;; |91-06-25|$Revision: 1.14 $|~/interfaces/hyperbutton.shar.Z
  68.  
  69. ;;=== Usage ===============================================================
  70. ;; 
  71. ;; (autoload 'call-hyperbutton "hyperbutton" nil t)
  72. ;; (autoload 'determine-hyperbutton-form "hyperbutton")
  73. ;; (autoload 'hyperbutton:goto-card "hyperbutton")
  74. ;; 
  75. ;; (autoload 'define-global-hyperbutton "hyperbutton" nil t)
  76. ;; (autoload 'define-local-hyperbutton "hyperbutton" nil t)
  77. ;; (autoload 'undefine-global-hyperbutton "hyperbutton")
  78. ;; (autoload 'undefine-local-hyperbutton "hyperbutton")
  79. ;;
  80. ;; And for the adventurous ...
  81. ;; (autoload 'hyperbutton:add-global-handler "hyperbutton")
  82. ;; (autoload 'hyperbutton:add-local-handler "hyperbutton")
  83. ;; (autoload 'hyperbutton:remove-global-handler "hyperbutton")
  84. ;; (autoload 'hyperbutton:remove-local-handler "hyperbutton")
  85.  
  86. ;;=== Version =============================================================
  87.  
  88. (defconst hyperbutton:version (substring "$Revision: 1.14 $" 11 -2)
  89.   "The revision number of dired (as string).  The complete RCS id is:
  90.  
  91.   $Id: hyperbutton.el,v 1.14 1991/06/25 03:43:55 mike-w Exp $")
  92.  
  93. ;;=== How it works ========================================================
  94. ;;
  95. ;; Determine-hyperbutton-form runs thru the list of handler functions,
  96. ;; calling each in turn until one returns non-nil.  Each handler returns a
  97. ;; form to be evaluated, or nil.
  98. ;;
  99. ;; Call-hyperbutton evaluates the result of a call to
  100. ;; determine-hyperbutton-form.  The two are separated, as it is reasonable
  101. ;; to use determine-hyperbutton-form elsewhere.  For instance, I use a
  102. ;; mouse handler which determines the hyperbutton form at the point the
  103. ;; mouse is clicked, but returns to the original window before evaluating
  104. ;; it.  Here's a pair of x-mouse functions which should work with the
  105. ;; standard x-mouse.el. 
  106.  
  107. (cond 
  108.  ((eq window-system 'x)
  109.   
  110.   (defconst x-previous-window nil 
  111.     "Window you were in before mouse-down")
  112.   (defconst x-previous-position (make-marker) 
  113.     "Position in target buffer before mouse-down")
  114.  
  115.   (defun x-mouse-hyperbutton-down (arg)
  116.     (setq x-previous-window (selected-window))
  117.     (x-mouse-select arg)
  118.     (set-marker x-previous-position (point))
  119.     (x-mouse-set-point arg))
  120.  
  121.   (defun x-mouse-hyperbutton-up (arg)
  122.     (let (form)
  123.       (unwind-protect
  124.       (progn
  125.         (x-mouse-set-point arg)
  126.         (setq form (determine-hyperbutton-form)))
  127.     (goto-char (marker-position x-previous-position))
  128.     (select-window x-previous-window)
  129.     (setq x-previous-window nil)
  130.     (set-marker x-previous-position nil))
  131.       (eval form)))
  132.  
  133.   ))
  134.  
  135. ;; Bind the first to a mouse-down event, and the second to the
  136. ;; corresponding mouse-up event.  eg.
  137. ;;
  138. ;;   (define-key mouse-map x-button-c-left     'x-mouse-hyperbutton-down) 
  139. ;;   (define-key mouse-map x-button-c-left-up  'x-mouse-hyperbutton-up)
  140. ;;   
  141. ;; Writing similar functions for use with emacstool (under suntools), or
  142. ;; for epoch, should be straightforward.  Alternatively, you could bind
  143. ;; call-hyperbutton to a key. eg.
  144. ;;
  145. ;;   (global-set-key "\M-+" 'call-hyperbutton)
  146.  
  147. ;;=== Standard buttons and handlers =======================================
  148. ;;
  149. ;; * File browsing [hyperbutton:view-file]
  150. ;;   Click on filename in any buffer to view the corresponding file.
  151. ;;   Hyperbutton searches for this file on the hyperbutton:find-file-path.
  152. ;;
  153. ;; * Evaluate arbitrary elisp form:
  154. ;;   [[ Eval: (message "Hi there") ]]    <-- click me
  155. ;;
  156. ;; * Find or view a file:
  157. ;;   [[ Find: ~/.emacs ]]        <-- click me
  158. ;;   [[ View: /etc/motd ]]        <-- click me
  159. ;;
  160. ;; * Send mail:
  161. ;;   [[ Mail: groucho, chico ]]        <-- click me
  162. ;;
  163. ;; * View a buffer in my ~/.HyperCard directory:
  164. ;;   [[ Card: Home ]]            <-- click me
  165.  
  166. ;;=== Ideas ===============================================================
  167. ;;
  168. ;;  * Define hyperbuttons for commonly executed functions, eg. reading
  169. ;;    mail [[ ReadMail ]], compiling an Emacs-Lisp file [[ CompileMe ]],
  170. ;;    banging your head against a brick wall [[ BrickWall ]].
  171. ;;
  172. ;;   (setq hyperbutton:global-button-alist
  173. ;;         (append
  174. ;;          hyperbutton:global-button-alist
  175. ;;          (list
  176. ;;           '(hyperbutton
  177. ;;             ("\\s +ReadMail\\s +" . (if (fboundp 'vm) (vm) (rmail)))
  178. ;;             ("\\s +CompileMe\\s +" . (byte-compile-file buffer-file-name))
  179. ;;             ))))
  180. ;;             
  181. ;;   (define-global-hyperbutton 'hyperbutton "BrickWall" '(doctor))
  182. ;;   
  183. ;;  * Create a handler that will popup help on certain keywords when they
  184. ;;    are clicked on.
  185. ;;  
  186. ;;  * Define handlers for easy browsing in info.
  187. ;;    [see info-hyper.el package]
  188. ;;    
  189. ;;  * Define handler to visit clicked-on error in *Compilation* buffer.
  190. ;;    [write me for details]
  191. ;;
  192. ;;  * Define a handler execute find-tag in source code buffers.
  193. ;;
  194. ;;  * Define handlers/buttons for GNUS/VM to allow easy selection of
  195. ;;    groups, articles and mail messages.
  196.  
  197. ;;=== Main user functions =================================================
  198.  
  199. (defun call-hyperbutton ()
  200.   "Apply functions in hyperbutton-handlers in turn, until one returns non-nil."
  201.   (interactive)
  202.   (eval (determine-hyperbutton-form)))
  203.  
  204. (defun determine-hyperbutton-form ()
  205.   (let ((button-list (append hyperbutton:local-handlers
  206.                  hyperbutton:global-handlers
  207.                  '(hyperbutton:undefined)))
  208.     return-val)
  209.     (while (and (not return-val) button-list)
  210.       (setq return-val (call-interactively (car button-list)))
  211.       (setq button-list (cdr button-list)))
  212.     return-val))
  213.  
  214. ;;=== Handler variables ===================================================
  215.  
  216. (defvar hyperbutton:global-handlers 
  217.   '(hyperbutton:lookup-button hyperbutton:view-file)
  218.   "Global list of functions to be called in turn by \\[hyperbutton], until
  219. one returns a form to be evaluated.
  220. Note that the hyperbutton:local-handlers take precedence.") 
  221.  
  222. (defvar hyperbutton:local-handlers nil
  223.   "Buffer-local list of functions to be called in turn by \\[hyperbutton],
  224. until one returns a form to be evaluated.
  225. These take precedence over the hyperbutton:global-handlers.") 
  226. (make-variable-buffer-local 'hyperbutton:local-handlers)
  227.  
  228. ;;=== Useful handlers =====================================================
  229.  
  230. ;;--- Default handler ---
  231.  
  232. (defun hyperbutton:undefined ()
  233.   (interactive)
  234.   '(message "No matching button"))
  235.  
  236. ;;--- Find/View files ---
  237.  
  238. (defvar hyperbutton:find-file-path '(nil "/usr/include")
  239.   "List of directories in which hyperbutton:find-file tries to locate files.
  240. It might be useful to set this to include directories like /usr/include.")
  241. (defvar hyperbutton:find-file-extensions '("" ".el" ".h")
  242.   "List of file extensions allowed by hyperbutton:find-file.")
  243.  
  244. (defun hyperbutton:find-file (FILE &optional VIEW)
  245.   "Find filename at point."
  246.   (interactive (list (thing-at-point 'filename)))
  247.   ;; Requires locate-file, from the lib-complete.el package
  248.   (if (not (featurep 'lib-complete))
  249.       (error "hyperbutton:find-file requires the lib-complete package"))
  250.   (let ((path (if FILE
  251.           (locate-file (substitute-in-file-name FILE)
  252.                    hyperbutton:find-file-path
  253.                    hyperbutton:find-file-extensions))))
  254.     (if path
  255.     (if VIEW 
  256.         (` (view-file (, path))) 
  257.       (` (find-file (, path)))))
  258.     ))
  259.  
  260. (defun hyperbutton:view-file (FILE)
  261.   "View filename at point."
  262.   (interactive (list (thing-at-point 'filename)))
  263.   (hyperbutton:find-file FILE 'view))
  264.  
  265. ;;--- View files in hypercard-directory ---
  266.  
  267. (defvar hyperbutton:hypercard-path '(nil "~/.HyperCard"))
  268.  
  269. (defun hyperbutton:goto-card (CARD)
  270.   "Goto specified card."
  271.   (interactive (list (thing-at-point 'sexp)))
  272.   (let ((path (and CARD 
  273.            (locate-file CARD hyperbutton:hypercard-path))))
  274.     (if path
  275.     (` (view-file (, path))))))
  276.  
  277. ;;=== General button handler ==============================================
  278.  
  279. ;;--- Hyperbutton name extraction ---
  280.  
  281. (defvar hyperbutton:start-re (concat (regexp-quote "[[") "[ \t]*"))
  282. (defvar hyperbutton:end-re   (concat "[ \t]*" (regexp-quote "]]")))
  283.  
  284. (defun beginning-of-hyperbutton () 
  285.   "Search backward for hyperbutton:start-re, and position point at end."
  286.   (re-search-backward hyperbutton:start-re)
  287.   (re-search-forward hyperbutton:start-re))
  288.  
  289. (defun end-of-hyperbutton () 
  290.   "Search forward for hyperbutton:end-re, and position point at beginning."
  291.   (re-search-forward hyperbutton:end-re)
  292.   (re-search-backward hyperbutton:end-re))
  293.  
  294. ;;--- Button/function association ---
  295.  
  296. (defvar hyperbutton:global-button-alist
  297.  
  298.   '((hyperbutton
  299.      ("\\`\\s *Eval:" . 
  300.       (message "%s"
  301.            (eval (read-from-whole-string 
  302.               (substring hyperbutton-name (match-end 0))))))
  303.      ("^\\s *Find:\\s *\\(\\S +\\)\\s *$" . 
  304.       (find-file (substring hyperbutton-name 
  305.                 (match-beginning 1) (match-end 1))))
  306.      ("^\\s *View:\\s *\\(\\S +\\)\\s *$" . 
  307.       (view-file (substring hyperbutton-name 
  308.                 (match-beginning 1) (match-end 1))))
  309.      ("^\\s *Card:\\s *\\(\\S +\\)\\s *$" . 
  310.       (hyperbutton:goto-card
  311.        (substring hyperbutton-name 
  312.           (match-beginning 1) (match-end 1))))
  313.      ("^\\s *Mail:\\s *\\(.+\\)$" . 
  314.       (mail nil (substring hyperbutton-name 
  315.                (match-beginning 1) (match-end 1))))))
  316.   
  317.   "Alist used by hyperbutton:lookup-button to determine a form to evaluate.
  318.  
  319.   ((THING 
  320.     (REGEXP . BODY)
  321.     (REGEXP . BODY)
  322.     ...)
  323.    (THING 
  324.     (REGEXP . BODY)
  325.     (REGEXP . BODY)
  326.     ...))
  327.  
  328.   When the THING at point (cf. thing-at-point) matches associated regular 
  329. expression REGEXP, execute BODY with the symbol 'hyperbutton-name 
  330. dynamically bound to the THING matched.") 
  331.  
  332. (defvar hyperbutton:local-button-alist nil
  333.   "Local alist used by hyperbutton:lookup-button to determine a form to 
  334. evaluate.  See documentation for hyperbutton:global-button-alist for 
  335. details.  Note that local definitions take precedence over global ones.")
  336. (make-variable-buffer-local 'hyperbutton:local-button-alist)
  337.  
  338. (defun hyperbutton:lookup-button ()
  339.   "Determine a form to be evaluated using hyperbutton:local-button-alist 
  340. and hyperbutton:global-button-alist."
  341.   (interactive)
  342.   (catch 'hyperbutton-form
  343.     (let ((clauses (append hyperbutton:local-button-alist 
  344.                hyperbutton:global-button-alist)))
  345.       (while clauses 
  346.     (let ((button-name (thing-at-point (car (car clauses))))
  347.           (alist (cdr (car clauses))))
  348.       (if (not button-name) nil
  349.         (while alist
  350.           (if (string-match (car (car alist)) button-name)
  351.           (throw 'hyperbutton-form 
  352.              (` (let ((hyperbutton-name (, button-name)))
  353.                   (eval (, (cdr (car alist))))))))
  354.           (setq alist (cdr alist)))))
  355.     (setq clauses (cdr clauses))))))
  356.  
  357. ;;=========================================================================
  358. ;;=== Utilities ===========================================================
  359.  
  360. (defun hyperbutton:filter (LIST PRED)
  361.   "Return list of elements in LIST for which PRED is true."
  362.   (cond 
  363.    ((not LIST) nil)
  364.    ((funcall PRED (car LIST))
  365.     (cons (car LIST) (hyperbutton:filter (cdr LIST) PRED)))
  366.    (t (hyperbutton:filter (cdr LIST) PRED))))
  367.  
  368. ;;=== Add/remove handlers =================================================
  369.  
  370. (defun hyperbutton:add-handler (HANDLER-LIST HANDLER)
  371.   "Update HANDLER-LIST to include HANDLER."
  372.   (if (catch 'member
  373.     (mapcar (function (lambda (elt) (if (equal elt HANDLER) 
  374.                         (throw 'member t))))
  375.         (symbol-value HANDLER-LIST))
  376.     nil) nil
  377.     (set HANDLER-LIST (append (symbol-value HANDLER-LIST) (list HANDLER)))))
  378.  
  379. (defun hyperbutton:add-global-handler (HANDLER)
  380.   "Update hyperbutton:global-handlers to include HANDLER."
  381.   (hyperbutton:add-handler 'hyperbutton:global-handlers HANDLER))
  382.  
  383. (defun hyperbutton:add-local-handler (HANDLER)
  384.   "Update hyperbutton:local-handlers to include HANDLER."
  385.   (hyperbutton:add-handler 'hyperbutton:local-handlers HANDLER))
  386.                
  387. (defun hyperbutton:remove-handler (HANDLER-LIST HANDLER)
  388.   "Update HANDLER-LIST to exclude HANDLER."
  389.   (set HANDLER-LIST
  390.        (hyperbutton:filter 
  391.     (symbol-value HANDLER-LIST)
  392.     (function (lambda (handler) (not (equal handler HANDLER)))))))
  393.  
  394. (defun hyperbutton:remove-global-handler (HANDLER)
  395.   "Update hyperbutton:global-handlers to exclude HANDLER."
  396.   (hyperbutton:remove-handler 'hyperbutton:global-handlers HANDLER))
  397.  
  398. (defun hyperbutton:remove-local-handler (HANDLER)
  399.   "Update hyperbutton:local-handlers to exclude HANDLER."
  400.   (hyperbutton:remove-handler 'hyperbutton:local-handlers HANDLER))
  401.             
  402. ;;=== Easy button definition ==============================================
  403.  
  404. (defun hyperbutton:define-hyperbutton (ALIST THING REGEXP BODY)
  405.   "Update ALIST so that when the THING at point (cf. thing-at-point) 
  406. matches REGEXP, BODY will be executed with hyperbutton-name bound to the 
  407. value of the THING matched."
  408.   (let ((entry (list THING (cons REGEXP BODY))))
  409.     (hyperbutton:undefine-hyperbutton ALIST THING REGEXP)
  410.     (set ALIST (append (symbol-value ALIST) (list entry)))))
  411.  
  412. (defun define-global-hyperbutton (THING REGEXP BODY)
  413.   "Update hyperbutton:global-button-alist so that when the THING at point
  414. \(cf. thing-at-point\) matches REGEXP, BODY will be executed with 
  415. hyperbutton-name bound to the value of the THING matched."
  416.   (interactive 
  417.    (list (read-from-whole-string 
  418.       (completing-read "Thing to define: " obarray))
  419.      (read-string "Regular expression: ")
  420.      (read-from-minibuffer "Form to eval: " nil nil 'read)))
  421.   (hyperbutton:define-hyperbutton 
  422.    'hyperbutton:global-button-alist THING REGEXP BODY))
  423.  
  424. (defun define-local-hyperbutton (THING REGEXP BODY)
  425.   "Update hyperbutton:local-button-alist so that when the THING at point
  426. \(cf. thing-at-point\) matches REGEXP, BODY will be executed with 
  427. hyperbutton-name bound to the value of the THING matched."
  428.   (interactive 
  429.    (list (read-from-whole-string 
  430.       (completing-read "Thing to define: " obarray))
  431.      (read-string "Regular expression: ")
  432.      (read-from-minibuffer "Form to eval: " nil nil 'read)))
  433.   (hyperbutton:define-hyperbutton 
  434.    'hyperbutton:local-button-alist THING REGEXP BODY))
  435.  
  436. (defun hyperbutton:undefine-hyperbutton (ALIST THING REGEXP)
  437.   "Remove any entry in ALIST for THING matching REGEXP."
  438.   (set ALIST
  439.        (hyperbutton:filter 
  440.     (mapcar 
  441.      (function 
  442.       (lambda (thing-assoc) 
  443.         (cons (car thing-assoc)
  444.           (hyperbutton:filter 
  445.            (cdr thing-assoc)
  446.            (function (lambda (regexp-assoc)
  447.                    (not (equal (car regexp-assoc) REGEXP))))))))
  448.      (symbol-value ALIST))
  449.     (function (lambda (thing-assoc)
  450.             (not (null (cdr thing-assoc))))))))
  451.  
  452. (defun undefine-global-hyperbutton (THING REGEXP)
  453.   "Remove global hyperbutton entry for THING matching REGEXP."
  454.   (hyperbutton:undefine-hyperbutton 
  455.    'hyperbutton:global-button-alist THING REGEXP))
  456.  
  457. (defun undefine-local-hyperbutton (THING REGEXP)
  458.   "Remove local hyperbutton entry for THING matching REGEXP."
  459.   (hyperbutton:undefine-hyperbutton 
  460.    'hyperbutton:local-button-alist THING REGEXP))
  461.  
  462. ;;=== That's all folks ====================================================
  463.  
  464. (run-hooks 'hyperbutton-load-hooks)
  465.  
  466. ;;=== END of hyperbutton.el ===============================================
  467.  
  468.