1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
|
;;; hact.el --- GNU Hyperbole button action handling
;;
;; Author: Bob Weiner
;;
;; Orig-Date: 18-Sep-91 at 02:57:09
;;
;; Copyright (C) 1991-2016 Free Software Foundation, Inc.
;; See the "HY-COPY" file for license information.
;;
;; This file is part of GNU Hyperbole.
;;; Commentary:
;;; Code:
;;; ************************************************************************
;;; Other required Elisp libraries
;;; ************************************************************************
(eval-and-compile (mapc #'require '(hhist hpath set)))
;;; ************************************************************************
;;; Public variables
;;; ************************************************************************
(defvar hrule:action 'actype:act
"Value is a function of any number of arguments that executes actions.
Variable is used to vary actual effect of evaluating a Hyperbole action,
e.g. to inhibit actions.")
;;; ************************************************************************
;;; Public functions
;;; ************************************************************************
;;; ========================================================================
;;; symset class - Hyperbole internal symbol set maintenance
;;; ========================================================================
(defun symset:add (elt symbol prop)
"Adds ELT to SYMBOL's PROP set.
Returns nil iff ELT is already in SET. Uses `eq' for comparison."
(let* ((set (get symbol prop))
(set:equal-op 'eq)
(new-set (set:add elt set)))
(and new-set (put symbol prop new-set))))
(defalias 'symset:delete 'symset:remove)
(defun symset:get (symbol prop)
"Returns SYMBOL's PROP set."
(get symbol prop))
(defun symset:remove (elt symbol prop)
"Removes ELT from SYMBOL's PROP set and returns the new set.
Assumes PROP is a valid set. Uses `eq' for comparison."
(let ((set (get symbol prop))
(set:equal-op 'eq))
(put symbol prop (set:remove elt set))))
;;; ========================================================================
;;; htype class - Hyperbole Types, e.g. action and implicit button types
;;; ========================================================================
(defun htype:body (htype-sym)
"Returns body for HTYPE-SYM. If HTYPE-SYM is nil, returns nil."
(and htype-sym (hypb:indirect-function htype-sym)))
(defun htype:category (type-category)
"Returns list of symbols in Hyperbole TYPE-CATEGORY in priority order.
Symbols contain category component.
TYPE-CATEGORY should be 'actypes, 'ibtypes or nil for all."
(let ((types (symset:get type-category 'symbols))
(categ-name (symbol-name type-category)))
(mapcar (lambda (type)
(intern (concat categ-name "::" (symbol-name type))))
types)))
;; Thanks to JWZ for help on this.
(defmacro htype:create (type type-category doc params body property-list)
"Creates a new Hyperbole TYPE within TYPE-CATEGORY (both unquoted symbols).
Third arg DOC is a string describing the type.
Fourth arg PARAMS is a list of parameters to send to the fifth arg BODY,
which is a list of forms executed when the type is evaluated.
Sixth arg PROPERTY-LIST is attached to the new type's symbol.
Returns the new function symbol derived from TYPE."
(let* ((sym (htype:symbol type type-category))
(action (nconc (list 'defun sym params doc) body)))
`(progn
,action
(setplist ',sym ,property-list)
(symset:add ',type ',type-category 'symbols)
(run-hooks 'htype-create-hook)
',sym)))
(defun htype:delete (type type-category)
"Deletes a Hyperbole TYPE derived from TYPE-CATEGORY (both symbols).
Returns the Hyperbole symbol for the TYPE if it existed, else nil."
(let* ((sym (htype:symbol type type-category))
(exists (fboundp 'sym)))
(setplist sym nil)
(symset:delete type type-category 'symbols)
(fmakunbound sym)
(run-hooks 'htype-delete-hook)
(and exists sym)))
(defun htype:doc (type)
"Returns documentation for Hyperbole TYPE, a symbol."
(documentation type))
(defun htype:names (type-category &optional sym)
"Returns a list of the current names for Hyperbole TYPE-CATEGORY in priority order.
Names do not contain the category component.
TYPE-CATEGORY should be 'actypes, 'ibtypes or nil for all.
When optional SYM is given, returns the name for that symbol only, if any."
(let ((types (symset:get type-category 'symbols))
(sym-name (and sym (symbol-name sym))))
(if sym-name
;; Strip category from sym-name before looking for a match.
(progn (if (string-match "::" sym-name)
(setq sym (intern (substring sym-name (match-end 0)))))
(if (memq sym types) (symbol-name sym)))
(mapcar 'symbol-name types))))
;;; ------------------------------------------------------------------------
(defun htype:symbol (type type-category)
"Returns Hyperbole type symbol composed from TYPE and TYPE-CATEGORY (both symbols)."
(intern (concat (symbol-name type-category) "::"
(symbol-name type))))
;;; ========================================================================
;;; action class
;;; ========================================================================
(defun action:commandp (function)
"Returns interactive calling form if FUNCTION has one, else nil."
(let ((action
(cond ((null function) nil)
((symbolp function)
(and (fboundp function)
(hypb:indirect-function function)))
((and (listp function)
(eq (car function) 'autoload))
(error "(action:commandp): Autoload not supported: %s" function))
(t function))))
(cond ((and action (fboundp 'interactive-form))
(interactive-form action))
((hypb:emacs-byte-code-p action)
(cond ((fboundp 'compiled-function-interactive)
(compiled-function-interactive action))
((commandp action)
(list 'interactive (aref action 5)))))
(t (commandp action)))))
(defun action:create (param-list body)
"Creates Hyperbole action defined by PARAM-LIST and BODY, a list of Lisp forms."
(if (symbolp body)
body
(list 'function (cons 'lambda (cons param-list body)))))
(defun action:kbd-macro (macro &optional repeat-count)
"Returns Hyperbole action that executes a keyboard MACRO REPEAT-COUNT times."
(list 'execute-kbd-macro macro repeat-count))
;; This function is based on Emacs `help-function-arglist'.
(defun action:params-emacs (def)
"Return the argument list for the function DEF which may be a symbol or a function body."
;; Handle symbols aliased to other symbols.
(if (and (symbolp def) (fboundp def)) (setq def (indirect-function def)))
;; If definition is a macro, find the function inside it.
(if (eq (car-safe def) 'macro) (setq def (cdr def)))
(cond
((and (byte-code-function-p def) (listp (aref def 0))) (aref def 0))
((eq (car-safe def) 'lambda) (nth 1 def))
((eq (car-safe def) 'closure) (nth 2 def))
((or (and (byte-code-function-p def) (integerp (aref def 0)))
(subrp def))
(or (let* ((doc (condition-case nil (documentation def) (error nil)))
(docargs (if doc (car (help-split-fundoc doc nil))))
(arglist (if docargs
(cdar (read-from-string (downcase docargs)))))
(valid t))
;; Check validity.
(dolist (arg arglist)
(unless (and (symbolp arg)
(let ((name (symbol-name arg)))
(if (eq (aref name 0) ?&)
(memq arg '(&rest &optional))
(not (string-match "\\." name)))))
(setq valid nil)))
(when valid arglist))
(let* ((args-desc (if (not (subrp def))
(aref def 0)
(let ((a (subr-arity def)))
(logior (car a)
(if (numberp (cdr a))
(lsh (cdr a) 8)
(lsh 1 7))))))
(max (lsh args-desc -8))
(min (logand args-desc 127))
(rest (logand args-desc 128))
(arglist ()))
(dotimes (i min)
(push (intern (concat "arg" (number-to-string (1+ i)))) arglist))
(when (> max min)
(push '&optional arglist)
(dotimes (i (- max min))
(push (intern (concat "arg" (number-to-string (+ 1 i min))))
arglist)))
(unless (zerop rest) (push '&rest arglist) (push 'rest arglist))
(nreverse arglist))))
((and (autoloadp def) (not (eq (nth 4 def) 'keymap)))
;; Force autoload to get function signature.
(setq def (autoload-do-load def))
(unless (autoloadp def)
(action:params-emacs def)))))
(defun action:params (action)
"Returns unmodified ACTION parameter list.
Autoloads action function if need be to get the parameter list."
(when (and (symbolp action) (fboundp action))
(setq action (hypb:indirect-function action)))
(cond ((null action) nil)
((listp action)
(if (eq (car action) 'autoload)
(error "(action:params): Autoload not supported: %s" action)
(car (cdr action))))
((hypb:emacs-byte-code-p action)
(if (fboundp 'compiled-function-arglist)
(compiled-function-arglist action)
(action:params-emacs action)))
((symbolp action)
(car (cdr (and (fboundp action) (hypb:indirect-function action)))))))
(defun action:param-list (action)
"Returns list of actual ACTION parameters (removes `&' special forms)."
(delq nil (mapcar (lambda (param)
(if (eq (aref (symbol-name param) 0) ?&)
nil param))
(action:params action))))
(defun action:path-args-abs (args-list &optional default-dirs)
"Returns any paths in ARGS-LIST made absolute.
Uses optional DEFAULT-DIRS or `default-directory'.
Other arguments are returned unchanged."
(mapcar (lambda (arg) (hpath:absolute-to arg default-dirs))
args-list))
(defun action:path-args-rel (args-list)
"Returns any paths in ARGS-LIST below current directory made relative.
Other paths are simply expanded. Non-path arguments are returned unchanged."
(let ((dir (hattr:get 'hbut:current 'dir)))
(mapcar (lambda (arg) (hpath:relative-to arg dir))
args-list)))
;;; ========================================================================
;;; action type class, actype
;;; ========================================================================
(defmacro hact (&rest args)
"Performs action formed from rest of ARGS and returns the result or acts as a no-op when testing implicit button type contexts.
First arg may be a symbol or symbol name for either an action type or a
function. Runs `action-act-hook' before performing action.
The value of `hrule:action' determines what effect this has."
(eval `(cons 'funcall (cons 'hrule:action ',args))))
(defun actype:act (actype &rest args)
"Performs action formed from ACTYPE and rest of ARGS and returns value.
If value is nil, however, t is returned instead, to ensure that implicit button
types register the performance of the action. ACTYPE may be a symbol or symbol
name for either an action type or a function. Runs `action-act-hook' before
performing ACTION."
(let ((prefix-arg current-prefix-arg)
(action (actype:action actype))
(act '(apply action args)))
(if (null action)
(error "(actype:act): Null action for: `%s'" actype)
;; Next 2 lines are needed so that relative paths are expanded
;; properly. But in rare cases, this can improperly expand simple
;; string arguments like "tags" as a pathname, when it is not
;; being used as a path. So do this only if actype is a defact
;; and not a defun to limit any potential impact. RSW - 9/22/2017
(if (symbolp action) (string-match "\\`actypes::" (symbol-name action))
(setq args (action:path-args-abs args)))
(let ((hist-elt (hhist:element)))
(run-hooks 'action-act-hook)
(prog1 (or (cond ((or (symbolp action) (listp action)
(hypb:emacs-byte-code-p action))
(eval act))
((and (stringp action)
(let ((func (key-binding action)))
(if (not (integerp action))
(setq action func))))
(eval act))
(t (eval action)))
t)
(hhist:add hist-elt))
))))
(defun actype:action (actype)
"Returns action part of ACTYPE (a symbol or symbol name).
ACTYPE may be a Hyperbole actype or Emacs Lisp function."
(let (actname)
(if (stringp actype)
(setq actname actype
actype (intern actype))
(setq actname (symbol-name actype)))
(cond ((htype:body (if (string-match "\\`actypes::" actname)
actype
(intern-soft (concat "actypes::" actname)))))
((fboundp actype) actype)
)))
(defmacro actype:create (type params doc &rest default-action)
"Creates an action TYPE (an unquoted symbol) with PARAMS, described by DOC.
The type uses PARAMS to perform DEFAULT-ACTION (list of the rest of the
arguments). A call to this function is syntactically the same as for
`defun', but a doc string is required.
Returns symbol created when successful, else nil."
(list 'htype:create type 'actypes doc params default-action nil))
(defalias 'defact 'actype:create)
(put 'actype:create 'lisp-indent-function 'defun)
(defun actype:delete (type)
"Deletes an action TYPE (a symbol). Returns TYPE's symbol if it existed."
(htype:delete type 'actypes))
(defun actype:doc (hbut &optional full)
"Returns first line of act doc for HBUT (a Hyperbole button symbol).
With optional FULL, returns full documentation string.
Returns nil when no documentation."
(let* ((act (and (hbut:is-p hbut) (or (hattr:get hbut 'action)
(hattr:get hbut 'actype))))
(but-type (hattr:get hbut 'categ))
(sym-p (and act (symbolp act)))
(end-line) (doc))
(cond ((and but-type (fboundp but-type)
(setq doc (htype:doc but-type)))
;; Is an implicit button, so use its doc string if any.
)
(sym-p
(setq doc (htype:doc act))))
(if (null doc)
nil
(setq doc (substitute-command-keys doc))
(or full (setq end-line (string-match "[\n]" doc)
doc (substring doc 0 end-line))))
doc))
(defun actype:identity (&rest args)
"Returns list of ARGS unchanged or if no ARGS, returns t.
Used as the setting of `hrule:action' to inhibit action evaluation."
(or args t))
(defun actype:interact (actype)
"Interactively calls default action for ACTYPE.
ACTYPE is a symbol that was previously defined with `defact'.
Returns nil only when no action is found or the action has no interactive
calling form."
(let ((action (htype:body
(intern-soft (concat "actypes::" (symbol-name actype))))))
(and action (action:commandp action) (or (call-interactively action) t))))
(defun actype:params (actype)
"Returns list of ACTYPE's parameters, including keywords."
(action:params (actype:action actype)))
(defun actype:param-list (actype)
"Returns list of ACTYPE's parameters without keywords."
(action:param-list (actype:action actype)))
(provide 'hact)
;;; hact.el ends here
|