Mercurial > hg > xemacs-beta
diff lisp/behavior.el @ 2546:5d1743698fb3
[xemacs-hg @ 2005-02-03 05:26:39 by ben]
behavior ws #3: behavior updates
behavior.el: Major update. Add documentation of how it works.
behavior-defs.el: Only define the basic behavior groups here.
Move the definitions for particular packages to the
appropriate package files.
mwheel.el: Add define-behavior for mwheel.
author | ben |
---|---|
date | Thu, 03 Feb 2005 05:26:41 +0000 |
parents | 01c57eb70ae9 |
children | ebb35ddea76a |
line wrap: on
line diff
--- a/lisp/behavior.el Thu Feb 03 05:03:45 2005 +0000 +++ b/lisp/behavior.el Thu Feb 03 05:26:41 2005 +0000 @@ -1,6 +1,6 @@ -;;; behavior.el --- consistent interface onto behaviors +;;; behavior.el --- consistent interface onto packages -;; Copyright (C) 2000, 2001, 2002 Ben Wing. +;; Copyright (C) 2000, 2001, 2002, 2003 Ben Wing. ;; Author: Ben Wing ;; Maintainer: XEmacs Development Team @@ -31,13 +31,152 @@ ;;; Commentary: -;; This file will be dumped with XEmacs. +;; This file is dumped with XEmacs. + +;; This file is part of the "Behaviors" project and is a work in progress. +;; The purpose of the project is to provide (a) a consistent interface (at +;; the API level) onto the functionality provided by packages, and (b) an +;; easy-to-use user interface for this functionality, something that +;; *really works*. +;; +;; First, what characteristics do/should packages have? (NOTE: In this +;; discussion below, `package' and `behavior' are being used more or less +;; interchangeably. Eventually this will get resolved.) + +;; 1) A file, or one or more file, containing the code of the package. In +;; addition, a "head" file in the case that the package needs to be +;; loaded in order to get its functionality (e.g. "load-to-enable" +;; packages -- an abomination that is tolerated only with severe +;; displeasure). +;; 2) A Lisp name -- a fairly short symbol (2-3 words max), uncapitalized, +;; without use of excessive abbreviation and with words set off by +;; dashes. This should be the same as the name of the topmost custom +;; group associated with the package (see next item), and preferably the +;; same as the common prefix used for variables defined by your package +;; and the name of the head file of the package. +;; 3) Associated custom group listing the settings associated with the package. +;; 4) Enable and disable methods for turning on or off the functionality of +;; the package, if it's amenable to such a model. Most packages are of two +;; types: +;; +;; (a) They add some functionality to XEmacs, which is incorporated +;; into and makes changes to the normal functionality of XEmacs. Once the +;; package is enabled, the user doesn't have to do anything specific for +;; the package to do its thing -- it happens automatically if the user is +;; using the area whose behavior has been changed. These include packages +;; such as `avoid' (which makes the mouse poointer move when the cursor +;; gets too close), EFS (which adds the ability to treat an FTP site as +;; part of the local file system), the packages that supply the +;; mode-specific handling for various files, etc +;; +;; (b) They provide functionality in the form of specific command to be +;; invoked. This can be as simple as the `hippie-expand' command (tries +;; lots of different expansion methods for the text before point, to +;; try and get a result) and as complicated as GNUS or VM. +;; +;; Some packages might provide both -- you can enable them and they +;; incorporate some functionality into the XEmacs base, but while +;; they're enabled they provide certain commands. #### We need some +;; thought here, and case-by-case analysis, to determine if this really +;; makes sense -- can the enable/disable be removed and whatever needs +;; to happen incorporated as part of the command? can the +;; enable/disable just added to the commands? +;; +;; 5) Packages of type (b) just above will have a list of commands that can be +;; run. They should be in standard menubar format -- i.e. just like a +;; submenu, but without the initial string indidicating the name of the +;; menu. +;; 6) Short doc string, for use in a menu item. *NOT* necessarily the same +;; as the documentation for the Custom group, which is often too long. +;; 7) Long documentation. +;; +;; Good package etiquette: +;; +;; +;; -- Never mess with the menu yourself, or try to "cheat" by putting yourself +;; high up in the hierarchy, e.g. at the top-level or directly off a +;; top-level group that expects to contain only groups of groups, not +;; end-level groups. +;; +;; -- Never use the `override-behavior' commands or the like for specifying +;; (in an overriding fashion) the exact appearance of the hierarchies. +;; +;; -- For type (a), with enable/disable methods: +;; +;; (a) Loading the file should NOT DO ANYTHING. Not enable, not add hooks, +;; nothing. +;; (b) Both enable and disable hooks must exist. The disable hook must +;; completely reset the environment to how it was before the package +;; was enabled. This includes restoring the prior bindings for +;; modified key bindings. #### We need some helper function to assist +;; with remembering the old key bindings and putting them back only +;; when new key bindings haven't been made -- but recognize when those +;; new key bondings were attached as a result of loading another +;; package, so that after any order of loading and unloading a series +;; of packages, the original bindings will eventually occur. (Something +;; like `advice' for key definitions.) Replacement of functions should +;; happen through `advice'. +;; +;; We recognize that many packages out there don't follow these precepts at +;; all. Many or most of them are install-only, often happening +;; automatically when the file is loaded. Converting these will be a step +;; at a time: First, redo the latter type so that the initialization code +;; is put into a function and not run automatically upon load. Next step, +;; try to provide some sort of disable. Third step, work on making sure +;; that disable removes *everything* and enable puts it all back. Fourth +;; step, work on properly advising keys and functions. +;; + +;; Comparison/Integration with Custom: + +;; Custom only handles variable settings, and has no concept of standard +;; enable/disable methods for a package, a standard way of specifying +;; package documentation, or a list of commands associated with a package. +;; Also, its groups do not always map very well onto packages and the +;; resulting hierarchy is too big, confusing, difficult-to-navigate, and +;; incoherent. More generally it does not address at all the basic problem +;; that a hierarchy created in a decentralized fashion -- and by a large +;; number of authors, some more competent than others -- will inevitably be +;; incoherent when put together. +;; + +;; In general, ease-of-use was not the overarching goal of Custom. The +;; primary goal of Custom seems to have been to provide a consistent interface +;; and get all the packages to use it. Ease-of-use -- or even following +;; established user-interface standards -- has taken a far-distant second, and +;; appears in many respects to be an afterthought that never had any serious +;; effort investigated into it. +;; +;; The eventual intent of this project is to integrate with custom. The final +;; intent of integration is that this project subsumes Custom completely, +;; making Custom the unified, user-friendly means of controlling XEmacs that +;; has never properly existed. However, that will take a lot of work. For +;; the meantime, the plan is to develop the Behavior subsystem independent of +;; Custom, with ease-of-use as the primary goal, and get it to the point where +;; it encompasses most packages out there, has stabilized its interface, and +;; works well. At that point, we will consider integration with Custom. (Note +;; that the hard part of the Behavior work is not actually behaviorizing the +;; packages, but developing the interface itself.) +;; +;; As for integrating with Custom -- ideally that would mean simply extending +;; defgroup, but that is not really possible given that backward-compatibility +;; would not work -- existing versions of `defgroup' give an error when +;; presented with an unknown keyword. In practice, then, this might mean that +;; a separate `define-behavior' command (or `defpackage', or the like) will +;; still exist. ;;; Code: ;; Hash table mapping behavior names to property lists, with entries for -;; :short-doc, :require, :enable, and :disable. +;; :group, :custom-group, :short-doc, :require, :enable, :disable, +;; and :commands. (defconst behavior-hash-table (make-hash-table)) +;; Hash table mapping groups to property lists (entries for :group, :children, +;; :short-doc). +(defconst behavior-group-hash-table (make-hash-table)) +;; Hash table with override information for groups. +;; :short-doc). +(defconst behavior-override-hash-table (make-hash-table)) (defvar within-behavior-enabling-disabling nil) @@ -68,40 +207,137 @@ (defvar behavior-history nil "History of entered behaviors.") -(defun define-behavior (name doc-string &rest cl-keys) +(defun behavior-group-p (group) + "Non-nil if GROUP is the name of a valid behavior group." + (not (null (gethash group behavior-group-hash-table)))) + +(defun check-behavior-group (group) + "Verify that GROUP is a valid behavior group, or nil. +Return GROUP if so." + (or (behavior-group-p group) + (error 'invalid-argument "Invalid behavior group" group)) + group) + +(defun* define-behavior (name doc-string &key + group custom-group + (short-doc + (capitalize-string-as-title + (replace-in-string (symbol-name name) "-" " "))) + require enable disable commands + &allow-other-keys) + ;; We allow other keys to allow for the possibility of extensions by + ;; later versions of XEmacs. Packages should be able to support those + ;; extensions without worrying about causing problems with older versions + ;; of XEmacs. "Define a behavior named NAME. DOC-STRING must be specified, a description of what the behavior does when it's enabled and how to further control it (typically through custom variables). Accepted keywords are +:group Symbol naming the behavior group this behavior is within. +:custom-group Symbol naming the custom group containing the options that + can be set in association with this behavior. If not specified, + the custom group with the same name as the behavior will be + used, if it exists. :short-doc A \"pretty\" version of the name, for use in menus. If omitted a prettified name will be generated. :require A single symbol or a list of such symbols, which need to be present at enable time, or will be loaded using `require'. :enable A function of no variables, which turns the behavior on. :disable A function of no variables, which turns the behavior off. +:commands A list of interactive commands that can be invoked in + conjunction with the behavior. These will appear in a submenu + along with the rest of the items for the behavior. Behaviors are assumed to be global, and to take effect immediately; if the underlying package is per-buffer, it may have to scan all existing buffers and frob them. When a behavior is disabled, it should completely go away *everywhere*, as if it were never invoked at all. -The :disable keywords can be missing, although this is considered bad +The :disable keyword can be missing, although this is considered bad practice. In such a case, attempting to disable the behavior will signal -an error unless you use the `force' option." - (cl-parsing-keywords - ((:short-doc (capitalize-string-as-title (replace-in-string - (symbol-name name) "-" " "))) - :require - :enable - :disable) - t - (let ((entry (list :short-doc cl-short-doc :require cl-require - :enable cl-enable :disable cl-disable))) - (puthash name entry behavior-hash-table)))) +an error unless you use the `force' option. + +The :enable keyword can be missing. This is useful for behaviors that +are really a series of related commands without anything semantically +corresponding to \"turning on\" or \"turning off\" the behavior. + +A behavior with no :enable and no :command is possible. This might be +used, for example, by a behavior that encapsulates a series of related +Lisp functions. Such behaviors may be handled specially, e.g. not +displayed in the menus or displayed in a separate location, since they +have no user-invocable behavior." + (let ((entry (list :group (check-behavior-group group) + :custom-group custom-group + :short-doc short-doc :require require + :enable enable :disable disable + :commands commands))) + (puthash name entry behavior-hash-table)) + ;; update the children list of the group we're in (maybe nil). + (unless (member name (getf (gethash group behavior-group-hash-table) + :children)) + (push name (getf (gethash group behavior-group-hash-table) :children)))) + +(defun* override-behavior (name &key + short-doc + group + include + demote-others) + "Override the default properties of a behavior group NAME. +Normally, groups are created and assigned properties by individual packages. +The resulting hierarchy may not make much sense globally. This function +allows the hierarchy and appearance of a group to be specified globally, +and will take precendence over the properties assigned by `define-behavior-group'. This allows a global organization to be imposed on groups, while still allowing for graceful handling of new or unknown groups. + +NAME can be a symbol specifying a group name, or a list of +\(PARENT [...] NAME), where a path from a particular parent is explicitly +given. (This latter form allows the same name to be assigned to more than one +group.) + +Accepted keywords are + +:short-doc A \"pretty\" version of the name, for use in menus. +:group Parent group, if any. Should not be given if the parents are + explicitly specified in NAME. +:include A list of behaviors that are specifically included in this + group, in addition to those that are included by the behaviors + themselves. +:demote-others If non-nil, exclude all behaviors not specified in the :include + list and put them instead (i.e. \"demote\" them) to another group, + usually a subgroup." + (let ((entry (list :group (check-behavior-group group) + :short-doc short-doc + :include include + :demote-others demote-others))) + (puthash name entry behavior-override-hash-table))) + +(defun* define-behavior-group (name &key + (short-doc + (capitalize-string-as-title + (replace-in-string (symbol-name name) "-" + " "))) + group) + "Define a behavior group NAME. + +NAME can be a symbol specifying a group name, or a list of +\(PARENT [...] NAME), where a path from a particular parent is explicitly +given. (This latter form allows the same name to be assigned to more than one +group.) + +Accepted keywords are + +:short-doc A \"pretty\" version of the name, for use in menus. If omitted + a prettified name will be generated. +:group Parent group, if any. Should not be given if the parents are + explicitly specified in NAME." + (let ((entry (list :group (check-behavior-group group) + :short-doc short-doc))) + (puthash name entry behavior-group-hash-table)) + ;; update the children list of the parent (maybe nil). + (push name (getf (gethash group behavior-group-hash-table) :children))) (defun read-behavior (prompt &optional must-match initial-contents history - default-value) + default-value) "Return a behavior symbol from the minibuffer, prompting with string PROMPT. If non-nil, optional second arg INITIAL-CONTENTS is a string to insert in the minibuffer before reading. @@ -119,12 +355,10 @@ behavior-hash-table) (nreverse lis)))) (mapc #'(lambda (aentry) - (setcar aentry (symbol-name - (car aentry)))) + (setcar aentry (symbol-name (car aentry)))) table) table) - nil must-match initial-contents - (or history 'behavior-history) + nil must-match initial-contents (or history 'behavior-history) default-value))) (if (and result (stringp result)) (intern result) @@ -179,6 +413,99 @@ (customize-set-variable 'enabled-behavior-list (delq behavior enabled-behavior-list)))))) +(defun compute-behavior-group-children (group hash) + "Compute the actual children for GROUP and its subgroups. +This takes into account the override information specified." + (let* ((group-plist (gethash group behavior-group-hash-table)) + (override (gethash group behavior-override-hash-table)) + (children (getf group-plist :children))) + ) + ) + +(defun behavior-menu-filter-1 (menu group) + (submenu-generate-accelerator-spec + (let* ( + ;;options + ;;help + (enable + (menu-split-long-menu + (menu-sort-menu + (let ((group-plist (gethash group behavior-group-hash-table))) + (loop for behavior in (getf group-plist :children) + nconc (if (behavior-group-p behavior) + (list + (cons (getf + (gethash behavior behavior-group-hash-table) + :short-doc) + (behavior-menu-filter-1 menu behavior))) + (let* ((plist (gethash behavior behavior-hash-table)) + (commands (getf plist :commands))) + (nconc + (if (getf plist :enable) + `([,(format "%s (%s) [toggle]" + (getf plist :short-doc) + behavior) + (if (memq ',behavior + enabled-behavior-list) + (disable-behavior ',behavior) + (enable-behavior ',behavior)) + :active ,(if (getf plist :disable) t + (not (memq + ',behavior + enabled-behavior-list))) + :style toggle + :selected (memq ',behavior + enabled-behavior-list)])) + (cond ((null commands) nil) + ((and (eq (length commands) 1) + (vectorp (elt commands 0))) + (let ((comm (copy-sequence + (elt commands 0)))) + (setf (elt comm 0) + (format "%s (%s)" + (elt comm 0) behavior)) + (list comm))) + (t (list + (cons (format "%s (%s) Commands" + (getf plist :short-doc) + behavior) + commands))))))))) + )) + ) + ) + enable) + '(?p))) + +(defun behavior-menu-filter (menu) + (append + '(("%_Package Utilities" + ("%_Set Download Site" + ("%_Official Releases" + :filter (lambda (&rest junk) + (menu-split-long-menu + (submenu-generate-accelerator-spec + (package-ui-download-menu))))) + ("%_Pre-Releases" + :filter (lambda (&rest junk) + (menu-split-long-menu + (submenu-generate-accelerator-spec + (package-ui-pre-release-download-menu))))) + ("%_Site Releases" + :filter (lambda (&rest junk) + (menu-split-long-menu + (submenu-generate-accelerator-spec + (package-ui-site-release-download-menu)))))) + "--:shadowEtchedIn" + ["%_Update Package Index" package-get-update-base] + ["%_List and Install" pui-list-packages] + ["U%_pdate Installed Packages" package-get-update-all] + ["%_Help" (Info-goto-node "(xemacs)Packages")]) + "----") + (behavior-menu-filter-1 menu nil))) + +;; Initialize top-level group. +(puthash nil '(:children nil :short-doc "Root") behavior-group-hash-table) + (provide 'behavior) ;;; finder-inf.el ends here