From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!not-for-mail From: "Drew Adams" Newsgroups: gmane.emacs.devel Subject: RE: customizing key definitions with Customize Date: Fri, 16 May 2008 00:51:56 -0700 Message-ID: <004301c8b729$b707c120$0200a8c0@us.oracle.com> References: <000301c8b39e$ded16a50$0200a8c0@us.oracle.com><003701c8b438$9d6e9f20$0200a8c0@us.oracle.com><87r6c75brc.fsf@jurta.org> <87od79sttq.fsf@jurta.org> <002701c8b646$afedc3a0$0200a8c0@us.oracle.com> NNTP-Posting-Host: lo.gmane.org Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="----=_NextPart_000_0044_01C8B6EF.0AA8E920" X-Trace: ger.gmane.org 1210924614 11084 80.91.229.12 (16 May 2008 07:56:54 GMT) X-Complaints-To: usenet@ger.gmane.org NNTP-Posting-Date: Fri, 16 May 2008 07:56:54 +0000 (UTC) Cc: emacs-devel@gnu.org To: , "'Juri Linkov'" Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Fri May 16 09:57:30 2008 Return-path: Envelope-to: ged-emacs-devel@m.gmane.org Original-Received: from lists.gnu.org ([199.232.76.165]) by lo.gmane.org with esmtp (Exim 4.50) id 1Jwuno-000324-Is for ged-emacs-devel@m.gmane.org; Fri, 16 May 2008 09:56:33 +0200 Original-Received: from localhost ([127.0.0.1]:34971 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1Jwun5-0002Vn-8M for ged-emacs-devel@m.gmane.org; Fri, 16 May 2008 03:55:47 -0400 Original-Received: from mailman by lists.gnu.org with tmda-scanned (Exim 4.43) id 1Jwumm-0002Kw-IR for emacs-devel@gnu.org; Fri, 16 May 2008 03:55:28 -0400 Original-Received: from exim by lists.gnu.org with spam-scanned (Exim 4.43) id 1Jwuml-0002JX-62 for emacs-devel@gnu.org; Fri, 16 May 2008 03:55:27 -0400 Original-Received: from [199.232.76.173] (port=48670 helo=monty-python.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1Jwuml-0002JO-0L for emacs-devel@gnu.org; Fri, 16 May 2008 03:55:27 -0400 Original-Received: from mx20.gnu.org ([199.232.41.8]:30087) by monty-python.gnu.org with esmtps (TLS-1.0:RSA_AES_256_CBC_SHA1:32) (Exim 4.60) (envelope-from ) id 1Jwumd-0005yL-4U; Fri, 16 May 2008 03:55:19 -0400 Original-Received: from agminet01.oracle.com ([141.146.126.228]) by mx20.gnu.org with esmtp (Exim 4.60) (envelope-from ) id 1Jwull-0005eE-9c; Fri, 16 May 2008 03:54:25 -0400 Original-Received: from agmgw2.us.oracle.com (agmgw2.us.oracle.com [152.68.180.213]) by agminet01.oracle.com (Switch-3.2.4/Switch-3.1.7) with ESMTP id m4G7rTX8003363; Fri, 16 May 2008 02:53:29 -0500 Original-Received: from acsmt351.oracle.com (acsmt351.oracle.com [141.146.40.151]) by agmgw2.us.oracle.com (Switch-3.2.0/Switch-3.2.0) with ESMTP id m4F03n2W023328; Fri, 16 May 2008 01:53:28 -0600 Original-Received: from inet-141-146-46-1.oracle.com by acsmt351.oracle.com with ESMTP id 3672712831210924314; Fri, 16 May 2008 00:51:54 -0700 Original-Received: from dradamslap1 (/141.144.65.168) by bhmail.oracle.com (Oracle Beehive Gateway v4.0) with ESMTP ; Fri, 16 May 2008 00:51:54 -0700 X-Mailer: Microsoft Office Outlook 11 In-reply-to: <002701c8b646$afedc3a0$0200a8c0@us.oracle.com> X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.3198 Thread-Index: Aci14WeS7sF2CiUdRxqlv8LezUilIgAYbV7wAC5ICYA= X-Brightmail-Tracker: AAAAAQAAAAI= X-Brightmail-Tracker: AAAAAQAAAAI= X-Whitelist: TRUE X-Whitelist: TRUE X-detected-kernel: by mx20.gnu.org: Linux 2.4-2.6 X-detected-kernel: by monty-python.gnu.org: Linux 2.6, seldom 2.4 (older, 4) X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Original-Sender: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Xref: news.gmane.org gmane.emacs.devel:97244 Archived-At: This is a multi-part message in MIME format. ------=_NextPart_000_0044_01C8B6EF.0AA8E920 Content-Type: text/plain; charset="iso-8859-1" Content-Transfer-Encoding: quoted-printable I wrote: > The code I sent indicates how to do this. As I said, to make=20 > the option's customizable key definitions be the only ones > in the keymap, it is enough to change the :set function I > used so that it first empties the keymap, before it adds the > customized keys. >=20 > That is, if a keymap variable is to be completely=20 > customizable, then that is what we would want to do: make > sure that :set not only adds bindings but also changes and > deletes existing bindings. That is the same :set function I > sent, but with the addition of a preliminary operation that > empties the keymap, so that setting the option makes the > keymap reflect just those key definitions that are present. Let me expand on that a bit. New keymap-option.el attached - please try = it out. 1. In the implementation I sent previously, the :set function only did `define-key' for the key definitions (after the user was done editing = them and chose `Set for Session'). That meant that no previously existing key = bindings were removed or changed, even if the user changed a `Key' value or = clicked DEL to delete the key definition from the option. Only new bindings were = made. 2. To remedy that, the :set function needs to first remove all existing = key bindings, so the only bindings will be those the user has kept. I've = done that now, in the attached version. The :set code now calls `makunbound' and = then sets the keymap variable to (make-sparse-keymap) before it defines the keys = per the user's customizations. 3. With that change, you can no longer use `custom-create-keymap-option' = to create an option that represents only some of a keymap's bindings. As I mentioned in another mail, that can sometimes be useful, to avoid = encouraging users to fiddle with some bindings, for instance. Probably that behavior = should be optional, so code can choose whether the user's customization should represent everything about the keymap (replaces its previous value = completely) or only some of its bindings. 4. `custom-create-keymap-option' creates a different variable (option) = from the keymap variable. This is necessary, IMO, because the keymap itself does = not match the custom type - the two variables have different types: one is a = keymap and the other is a `repeat' of `key-definition'. Every keymap can have = an associated user option for customization, but it need not have one = created for it automatically. An explicit call to `custom-create-keymap-option' = creates the option from the keymap variable. Code such as `define-minor-mode' could = be made to automatically call `custom-create-keymap-option' to do that. 5. The prefix keys of a keymap are not present as part of the defcustom. = I use the output of `substitute-command-keys' to capture the bindings of the = keymap, and then filter out those printed as `Prefix Command'. The individual = bindings that use the prefix are all present, but the prefix itself is not = present explicitly as a `key-definition'. 6. However, users need to be able to bind a key to a keymap within = Customize, that is, to create a prefix key. They can do that by using, as the = target `Command', a command whose symbol-function is a keymap. You can, for = instance, customize a key to have the `Command' value of `ctl-x-5-prefix', = effectively making that key a prefix key. 7. In the previous version I sent, input of the keymap variable used = strict completion, and an error was raised if the variable was for some reason = not a symbol bound to a keymap. In the new version, completion is lax, and you = can use `custom-create-keymap-option' to create both the user option and the = keymap itself. IOW, you can input a new symbol `foo-map', and the result is (1) = a keymap variable bound to an empty sparse keymap and (2) the = corresponding user option, `foo-map-defs', which users can customize to add key bindings. 8. With the new version, customization of keymaps is fairly complete, = except for these things:=20 a. It still does not do anything special for ranges of keys. It naively = produces a single `Key' entry with a value of, say, `\200 . . =FF'. I'm not sure = what would be the best way to deal with such key ranges. We could filter them out, = but then the option would not faithfully represent the keymap. If you do, for = instance, `M-x custom-create-keymap-option global-map', it works, and you can then = do `customize-option global-map-defs'. But you will see four range entries = as four single key definitions: \200 . . =FF, bound to `self-insert-command' C-0 . . C-9, bound to `digit-argument' C-M-@ . . M-, bound to `digit-argument' C-M-0 . . C-M-9, bound to `digit-argument' (And you will see the message "if: Key sequence C-M-@ . . M- starts = with non-prefix key C-M-@". when the option is created.) We could parse the range expression and create multiple key definitions = from it, but that would fill the Customize buffer with lots of keys with the same = binding - imagine what that would do for `self-insert-command'. Ideas? b. You cannot yet use a lambda form as a command to bind to a key. So, = for instance, if a keymap has a key bound to (lambda () (interative) = (message "hello")) then the code currently just strips that key definition from = the option. The input from `substitute-command-keys' is just `??', which is unhelpful. Similarly, when customizing, you cannot enter a lambda form = as the `Command' sexp. Perhaps the definition of widget `key-definition' could = be altered to accommodate lambdas. c. The `edmacro-parse-keys' Emacs bug I reported can cause some menu = bindings to break. It turns a menu item such as = into < B r a z i l i a n P o r t u g u e s e >. When that bug is = fixed, this problem will go away. 10. The attached version also fixes some bugs that caused `custom-create-keymap-option' to loop forever. For example, some = printouts from `substitute-command-keys' (e.g. for `emacs-lisp-mode-map') include the = following line, which wasn't taken into account: " (that binding is currently = shadowed by another mode)". The attached code corrects these problems. This is the = kind of thing I meant by the code being fragile since it uses the output of `substitute-command-keys' as input. Nevertheless, it seems to work = pretty well. 11. I also special-cased `prev-buffer', which, like `mouse-face' and `ignore-event', appears as a key binding but is not in fact a command. = Dunno if there are anymore such symbols. Please try the code. Again, the implementation is admittedly fragile and = not pretty - a more direct approach using `map-keymap' or whatever might be preferable. But this at least works. It gives an idea of a possible UI = for customizing keymaps and some of the choices or issues we might want to = discuss. ------=_NextPart_000_0044_01C8B6EF.0AA8E920 Content-Type: application/octet-stream; name="keymap-option.el" Content-Transfer-Encoding: quoted-printable Content-Disposition: attachment; filename="keymap-option.el" (define-widget 'key-definition 'lazy=0A= "Key definition.=0A= A list of two components: KEY, BINDING.=0A= KEY is either a key sequence (string or vector) or a command.=0A= If KEY is a command, then the binding represented is its remapping=0A= to BINDING, which must also be a command."=0A= :tag "Key Definition" :indent 1 :offset 0=0A= :type=0A= '(list=0A= (choice=0A= (key-sequence :tag "Key" :value [ignore])=0A= (restricted-sexp :tag "Command to remap"=0A= :match-alternatives (commandp) :value ignore))=0A= (sexp :tag "Command")))=0A= =0A= (defun custom-create-keymap-option (map)=0A= "Define a user option for keymap MAP."=0A= (interactive=0A= (list (intern=0A= (completing-read=0A= "Keymap (symbol): " obarray=0A= (lambda (s) (and (boundp s) (keymapp (symbol-value s))))=0A= nil nil 'variable-name-history))))=0A= (let ((opt-name (intern (concat (symbol-name map) "-defs")))=0A= (defs ()))=0A= (if (not (and (symbolp map) (boundp map) (keymapp (symbol-value = map))))=0A= (set map (make-sparse-keymap))=0A= (with-temp-buffer=0A= (princ (substitute-command-keys=0A= (concat "\\{" (symbol-name map) "}")) (current-buffer))=0A= (goto-char (point-min))=0A= (with-syntax-table emacs-lisp-mode-syntax-table=0A= (while (re-search-forward=0A= "^key +binding\n\\(-+ +\\)-+\n\n" nil t)=0A= (let ((col (- (match-end 1) (match-beginning 1))))=0A= (while (and (not (eobp)) (not (looking-at "\n\\s-*\n")))=0A= (if (or (eolp)=0A= (looking-at "^\\S-+.+\\s-+Prefix Command$")=0A= (looking-at ".+(binding currently shadowed)$")=0A= (looking-at "^\\s-+(that binding is currently \=0A= shadowed by another mode)$")=0A= (looking-at "^.+\\s-+[?][?]"))=0A= (delete-region (line-beginning-position)=0A= (1+ (line-end-position)))=0A= (end-of-line)=0A= (skip-chars-backward "^ \t\n")=0A= (looking-at "\\(\\sw\\|\\s_\\)+$")=0A= (if (>=3D (current-column) col)=0A= (let ((sym (intern-soft (match-string 0)))=0A= (cmd-beg (match-beginning 0))=0A= eokey-pos)=0A= (cond ((or (fboundp sym)=0A= (memq sym '(mouse-face ignore-event = prev-buffer)))=0A= (end-of-line)=0A= (insert ")")=0A= (goto-char cmd-beg)=0A= (skip-chars-backward " \t")=0A= (setq eokey-pos (point))=0A= (insert "\")") ; +2=0A= (forward-line 0)=0A= (insert "(,(kbd \"") ; +8=0A= (while (< (point) (+ 8 eokey-pos))=0A= (when (looking-at "\\(\"\\|\\\\\\)")=0A= (insert "\\"))=0A= (forward-char))=0A= (goto-char (+ 10 cmd-beg))=0A= (forward-line))=0A= (t=0A= (forward-line)=0A= (if (looking-at "^\\s-+\\S-+$")=0A= (custom-create-keymap-option-1 col)=0A= (beginning-of-line)=0A= (delete-region=0A= (line-beginning-position)=0A= (1+ (line-end-position)))))))=0A= (forward-line)=0A= (if (looking-at "^\\s-+\\S-+$")=0A= (custom-create-keymap-option-1 col)=0A= (forward-line -1)=0A= (delete-region (line-beginning-position)=0A= (1+ (line-end-position 2))))))))))=0A= (goto-char (point-min))=0A= (while (re-search-forward=0A= "^key +binding\n\\(-+ +\\)-+\n\n" nil t)=0A= (forward-line -3)=0A= (delete-region (line-beginning-position)=0A= (1+ (line-end-position 3))))=0A= (insert "`(\n")=0A= (goto-char (point-max))=0A= (insert ")")=0A= (goto-char (point-min))=0A= (setq defs (eval (read (current-buffer))))))=0A= (eval=0A= `(defcustom ,opt-name=0A= ',defs=0A= ,(format "Customizable keymap for `%s'." map)=0A= :type '(repeat key-definition)=0A= :set #'(lambda (sym defns)=0A= (custom-set-default sym defns)=0A= (makunbound ',map)=0A= (setq ,map (make-sparse-keymap))=0A= (let (key command)=0A= (dolist (key-def defns)=0A= (setq key (car key-def)=0A= command (cadr key-def))=0A= (if (symbolp key)=0A= (define-key=0A= ,map (vector 'remap key) command)=0A= (define-key ,map key command)))))=0A= :initialize #'custom-initialize-set))))=0A= =0A= (defun custom-create-keymap-option-1 (col)=0A= (end-of-line)=0A= (skip-chars-backward "^ \t\n")=0A= (when (looking-at "\\(\\sw\\|\\s_\\)+$")=0A= (if (>=3D (current-column) col)=0A= (let ((sym (intern-soft (match-string 0))))=0A= (cond ((or (fboundp sym)=0A= (memq sym '(mouse-face ignore-event)))=0A= (end-of-line)=0A= (insert ")")=0A= (forward-line -1)=0A= (end-of-line)=0A= (insert "\")") ; +2=0A= (forward-line 0)=0A= (insert "(,(kbd \"") ; +8=0A= (forward-line 2))=0A= (t=0A= (forward-line 0)=0A= (delete-region (line-beginning-position)=0A= (1+ (line-end-position 2)))))))))=0A= =0A= =0A= ------=_NextPart_000_0044_01C8B6EF.0AA8E920--