;;; 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