comparison lisp/hyperbole/hbut.el @ 0:376386a54a3c r19-14

Import from CVS: tag r19-14
author cvs
date Mon, 13 Aug 2007 08:45:50 +0200
parents
children 4103f0995bd7
comparison
equal deleted inserted replaced
-1:000000000000 0:376386a54a3c
1 ;;!emacs
2 ;;
3 ;; FILE: hbut.el
4 ;; SUMMARY: Hyperbole button constructs.
5 ;; USAGE: GNU Emacs Lisp Library
6 ;; KEYWORDS: extensions, hypermedia
7 ;;
8 ;; AUTHOR: Bob Weiner
9 ;; ORG: Brown U.
10 ;;
11 ;; ORIG-DATE: 18-Sep-91 at 02:57:09
12 ;; LAST-MOD: 25-Oct-95 at 04:12:59 by Bob Weiner
13 ;;
14 ;; This file is part of Hyperbole.
15 ;; Available for use and distribution under the same terms as GNU Emacs.
16 ;;
17 ;; Copyright (C) 1991-1995, Free Software Foundation, Inc.
18 ;; Developed with support from Motorola Inc.
19 ;;
20 ;; DESCRIPTION:
21 ;; DESCRIP-END.
22
23 ;;; ************************************************************************
24 ;;; Other required Elisp libraries
25 ;;; ************************************************************************
26
27 (require 'hmoccur)
28 (require 'hbmap)
29 (require 'htz)
30 (require 'hbdata)
31 (require 'hact)
32
33 ;;; ************************************************************************
34 ;;; Public definitions
35 ;;; ************************************************************************
36
37 ;;; ========================================================================
38 ;;; ebut class - Explicit Hyperbole buttons
39 ;;; ========================================================================
40
41 (defvar ebut:hattr-save t
42 "*Non-nil value saves button data when button source is saved.
43 Nil disables saving.")
44
45 (defconst ebut:max-len 100
46 "Maximum length of a hyper-button label.")
47
48
49 (defun ebut:alist (&optional file)
50 "Returns alist with each element a list containing a button label.
51 For use as a completion table. Gets labels from optional FILE or current
52 buffer."
53 (mapcar 'list (ebut:list file)))
54
55 (defun ebut:at-p (&optional start-delim end-delim)
56 "Returns explicit Hyperbole button at point or nil.
57 Assumes point is within first line of button label, if at all.
58 Optional START-DELIM and END-DELIM are strings that override default
59 button delimiters."
60 (let ((key (ebut:label-p nil start-delim end-delim)))
61 (and key (ebut:get key))))
62
63 (defun ebut:create (&optional but-sym)
64 "Creates Hyperbole explicit button based on optional BUT-SYM.
65 Default is 'hbut:current'.
66 Button should hold the following attributes (see 'hattr:set'):
67 lbl-key (normalized button label string),
68 loc (filename or buffer where button is located),
69 dir (directory name where button is located),
70 actype (action type that provides a default action for the button),
71 action (optional action that overrides the default),
72 args (list of arguments for action, if action takes a single
73 argument of the button lbl-key, args may be nil).
74
75 If successful returns any instance number to append to button label
76 except when instance number would be 1, then returns t. On failure,
77 returns nil.
78
79 If successful, leaves point in button data buffer, so caller should use
80 'save-excursion'. Does not save button data buffer."
81 (let ((lbl-instance (hbdata:write nil but-sym)))
82 (run-hooks 'ebut:create-hook)
83 lbl-instance))
84
85 (defun ebut:delete (&optional but-sym)
86 "Deletes Hyperbole explicit button based on optional BUT-SYM.
87 Default is 'hbut:current'.
88 Returns entry deleted (a list of attribute values) or nil."
89 (if (null but-sym) (setq but-sym 'hbut:current))
90 (if (ebut:is-p but-sym)
91 (let* ((but-key (hattr:get but-sym 'lbl-key))
92 (loc (hattr:get but-sym 'loc))
93 (entry (hbdata:delete-entry but-key loc)))
94 (run-hooks 'ebut:delete-hook)
95 entry)))
96
97 (defun ebut:get (&optional lbl-key buffer key-src)
98 "Returns explicit Hyperbole button symbol given by LBL-KEY and BUFFER.
99 KEY-SRC is given when retrieving global buttons and is full source pathname.
100 Retrieves button data, converts into a button object and returns a symbol
101 which references the button.
102
103 All arguments are optional. When none are given, returns symbol for
104 button that point is within or nil. BUFFER defaults to the current
105 buffer."
106 (hattr:clear 'hbut:current)
107 (save-excursion
108 (let ((key-file) (key-dir) (but-data) (actype))
109 (or lbl-key (setq lbl-key (ebut:label-p)))
110 (if buffer
111 (if (bufferp buffer) (set-buffer buffer)
112 (error "(ebut:get): Invalid buffer argument: %s" buffer)))
113 (if key-src
114 nil
115 (if (equal lbl-key (ebut:label-p))
116 nil
117 (goto-char (point-min))
118 (ebut:next-occurrence lbl-key))
119 (if (setq key-src (ebut:key-src 'full))
120 ;; 'ebut:key-src' sets current buffer to key-src buffer.
121 (setq buffer (current-buffer)))
122 )
123 (if (and (stringp lbl-key) key-src)
124 (progn
125 (if (stringp key-src)
126 (setq key-dir (file-name-directory key-src)
127 key-file (file-name-nondirectory key-src)))
128 (setq but-data (and key-src
129 (hbdata:get-entry lbl-key (or key-file key-src)
130 key-dir)))
131 (if (null but-data)
132 nil
133 (hattr:set 'hbut:current 'lbl-key lbl-key)
134 (hattr:set 'hbut:current 'loc key-src)
135 (hattr:set 'hbut:current 'categ 'explicit)
136 (hattr:set 'hbut:current 'action nil)
137 (hattr:set 'hbut:current 'actype
138 (intern (setq actype (hbdata:actype but-data))))
139 ;; Hyperbole V1 referent compatibility
140 (if (= (length actype) 2)
141 (hattr:set 'hbut:current 'referent
142 (hbdata:referent but-data)))
143 (hattr:set 'hbut:current 'args (hbdata:args but-data))
144 (hattr:set 'hbut:current 'creator (hbdata:creator but-data))
145 (hattr:set 'hbut:current
146 'create-time (hbdata:create-time but-data))
147 (hattr:set 'hbut:current
148 'modifier (hbdata:modifier but-data))
149 (hattr:set 'hbut:current
150 'mod-time (hbdata:mod-time but-data))
151 'hbut:current)
152 )))))
153
154 (defun ebut:is-p (object)
155 "Returns non-nil if OBJECT denotes an explicit Hyperbole button."
156 (and (symbolp object)
157 (eq (hattr:get object 'categ) 'explicit)))
158
159 (defun ebut:key-of-label-p (key label)
160 "Returns t iff KEY matches to LABEL in a case insensitive manner."
161 (and (stringp key) (stringp label)
162 (equal key (downcase (ebut:label-to-key label)))))
163
164 (defun ebut:key-src (&optional full)
165 "Return key source (usually unqualified) for current Hyperbole button.
166 Also sets current buffer to key source.
167 With optional FULL when source is a pathname, the full pathname is returned."
168 (let ((src (cond ((hmail:mode-is-p) (current-buffer))
169 ((ebut:key-src-fmt))
170 ((save-excursion
171 (save-restriction
172 (widen)
173 (if (and (search-backward hbut:source-prefix nil t)
174 (or (memq (preceding-char) '(?\n ?\^M))
175 (= (point) (point-min))))
176 (hbut:source full)))))
177 (buffer-file-name
178 (if full buffer-file-name
179 (file-name-nondirectory buffer-file-name)))
180 (t (current-buffer))
181 )))
182 (cond ((null src) nil)
183 ((bufferp src)
184 (set-buffer src)
185 src)
186 ((file-readable-p src)
187 (set-buffer (find-file-noselect src))
188 src)
189 ((file-readable-p (setq src (hpath:symlink-referent src)))
190 (set-buffer (find-file-noselect src))
191 src))))
192
193 (defun ebut:key-src-fmt ()
194 "Returns unformatted filename associated with formatted current buffer.
195 This is used to obtain the source of explicit buttons for buffers that
196 represent the output of particular document formatters."
197 (cond ((or (eq major-mode 'Info-mode)
198 (string-match "\\.info\\(-[0-9]+\\)?$" (buffer-name)))
199 (let ((src (and buffer-file-name
200 (substring
201 buffer-file-name
202 0 (string-match "\\.[^.]+$" buffer-file-name)))))
203 (cond ((file-exists-p (concat src ".texi"))
204 (concat src ".texi"))
205 ((file-exists-p (concat src ".texinfo"))
206 (concat src ".texinfo"))
207 ((current-buffer)))))
208 ))
209
210 (defun ebut:key-to-label (lbl-key)
211 "Unnormalizes LBL-KEY and returns a label string approximating actual label."
212 (if lbl-key
213 (let* ((pos 0) (len (length lbl-key)) (lbl) c)
214 (while (< pos len)
215 (setq c (aref lbl-key pos)
216 lbl (concat lbl
217 (if (= c ?_)
218 (if (or (= (1+ pos) len)
219 (/= (aref lbl-key (1+ pos)) ?_))
220 " "
221 (setq pos (1+ pos))
222 "_")
223 (char-to-string c)))
224 pos (1+ pos)))
225 lbl)))
226
227 (defun ebut:label-p (&optional as-label start-delim end-delim pos-flag)
228 "Returns key for Hyperbole button label that point is within.
229 Returns nil if not within a label.
230 Assumes point is within first line of button label, if at all.
231 If optional AS-LABEL is non-nil, label is returned rather than the key
232 derived from the label. Optional START-DELIM and END-DELIM are strings
233 that override default button delimiters. With optional POS-FLAG non-nil,
234 returns list of label-or-key, but-start-position, but-end-position.
235 Positions include delimiters."
236 (let ((opoint (point))
237 (npoint (1+ (point)))
238 (quoted "\\(^\\|[^\\{]\\)")
239 (start)
240 lbl-key end but-start but-end)
241 (or start-delim (setq start-delim ebut:start))
242 (or end-delim (setq end-delim ebut:end))
243 (save-excursion
244 (beginning-of-line)
245 (while (and (progn
246 (while (re-search-forward
247 (concat quoted (regexp-quote start-delim))
248 npoint t)
249 (setq start t))
250 start)
251 (re-search-forward (concat "[^\\{]" (regexp-quote end-delim))
252 npoint t))
253 (setq start nil))
254 (if start
255 (progn
256 (setq start (point)
257 but-start (match-end 1))
258 (if (= ?\( (char-syntax (preceding-char)))
259 (condition-case ()
260 (progn
261 (forward-char -1)
262 (forward-list)
263 (forward-char -2))
264 (error (goto-char (1- opoint))))
265 (goto-char (1- opoint)))
266 (and (< (point) (+ start ebut:max-len))
267 (re-search-forward (concat quoted (regexp-quote end-delim))
268 (+ start ebut:max-len) t)
269 (setq but-end (point)
270 end (- (point) (length end-delim))
271 lbl-key (ebut:label-to-key (buffer-substring start end)))
272 (cond (pos-flag
273 (if as-label
274 (list (ebut:key-to-label lbl-key) but-start but-end)
275 (list lbl-key but-start but-end)))
276 (t (if as-label (ebut:key-to-label lbl-key) lbl-key)))))))))
277
278 (defun ebut:label-regexp (lbl-key &optional no-delim)
279 "Unnormalizes LBL-KEY. Returns regular expr matching delimited but label.
280 Optional NO-DELIM leaves off delimiters and leading and trailing space."
281 (if lbl-key
282 (let* ((pos 0)
283 (len (length lbl-key))
284 (c)
285 (sep0 "[ \t\n\^M]*")
286 (sep "[ \t\n\^M]+")
287 (regexp (if no-delim "" (concat (regexp-quote ebut:start) sep0)))
288 (case-fold-search))
289 (while (< pos len)
290 (setq c (aref lbl-key pos)
291 regexp (concat regexp
292 (if (= c ?_)
293 (if (or (= (1+ pos) len)
294 (/= (aref lbl-key (1+ pos)) ?_))
295 sep
296 (setq pos (1+ pos))
297 "_")
298 (regexp-quote (char-to-string c))))
299 pos (1+ pos)))
300 (if no-delim regexp
301 (setq regexp (concat regexp sep0 (regexp-quote ebut:end)))))))
302
303 (defun ebut:label-to-key (label)
304 "Normalizes LABEL for use as a Hyperbole button key and returns key.
305 Eliminates any fill prefix in the middle of the label, replaces '_' with
306 '__', removes leading and trailing whitespace and replaces each other
307 whitespace sequence with '_'."
308 (if (null label)
309 nil
310 (setq label (hbut:fill-prefix-remove label)
311 ;; Remove leading and trailing space.
312 label (hypb:replace-match-string "\\`[ \t\n\^M]+\\|[ \t\n\^M]+\\'"
313 label "" t)
314 label (hypb:replace-match-string "_" label "__" t))
315 (hypb:replace-match-string "[ \t\n\^M]+" label "_" t)))
316
317 (defun ebut:list (&optional file loc-p)
318 "Returns list of button labels from given FILE or current buffer.
319 Removes duplicate labels if optional LOC-P is omitted. With LOC-P, returns
320 list of elements (label start end) where start and end are the buffer
321 positions at which the starting button delimiter begins and ends."
322 (interactive)
323 (setq file (if file (and (file-exists-p file) (find-file-noselect file))
324 (current-buffer)))
325 (if file
326 (progn
327 (set-buffer file)
328 (let ((buts (ebut:map (if loc-p
329 (function
330 (lambda (lbl start end)
331 ;; Normalize label spacing
332 (list (ebut:key-to-label
333 (ebut:label-to-key lbl))
334 start end)))
335 (function
336 (lambda (lbl start end)
337 ;; Normalize label spacing
338 (ebut:key-to-label
339 (ebut:label-to-key lbl))))))))
340 (if loc-p buts (nreverse (set:create buts)))))))
341
342 (fset 'map-ebut 'ebut:map)
343 (defun ebut:map (but-func &optional start-delim end-delim
344 regexp-match include-delims)
345 "Applies BUT-FUNC to buttons delimited by optional START-DELIM and END-DELIM.
346 If REGEXP-MATCH is non-nil, only buttons which match this argument are
347 considered.
348 Maps over portion of buffer visible under any current restriction.
349 BUT-FUNC must take precisely three arguments: the button label, the
350 start position of the delimited button label and its end position (positions
351 include delimiters when INCLUDE-DELIMS is non-nil).
352 If END-DELIM is a symbol, e.g. t, then START-DELIM is taken as a regular
353 expression which matches an entire button string."
354 (or start-delim (setq start-delim ebut:start))
355 (or end-delim (setq end-delim ebut:end))
356 (let* ((regexp (symbolp end-delim))
357 (end-sym (or regexp (substring end-delim -1)))
358 (rtn)
359 (quoted)
360 start end but lbl)
361 (save-excursion
362 (goto-char (point-min))
363 (setq include-delims (if include-delims 0 1))
364 (while (re-search-forward
365 (if regexp start-delim
366 (concat (regexp-quote start-delim)
367 "\\([^" end-sym "\"][^" end-sym "]*\\)"
368 (regexp-quote end-delim)))
369 nil t)
370 (setq start (match-beginning include-delims)
371 end (match-end include-delims)
372 but (buffer-substring (match-beginning 0) (match-end 0))
373 lbl (buffer-substring (match-beginning 1) (match-end 1)))
374 (save-excursion
375 (goto-char start)
376 (if (or (= (preceding-char) ?\\) (= (preceding-char) ?\{))
377 ;; Ignore matches with quoted delimiters.
378 (setq quoted t)))
379 (cond (quoted (setq quoted nil))
380 ((or (not regexp-match)
381 (string-match regexp-match but))
382 (setq rtn (cons (funcall but-func lbl start end) rtn))))))
383 (nreverse rtn)))
384
385 (defun ebut:modify (&optional lbl-key but-sym)
386 "Modifies existing Hyperbole button from optional LBL-KEY and BUT-SYM.
387 Defaults are the key for any button label at point and 'hbut:current'.
388 If successful, returns button's instance number except when instance
389 number is 1, then returns t. On failure, as when button does not exist,
390 returns nil.
391
392 If successful, leaves point in button data buffer, so caller should use
393 'save-excursion'. Does not save button data buffer."
394 (save-excursion
395 (let ((lbl-instance (hbdata:write lbl-key but-sym)))
396 (run-hooks 'ebut:modify-hook)
397 lbl-instance)))
398
399 (defun ebut:next-occurrence (lbl-key &optional buffer)
400 "Moves point to next occurrence of button with LBL-KEY in optional BUFFER.
401 BUFFER defaults to current buffer. It may be a buffer name.
402 Returns non-nil iff occurrence is found.
403
404 Remember to use (goto-char (point-min)) before calling this in order to
405 move to the first occurrence of the button."
406 (if buffer
407 (if (not (or (bufferp buffer)
408 (and (stringp buffer) (get-buffer buffer))))
409 (error "(ebut:next-occurrence): Invalid buffer arg: %s" buffer)
410 (switch-to-buffer buffer)))
411 (if (re-search-forward (ebut:label-regexp lbl-key) nil t)
412 (goto-char (+ (match-beginning 0) (length ebut:start)))))
413
414 (defun ebut:operate (curr-label new-label)
415 "Operates on a new or existing Hyperbole button given by CURR-LABEL.
416 When NEW-LABEL is non-nil, this is substituted for CURR-LABEL and the
417 associated button is modified. Otherwise, a new button is created.
418 Returns instance string appended to label to form unique label, nil if
419 label is already unique. Signals an error when no such button is found
420 in the current buffer."
421 (let* ((lbl-key (ebut:label-to-key curr-label))
422 (lbl-regexp (ebut:label-regexp lbl-key))
423 (modify new-label)
424 (instance-flag))
425 (or new-label (setq new-label curr-label))
426 (hattr:set 'hbut:current 'lbl-key (ebut:label-to-key new-label))
427 (save-excursion
428 (if (setq instance-flag
429 (if modify (ebut:modify lbl-key) (ebut:create)))
430 (if (hmail:editor-p) (hmail:msg-narrow))))
431 (if instance-flag
432 (progn
433 ;; Rename all occurrences of button - those with same label.
434 (if modify
435 (let* ((but-key-and-pos (ebut:label-p nil nil nil 'pos))
436 (at-but (equal (car but-key-and-pos)
437 (ebut:label-to-key new-label))))
438 (if at-but
439 (ebut:delimit (nth 1 but-key-and-pos)
440 (nth 2 but-key-and-pos)
441 instance-flag))
442 (cond ((ebut:map
443 (function
444 (lambda (lbl start end)
445 (delete-region start end)
446 (ebut:delimit
447 (point)
448 (progn (insert new-label) (point))
449 instance-flag)))
450 nil nil lbl-regexp 'include-delims))
451 (at-but)
452 ((hypb:error "(ebut:operate): No button matching: %s" curr-label))))
453 ;; Add a new button.
454 (let (start end buf-lbl)
455 (cond ((and (marker-position (hypb:mark-marker t))
456 (setq start (region-beginning)
457 end (region-end)
458 buf-lbl (buffer-substring start end))
459 (equal buf-lbl curr-label))
460 nil)
461 ((looking-at (regexp-quote curr-label))
462 (setq start (point)
463 end (match-end 0)))
464 (t (setq start (point))
465 (insert curr-label)
466 (setq end (point))))
467 (ebut:delimit start end instance-flag))
468 )
469 ;; Position point
470 (let ((new-key (ebut:label-to-key new-label)))
471 (cond ((equal (ebut:label-p) new-key)
472 (forward-char 1) (search-backward ebut:start nil t)
473 (goto-char (match-end 0)))
474 ((let ((regexp (ebut:label-regexp new-key)))
475 (or (re-search-forward regexp nil t)
476 (re-search-backward regexp nil t)))
477 (goto-char (+ (match-beginning 0) (length ebut:start))))))
478 ;; instance-flag might be 't which we don't want to return.
479 (if (stringp instance-flag) instance-flag))
480 (hypb:error
481 "(ebut:operate): Operation failed. Check button attribute permissions: %s"
482 hattr:filename))))
483
484 (defun ebut:search (string out-buf &optional match-part)
485 "Writes explicit button lines matching STRING to OUT-BUF.
486 Uses Hyperbole space into which user has written buttons for the search.
487 By default, only matches for whole button labels are found, optional MATCH-PART
488 enables partial matches."
489 (let* ((buffers (mapcar (function
490 (lambda (dir)
491 (expand-file-name hattr:filename dir)))
492 (hbmap:dir-list)))
493 (total 0)
494 (firstmatch))
495 (save-excursion
496 (set-buffer out-buf)
497 (setq buffer-read-only nil)
498 (widen)
499 (erase-buffer)
500 (let (currbuf currfile kill-buf src-matches dir)
501 (while buffers
502 (setq currbuf (car buffers)
503 currfile (if (stringp currbuf) currbuf)
504 kill-buf (and currfile (not (get-file-buffer currfile)))
505 buffers (cdr buffers))
506 (if currfile
507 (setq currbuf (and (file-readable-p currfile)
508 (find-file-noselect currfile))
509 dir (file-name-directory currfile))
510 (setq currfile (buffer-file-name currbuf)))
511 (and currfile currbuf
512 (unwind-protect
513 (setq src-matches
514 (hbdata:search currbuf string match-part))
515 (if kill-buf (kill-buffer currbuf))))
516 (if src-matches
517 (let (elt matches)
518 (while src-matches
519 (setq elt (car src-matches))
520 (if (null elt) nil
521 (setq src-matches (cdr src-matches)
522 currfile (expand-file-name (car elt) dir)
523 matches (cdr elt)
524 currbuf (get-file-buffer currfile)
525 kill-buf (not currbuf)
526 currbuf (or currbuf
527 (and (file-readable-p currfile)
528 (find-file-noselect currfile))))
529 (if (null currbuf)
530 (progn (set-buffer out-buf)
531 (insert "ERROR: (ebut:search): \"" currfile
532 "\" is not readable.\n\n"))
533 (set-buffer currbuf)
534 (unwind-protect
535 (save-excursion
536 (widen) (goto-char 1)
537 (let ((case-fold-search t)
538 (regexp
539 (ebut:match-regexp matches match-part)))
540 (setq firstmatch t)
541 (while (re-search-forward regexp nil t)
542 (setq total (1+ total))
543 (let* ((linenum (count-lines (point-min)
544 (point)))
545 (tag (format "\n%4d:" linenum))
546 lns start end)
547 (setq end (progn (end-of-line) (point))
548 start (progn
549 (goto-char (match-beginning 0))
550 (beginning-of-line) (point))
551 lns (buffer-substring start end))
552 (goto-char end)
553 (save-excursion
554 (set-buffer out-buf)
555 (if firstmatch
556 (progn
557 (insert hbut:source-prefix "\""
558 currfile "\"\n")
559 (setq firstmatch nil)))
560 (insert tag lns))))
561 (set-buffer out-buf)
562 (if (not firstmatch) (insert "\n\n"))))
563 (if kill-buf (kill-buffer currbuf)))))))))))
564 total))
565
566 ;;; ------------------------------------------------------------------------
567 (defun ebut:delimit (start end instance-str)
568 "Delimits button label spanning region START to END in current buffer.
569 If button is already delimited or delimit fails, returns nil, else t.
570 Inserts INSTANCE-STR after END, before ending delimiter."
571 (goto-char start)
572 (if (looking-at (regexp-quote ebut:start))
573 (forward-char (length ebut:start)))
574 (if (ebut:label-p)
575 nil
576 (if (not (stringp instance-str)) (setq instance-str ""))
577 (insert ebut:start)
578 (goto-char (setq end (+ end (length ebut:start))))
579 (insert instance-str ebut:end)
580 (setq end (+ end (length instance-str) (length ebut:end)))
581 (and (fboundp 'hproperty:but-add) (hproperty:but-add start end hproperty:but))
582 (hbut:comment start end)
583 (goto-char end)
584 t))
585
586 (defun ebut:match-regexp (match-keys match-part)
587 "Returns regexp to match to all explicit button keys from MATCH-KEYS."
588 (setq match-part (if match-part
589 (concat "[^" (substring ebut:end -1) "]*")
590 "[ \t\n]*"))
591 (concat
592 (regexp-quote ebut:start) match-part
593 "\\(" (mapconcat (function
594 (lambda (key) (ebut:label-regexp key 'no-delim)))
595 match-keys "\\|")
596 "\\)" match-part (regexp-quote ebut:end)))
597
598 (defconst ebut:start "<("
599 "String matching the start of a hyper-button.")
600 (defconst ebut:end ")>"
601 "String matching the end of a hyper-button.")
602 (defconst ebut:instance-sep ":"
603 "String of one character, separates an ebut label from its instance num.")
604
605 ;;; ========================================================================
606 ;;; gbut class - Global Hyperbole buttons - activated by typing label name
607 ;;; ========================================================================
608
609 (defvar gbut:file (expand-file-name hbmap:filename hbmap:dir-user)
610 "File that stores Hyperbole buttons accessible by name, global buttons.")
611
612 (defun gbut:act (label)
613 "Activates Hyperbole global button with LABEL."
614 (interactive (list (hargs:read-match "Activate global button labeled: "
615 (mapcar 'list (gbut:lbl-list))
616 nil t nil 'ebut)))
617 (let* ((lbl-key (hbut:label-to-key label))
618 (but (ebut:get lbl-key nil gbut:file)))
619 (if but
620 (hbut:act but)
621 (error "(gbut:act): No global button labeled: %s" label))))
622
623 (defun gbut:help (label)
624 "Displays help for Hyperbole global button with LABEL."
625 (interactive (list (hargs:read-match "Report on global button labeled: "
626 (mapcar 'list (gbut:lbl-list))
627 nil t nil 'ebut)))
628 (let* ((lbl-key (hbut:label-to-key label))
629 (but (ebut:get lbl-key nil gbut:file)))
630 (if but
631 (hbut:report but)
632 (error "(gbut:help): No global button labeled: %s" label))))
633
634 ;;; ------------------------------------------------------------------------
635 (defun gbut:key-list ()
636 "Returns list of global button label keys."
637 (save-excursion
638 (if (hbdata:to-entry-buf gbut:file)
639 (let ((gbuts))
640 (save-restriction
641 (narrow-to-region (point) (if (search-forward "\^L" nil t)
642 (point) (point-max)))
643 (goto-char (point-min))
644 (condition-case ()
645 (while (setq gbuts (cons (car (read (current-buffer))) gbuts)))
646 (error nil))
647 gbuts)))))
648
649 (defun gbut:lbl-list ()
650 "Returns list of global button labels."
651 (mapcar 'hbut:key-to-label (gbut:key-list)))
652
653 ;;; ========================================================================
654 ;;; hattr class
655 ;;; ========================================================================
656
657 (defun hattr:attributes (obj-symbol)
658 "Returns a list of OBJ-SYMBOL's attributes as symbols."
659 (if (symbolp obj-symbol)
660 (let* ((attr-val-list (symbol-plist obj-symbol))
661 (i -1))
662 (delq nil (mapcar (function
663 (lambda (elt)
664 (setq i (1+ i))
665 (and (= (% i 2) 0) elt)))
666 attr-val-list)))))
667
668 (defun hattr:clear (hbut)
669 "Removes all of HBUT's attributes except `variable-documentation'."
670 (let (sublist)
671 (or (symbolp hbut)
672 (error "(hattr:clear): Argument not a Hyperbole button: %s" hbut))
673 (if (setq sublist (memq 'variable-documentation (symbol-plist hbut)))
674 (progn
675 (setcdr (cdr sublist) nil)
676 (setplist hbut sublist))
677 (setplist hbut nil)
678 )))
679
680 (defun hattr:copy (from-hbut to-hbut)
681 "Copies attributes FROM-HBUT TO-HBUT, eliminating attributes TO-HBUT had.
682 Returns TO-HBUT."
683 (mapcar
684 (function
685 (lambda (hbut)
686 (or (and hbut (symbolp hbut))
687 (error "(hattr:clear): Argument not a Hyperbole button: %s" hbut))))
688 (list from-hbut to-hbut))
689 (unwind-protect
690 nil
691 (hattr:clear to-hbut)
692 (setplist to-hbut (copy-sequence (symbol-plist from-hbut))))
693 to-hbut)
694
695 (defun hattr:get (obj-symbol attr-symbol)
696 "Returns value of OBJ-SYMBOL's attribute ATTR-SYMBOL."
697 (get obj-symbol attr-symbol))
698
699 (defun hattr:list (obj-symbol)
700 "Returns a property list of OBJ-SYMBOL's attributes.
701 Each pair of elements is: <attrib-name> <attrib-value>."
702 (if (symbolp obj-symbol)
703 (symbol-plist obj-symbol)
704 (error "(hattr:list): Argument not a symbol: %s" obj-symbol)))
705
706 (defun hattr:memq (attr-symbol obj-symbol)
707 "Returns t if ATTR-SYMBOL is in OBJ-SYMBOL's attribute list, else nil."
708 (and (symbolp obj-symbol) (symbolp attr-symbol)
709 (let* ((attr-val-list (symbol-plist obj-symbol))
710 (attr-list (let ((i -1))
711 (delq nil (mapcar
712 (function
713 (lambda (elt)
714 (setq i (1+ i))
715 (and (= (% i 2) 0) elt)))
716 attr-val-list)))))
717 (if (memq attr-symbol attr-list) t))))
718
719 (defun hattr:report (attrib-list)
720 "Pretty prints to standard-output attribute-value pairs from ATTRIB-LIST.
721 Ignores nil valued attributes. Returns t unless no attributes are printed."
722 (let ((has-attr) attr val len)
723 (if (or (null attrib-list) (not (listp attrib-list))
724 ;; odd number of elements?
725 (= (% (length attrib-list) 2) 1))
726 nil
727 (while (setq attr (car attrib-list))
728 (setq val (car (setq attrib-list (cdr attrib-list)))
729 attrib-list (cdr attrib-list))
730 (if val
731 (progn
732 (setq has-attr t
733 attr (symbol-name attr)
734 len (max (- 16 (length attr)) 1))
735 (princ " ") (princ attr) (princ ":")
736 (princ (make-string len ? ))
737 (let (str)
738 (prin1 (cond ((string-match "time" attr)
739 (htz:date-unix val
740 (and (>= (aref val 0) ?0)
741 (<= (aref val 0) ?9)
742 "GMT") htz:local))
743 ((and (setq str (if (stringp val) val
744 (prin1-to-string val)))
745 (string-match "\\`actypes::" str))
746 (intern (substring str (match-end 0))))
747 (t val))))
748 (terpri))))
749 has-attr)))
750
751 (defun hattr:save ()
752 "Saves button attribute file for current directory, if modified.
753 Suitable for use as part of 'write-file-hooks'."
754 (let* ((bd-file (expand-file-name hattr:filename default-directory))
755 (buf (and (stringp default-directory)
756 (get-file-buffer bd-file))))
757 (if (and ebut:hattr-save buf (not (eq buf (current-buffer))))
758 (let ((ebut:hattr-save));; Prevents 'write-file-hooks' looping.
759 (and (buffer-modified-p buf)
760 (save-excursion
761 (set-buffer buf) (save-buffer)
762 ;; Unlock button attribute file; kill buffer so user is
763 ;; never holding a buffer which is out of sync with file,
764 ;; due to some other user's edits.
765 ;; Maybe this should be user or site configurable.
766 (or (buffer-modified-p buf) (kill-buffer buf))
767 )))))
768 ;; Must return nil, so can be used as part of write-file-hooks.
769 nil)
770
771 (defun hattr:set (obj-symbol attr-symbol attr-value)
772 "Sets OBJ-SYMBOL's attribute ATTR-SYMBOL to ATTR-VALUE."
773 (put obj-symbol attr-symbol attr-value))
774
775 (fset 'hattr:summarize 'hattr:report)
776
777 (defvar hattr:filename
778 (if (memq system-type '(ms-windows windows-nt ms-dos)) "_hypb" ".hypb")
779 "Per directory file name in which explicit button attributes are stored.
780 If you change its value, you will be unable to use buttons created by
781 others who use a different value!")
782
783 ;;; ========================================================================
784 ;;; hbut class - abstract
785 ;;; ========================================================================
786
787 (defun hbut:act (hbut)
788 "Performs action for explicit or implicit Hyperbole button symbol HBUT."
789 (and hbut (apply 'actype:act (hattr:get hbut 'actype)
790 (hattr:get hbut 'args))))
791
792 (defun hbut:action (hbut)
793 "Returns appropriate action for Hyperbole button symbol HBUT."
794 (let ((categ (hattr:get hbut 'categ)) (atype) (action))
795 (if (eq categ 'explicit)
796 (progn (setq action (hattr:get hbut 'action)
797 atype (hattr:get hbut 'actype))
798 (if (= (length (symbol-name atype)) 2)
799 atype
800 (or action (actype:action atype))))
801 ;; Must be an implicit button.
802 (if (fboundp atype) atype))))
803
804 (defun hbut:at-p ()
805 "Returns symbol for explicit or implicit Hyperbole button at point or nil."
806 (or (ebut:at-p) (ibut:at-p)))
807
808
809 (defun hbut:comment (start end)
810 "Comment button label spanning region START to END in current buffer.
811 Use buffer commenting grammar, if any, otherwise don't comment."
812 (save-excursion
813 (if comment-start
814 (if (or (equal comment-end "")
815 (null comment-end))
816 (progn
817 (beginning-of-line)
818 (if (search-forward comment-start start t)
819 nil
820 (goto-char start)
821 (insert comment-start)
822 (if (/= (preceding-char) ? )
823 (insert ? ))))
824 ;; Comments have both start and end delimiters
825 (if (and (re-search-backward
826 (concat (regexp-quote comment-start) "\\|"
827 (regexp-quote comment-end))
828 nil t)
829 (looking-at (regexp-quote comment-start)))
830 nil
831 (goto-char start)
832 (insert comment-start)
833 (if (/= (preceding-char) ? )
834 (insert ? ))
835 (goto-char (+ (point) (- end start)))
836 (if (/= (following-char) ? )
837 (insert ? ))
838 (insert comment-end)
839 )))))
840
841 ;;; Regexps derived in part from "filladapt.el" under the GPL, Copyright
842 ;;; 1989 Kyle E. Jones.
843 (defvar hbut:fill-prefix-regexps
844 '(
845 ;; Included text in news or mail messages
846 "\n[ \t]*\\([:|<>]+ *\\)+"
847 ;; Included text generated by SUPERCITE. We can't hope to match all
848 ;; the possible variations.
849 "\n[ \t]*[^'`\"< \t]*> *"
850 ;; Lisp comments
851 "\n[ \t]*\\(;+[ \t]*\\)+"
852 ;; UNIX shell comments
853 "\n[ \t]*\\(#+[ \t]*\\)+"
854 ;; C++ comments
855 "\n[ \t]*//[/ \t]+"
856 ;; C or Pascal comments, one open and close per line, so match close
857 ;; then open.
858 "\\*+[/\)][ \t]*\n+[ \t]*[/\(]\\*+"
859 "}[ \t]*\n+[ \t]*{"
860 ;; Eiffel or Sather comments
861 "\n[ \t]*--[ \t]+"
862 ;; Fortran comments
863 "\n[Cc][ \t]+"
864 ;; Postscript comments
865 "\n[ \t]*\\(%+[ \t]*\\)+"
866 )
867 "List of regexps of fill prefixes to remove from the middle of buttons.")
868
869 (defun hbut:fill-prefix-remove (label)
870 "Removes any recognized fill prefix from within LABEL.
871 'hbut:fill-prefix-regexps' is a list of fill prefixes to recognize."
872 (if (string-match "\n" label)
873 (mapcar
874 (function
875 (lambda (fill-prefix)
876 (and (string-match "\n" label)
877 (setq label
878 (hypb:replace-match-string fill-prefix label " " t)))))
879 hbut:fill-prefix-regexps))
880 label)
881
882 (defun hbut:is-p (object)
883 "Returns non-nil if object denotes a Hyperbole button."
884 (and (symbolp object) (hattr:get object 'categ)))
885
886 (fset 'hbut:key-src 'ebut:key-src)
887 (fset 'hbut:key-to-label 'ebut:key-to-label)
888
889 (defun hbut:label (hbut)
890 "Returns the label for Hyperbole button symbol HBUT."
891 (if (hbut:is-p hbut)
892 (hbut:key-to-label (hattr:get hbut 'lbl-key))
893 (error "(hbut:label): Argument is not a Hyperbole button symbol, '%s'"
894 hbut)))
895
896 (fset 'hbut:label-p 'ebut:label-p)
897 (fset 'hbut:label-to-key 'ebut:label-to-key)
898
899 (defun hbut:report (&optional arg)
900 "Pretty prints the attributes of a button or buttons.
901
902 Takes an optional ARG interpreted as follows:
903 a button symbol - report on that button;
904 nil - report on button at point, if any;
905 integer > 0 - report on all explicit buttons in buffer, alphabetize;
906 integer < 1 - report on all explicit buttons in occurrence order;
907
908 Returns number of buttons reported on or nil if none."
909 (setq arg (cond ((or (integerp arg) (symbolp arg)) arg)
910 ((listp arg)
911 (if (integerp (setq arg (car arg))) arg 1))
912 (t 1)))
913 (let* ((but (if (and arg (symbolp arg)) arg (hbut:at-p)))
914 (curr-key (and but (hattr:get but 'lbl-key)))
915 (key-src (or (and but (hattr:get but 'loc)) (hbut:key-src)))
916 (lbl-lst (cond ((not arg)
917 (if curr-key (list (ebut:key-to-label curr-key))))
918 ((symbolp arg) (if curr-key
919 (list (hbut:key-to-label
920 (hattr:get arg 'lbl-key)))))
921 ((< arg 1) (ebut:list))
922 (t (sort (ebut:list)
923 (function
924 (lambda (s1 s2)
925 (string< (downcase s1) (downcase s2))))))))
926 (key-buf (current-buffer))
927 (buf-name (hypb:help-buf-name))
928 (attribs))
929 (if lbl-lst
930 (progn
931 (with-output-to-temp-buffer buf-name
932 (princ hbut:source-prefix)
933 (prin1 key-src)
934 (terpri)
935 (terpri)
936 (mapcar
937 (function
938 (lambda (lbl)
939 (if (setq but
940 (cond ((or (null arg) (symbolp arg)) but)
941 (t (ebut:get (ebut:label-to-key lbl) key-buf)))
942 attribs (hattr:list but))
943 (progn
944 (princ (if (ibut:is-p but)
945 lbl
946 (concat ebut:start lbl ebut:end)))
947 (terpri)
948 (let ((doc (actype:doc but (= 1 (length lbl-lst)))))
949 (if doc
950 (progn
951 (princ " ")
952 (princ doc)
953 (terpri))))
954 (hattr:report
955 ;; (if (eq (car (cdr (memq 'categ attribs))) 'explicit)
956 ;; (memq 'action attribs)
957 ;; (memq 'categ attribs))
958 attribs)
959 (terpri))
960 )))
961 lbl-lst))
962 (length lbl-lst)))))
963
964 (defun hbut:source (&optional full)
965 "Returns Hyperbole source buffer or file given at point.
966 If a file, always returns a full path if optional FULL is non-nil."
967 (goto-char (match-end 0))
968 (cond ((looking-at "#<buffer \\([^ \n]+\\)>")
969 (get-buffer (buffer-substring (match-beginning 1)
970 (match-end 1))))
971 ((looking-at "\".+\"")
972 (let* ((file (buffer-substring (1+ (match-beginning 0))
973 (1- (match-end 0))))
974 (absolute (file-name-absolute-p file)))
975 (if (and full (not absolute))
976 (expand-file-name file default-directory)
977 file)))))
978
979 (fset 'hbut:summarize 'hbut:report)
980
981 (defvar hbut:current nil
982 "Currently selected Hyperbole button.
983 Available to action routines.")
984
985 (defconst hbut:source-prefix moccur-source-prefix
986 "String found at start of a buffer containing only a hyper-button menu.
987 This expression should be followed immediately by a file-name indicating the
988 source file for the buttons in the menu, if any.")
989
990 ;;; ========================================================================
991 ;;; htype class - Hyperbole Types, e.g. action and implicit button types
992 ;;; ========================================================================
993
994 (require 'set)
995
996 (defun htype:body (htype-sym)
997 "Return body for HTYPE-SYM. If HTYPE-SYM is nil, return nil."
998 (and htype-sym (hypb:indirect-function htype-sym)))
999
1000 (defun htype:category (type-category)
1001 "Return list of symbols in Hyperbole TYPE-CATEGORY in priority order.
1002 Symbols contain category component.
1003 TYPE-CATEGORY should be 'actypes, 'ibtypes or nil for all."
1004 (let ((types (symset:get type-category 'symbols))
1005 (categ-name (symbol-name type-category)))
1006 (mapcar (function
1007 (lambda (type)
1008 (intern (concat categ-name "::" (symbol-name type)))))
1009 types)))
1010
1011 ;; Thanks to JWZ for help on this.
1012 (defmacro htype:create (type type-category doc params body property-list)
1013 "Create a new Hyperbole TYPE within TYPE-CATEGORY (both unquoted symbols).
1014 Third arg DOC is a string describing the type.
1015 Fourth arg PARAMS is a list of parameters to send to the fifth arg BODY,
1016 which is a list of forms executed when the type is evaluated.
1017 Sixth arg PROPERTY-LIST is attached to the new type's symbol.
1018
1019 This symbol is returned."
1020 (let* ((sym (htype:symbol type type-category))
1021 (action (nconc (list 'defun sym params doc) body)))
1022 (` (progn
1023 (, action)
1024 (setplist '(, sym) (, property-list))
1025 (symset:add '(, type) '(, type-category) 'symbols)
1026 (run-hooks 'htype:create-hook)
1027 '(, sym)))))
1028
1029 (defun htype:delete (type type-category)
1030 "Delete a Hyperbole TYPE derived from TYPE-CATEGORY (both symbols).
1031 Return the Hyperbole symbol for the TYPE if it existed, else nil."
1032 (let* ((sym (htype:symbol type type-category))
1033 (exists (fboundp 'sym)))
1034 (setplist sym nil)
1035 (symset:delete type type-category 'symbols)
1036 (fmakunbound sym)
1037 (run-hooks 'htype:delete-hook)
1038 (and exists sym)))
1039
1040 (defun htype:doc (type)
1041 "Return documentation for Hyperbole TYPE, a symbol."
1042 (documentation type))
1043
1044 (defun htype:names (type-category &optional sym)
1045 "Return list of current names for Hyperbole TYPE-CATEGORY in priority order.
1046 Names do not contain category component.
1047 TYPE-CATEGORY should be 'actypes, 'ibtypes or nil for all.
1048 When optional SYM is given, return the name for that symbol only, if any."
1049 (let ((types (symset:get type-category 'symbols))
1050 (sym-name (and sym (symbol-name sym))))
1051 (if sym-name
1052 ;; Strip category from sym-name before looking for a match.
1053 (progn (if (string-match "::" sym-name)
1054 (setq sym (intern (substring sym-name (match-end 0)))))
1055 (if (memq sym types) (symbol-name sym)))
1056 (mapcar 'symbol-name types))))
1057
1058 ;;; ------------------------------------------------------------------------
1059
1060 (defun htype:symbol (type type-category)
1061 "Return Hyperbole type symbol composed from TYPE and TYPE-CATEGORY (both symbols)."
1062 (intern (concat (symbol-name type-category) "::"
1063 (symbol-name type))))
1064
1065 ;;; ========================================================================
1066 ;;; ibut class - Implicit Hyperbole Buttons
1067 ;;; ========================================================================
1068
1069 (defun ibut:at-p (&optional key-only)
1070 "Returns symbol for implicit button at point, else nil.
1071 With optional KEY-ONLY, returns only the label key for button."
1072 (let ((types (htype:category 'ibtypes))
1073 ;; Global var used in (hact) function, don't delete.
1074 (hrule:action 'actype:identity)
1075 (itype)
1076 (args)
1077 (is-type))
1078 (or key-only (hattr:clear 'hbut:current))
1079 (while (and (not is-type) types)
1080 (setq itype (car types))
1081 (if (setq args (funcall itype))
1082 (setq is-type itype)
1083 (setq types (cdr types))))
1084 (if is-type
1085 (if key-only
1086 (hattr:get 'hbut:current 'lbl-key)
1087 (hattr:set 'hbut:current 'loc (save-excursion
1088 (hbut:key-src 'full)))
1089 (hattr:set 'hbut:current 'categ is-type)
1090 (or (hattr:get 'hbut:current 'args)
1091 (not (listp args))
1092 (progn
1093 (hattr:set 'hbut:current 'actype
1094 (or
1095 ;; Hyperbole action type
1096 (intern-soft (concat "actypes::"
1097 (symbol-name (car args))))
1098 ;; Regular Emacs Lisp function symbol
1099 (car args)
1100 ))
1101 (hattr:set 'hbut:current 'args (cdr args))))
1102 'hbut:current))))
1103
1104 (defun ibut:is-p (object)
1105 "Returns non-nil if object denotes an implicit Hyperbole button."
1106 (if (symbolp object)
1107 (let ((categ (hattr:get object 'categ)))
1108 (and categ (string-match "^ibtypes::" (symbol-name categ))))))
1109
1110 (defun ibut:label-p ()
1111 "Returns key for Hyperbole implicit button label that point is on or nil."
1112 (ibut:at-p 'key-only))
1113
1114 (defun ibut:label-set (label &optional start end)
1115 "Sets current implicit button attributes from LABEL and START, END position.
1116 START and END are optional. When given, they specify the region in the buffer
1117 to flash when this implicit button is activated or queried for its attributes.
1118 If LABEL is a list, it is assumed to contain all arguments."
1119 (cond ((stringp label)
1120 (hattr:set 'hbut:current 'lbl-key (hbut:label-to-key label))
1121 (and start (hattr:set 'hbut:current 'lbl-start start))
1122 (and end (hattr:set 'hbut:current 'lbl-end end)))
1123 ((and label (listp label))
1124 (hattr:set 'hbut:current 'lbl-key (hbut:label-to-key (car label)))
1125 (hattr:set 'hbut:current 'lbl-start (nth 1 label))
1126 (hattr:set 'hbut:current 'lbl-end (nth 2 label)))
1127 (t (error "(ibut:label-set): Invalid label arg: '%s'" label)))
1128 t)
1129
1130 ;;; ========================================================================
1131 ;;; ibtype class - Implicit button types
1132 ;;; ========================================================================
1133
1134 (fset 'defib 'ibtype:create)
1135 (put 'ibtype:create 'lisp-indent-function 'defun)
1136 (defmacro ibtype:create (type params doc at-p &optional to-p style)
1137 "Creates implicit button TYPE (unquoted sym) with PARAMS, described by DOC.
1138 PARAMS are presently ignored.
1139
1140 AT-P is a boolean form of no arguments which determines whether or not point
1141 is within a button of this type.
1142 Optional TO-P is a boolean form which moves point immediately after the next
1143 button of this type within the current buffer and returns a list of (button-
1144 label start-pos end-pos), or nil when none is found.
1145 Optional STYLE is a display style specification to use when highlighting
1146 buttons of this type; most useful when TO-P is also given.
1147
1148 Returns symbol created when successful, else nil. Nil indicates that action
1149 type for ibtype is presently undefined."
1150 (if type
1151 (let ((to-func (if to-p (action:create nil (list to-p))))
1152 (at-func (list at-p)))
1153 (` (htype:create (, type) ibtypes (, doc) nil (, at-func)
1154 (list 'to-p (, to-func) 'style (, style)))))))
1155
1156 (defun ibtype:delete (type)
1157 "Deletes an implicit button TYPE (a symbol).
1158 Returns TYPE's symbol if it existed, else nil."
1159 (htype:delete type 'ibtypes))
1160
1161 ;;; ========================================================================
1162 ;;; symset class - Hyperbole internal symbol set maintenance
1163 ;;; ========================================================================
1164
1165 (require 'set)
1166
1167 (defun symset:add (elt symbol prop)
1168 "Adds ELT to SYMBOL's PROP set.
1169 Returns nil iff ELT is already in SET. Uses 'eq' for comparison."
1170 (let* ((set (get symbol prop))
1171 (set:equal-op 'eq)
1172 (new-set (set:add elt set)))
1173 (and new-set (put symbol prop new-set))))
1174
1175 (fset 'symset:delete 'symset:remove)
1176
1177 (defun symset:get (symbol prop)
1178 "Returns SYMBOL's PROP set."
1179 (get symbol prop))
1180
1181 (defun symset:remove (elt symbol prop)
1182 "Removes ELT from SYMBOL's PROP set and returns the new set.
1183 Assumes PROP is a valid set. Uses 'eq' for comparison."
1184 (let ((set (get symbol prop))
1185 (set:equal-op 'eq))
1186 (put symbol prop (set:remove elt set))))
1187
1188
1189 (provide 'hbut)