unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
From: "Drew Adams" <drew.adams@oracle.com>
To: <rms@gnu.org>, "'Juri Linkov'" <juri@jurta.org>
Cc: emacs-devel@gnu.org
Subject: RE: customizing key definitions with Customize
Date: Fri, 16 May 2008 00:51:56 -0700	[thread overview]
Message-ID: <004301c8b729$b707c120$0200a8c0@us.oracle.com> (raw)
In-Reply-To: <002701c8b646$afedc3a0$0200a8c0@us.oracle.com>

[-- Attachment #1: Type: text/plain, Size: 6918 bytes --]

I wrote:

> The code I sent indicates how to do this. As I said, to make 
> 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.
> 
> That is, if a keymap variable is to be completely 
> 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: 

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 . . ÿ'. 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 . . ÿ, 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 <describe> <describe-language-environment>
<European>
<Brazilian Portuguese> into <describe> <describe-language-environment>
<European> < 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.

[-- Attachment #2: keymap-option.el --]
[-- Type: application/octet-stream, Size: 5970 bytes --]

(define-widget 'key-definition 'lazy
  "Key definition.
A list of two components: KEY, BINDING.
KEY is either a key sequence (string or vector) or a command.
If KEY is a command, then the binding represented is its remapping
to BINDING, which must also be a command."
  :tag "Key Definition" :indent 1 :offset 0
  :type
  '(list
    (choice
     (key-sequence :tag "Key" :value [ignore])
     (restricted-sexp :tag "Command to remap"
      :match-alternatives (commandp) :value ignore))
    (sexp :tag "Command")))

(defun custom-create-keymap-option (map)
  "Define a user option for keymap MAP."
  (interactive
   (list (intern
          (completing-read
           "Keymap (symbol): " obarray
           (lambda (s) (and (boundp s) (keymapp (symbol-value s))))
           nil nil 'variable-name-history))))
  (let ((opt-name (intern (concat (symbol-name map) "-defs")))
        (defs ()))
    (if (not (and (symbolp map) (boundp map) (keymapp (symbol-value map))))
        (set map (make-sparse-keymap))
      (with-temp-buffer
        (princ (substitute-command-keys
                (concat "\\{" (symbol-name map) "}")) (current-buffer))
        (goto-char (point-min))
        (with-syntax-table emacs-lisp-mode-syntax-table
          (while (re-search-forward
                  "^key +binding\n\\(-+ +\\)-+\n\n" nil t)
            (let ((col (- (match-end 1) (match-beginning 1))))
              (while (and (not (eobp)) (not (looking-at "\n\\s-*\n")))
                (if (or (eolp)
                        (looking-at "^\\S-+.+\\s-+Prefix Command$")
                        (looking-at ".+(binding currently shadowed)$")
                        (looking-at "^\\s-+(that binding is currently \
shadowed by another mode)$")
                        (looking-at "^.+\\s-+[?][?]"))
                    (delete-region (line-beginning-position)
                                   (1+ (line-end-position)))
                  (end-of-line)
                  (skip-chars-backward "^ \t\n")
                  (looking-at "\\(\\sw\\|\\s_\\)+$")
                  (if (>= (current-column) col)
                      (let ((sym (intern-soft (match-string 0)))
                            (cmd-beg (match-beginning 0))
                            eokey-pos)
                        (cond ((or (fboundp sym)
                                   (memq sym '(mouse-face ignore-event prev-buffer)))
                               (end-of-line)
                               (insert ")")
                               (goto-char cmd-beg)
                               (skip-chars-backward " \t")
                               (setq eokey-pos (point))
                               (insert "\")") ; +2
                               (forward-line 0)
                               (insert "(,(kbd \"") ; +8
                               (while (< (point) (+ 8 eokey-pos))
                                 (when (looking-at "\\(\"\\|\\\\\\)")
                                   (insert "\\"))
                                 (forward-char))
                               (goto-char (+ 10 cmd-beg))
                               (forward-line))
                              (t
                               (forward-line)
                               (if (looking-at "^\\s-+\\S-+$")
                                   (custom-create-keymap-option-1 col)
                                 (beginning-of-line)
                                 (delete-region
                                  (line-beginning-position)
                                  (1+ (line-end-position)))))))
                    (forward-line)
                    (if (looking-at "^\\s-+\\S-+$")
                        (custom-create-keymap-option-1 col)
                      (forward-line -1)
                      (delete-region (line-beginning-position)
                                     (1+ (line-end-position 2))))))))))
        (goto-char (point-min))
        (while (re-search-forward
                "^key +binding\n\\(-+ +\\)-+\n\n" nil t)
          (forward-line -3)
          (delete-region (line-beginning-position)
                         (1+ (line-end-position 3))))
        (insert "`(\n")
        (goto-char (point-max))
        (insert ")")
        (goto-char (point-min))
        (setq defs (eval (read (current-buffer))))))
    (eval
     `(defcustom ,opt-name
        ',defs
        ,(format "Customizable keymap for `%s'." map)
        :type '(repeat key-definition)
        :set #'(lambda (sym defns)
                 (custom-set-default sym defns)
                 (makunbound ',map)
                 (setq ,map (make-sparse-keymap))
                 (let (key command)
                   (dolist (key-def defns)
                     (setq key      (car key-def)
                           command  (cadr key-def))
                     (if (symbolp key)
                         (define-key
                             ,map (vector 'remap key) command)
                       (define-key ,map key command)))))
        :initialize #'custom-initialize-set))))

(defun custom-create-keymap-option-1 (col)
  (end-of-line)
  (skip-chars-backward "^ \t\n")
  (when (looking-at "\\(\\sw\\|\\s_\\)+$")
    (if (>= (current-column) col)
        (let ((sym (intern-soft (match-string 0))))
          (cond ((or (fboundp sym)
                     (memq sym '(mouse-face ignore-event)))
                 (end-of-line)
                 (insert ")")
                 (forward-line -1)
                 (end-of-line)
                 (insert "\")")         ; +2
                 (forward-line 0)
                 (insert "(,(kbd \"")   ; +8
                 (forward-line 2))
                (t
                 (forward-line 0)
                 (delete-region (line-beginning-position)
                                (1+ (line-end-position 2)))))))))



  parent reply	other threads:[~2008-05-16  7:51 UTC|newest]

Thread overview: 43+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2008-05-11 19:40 customizing key definitions with Customize Drew Adams
2008-05-11 22:02 ` Lennart Borgman (gmail)
2008-05-11 22:28   ` Drew Adams
2008-05-11 22:40     ` Lennart Borgman (gmail)
2008-05-11 23:02       ` Drew Adams
2008-05-11 23:09         ` Lennart Borgman (gmail)
2008-05-11 23:19           ` Drew Adams
2008-05-11 23:23             ` Lennart Borgman (gmail)
2008-05-11 23:34               ` Drew Adams
2008-05-12 20:42                 ` Lennart Borgman (gmail)
2008-05-14  5:24         ` Drew Adams
2008-05-12  8:59 ` Reiner Steib
2008-05-12 20:58   ` Drew Adams
2008-05-12 11:20 ` Richard M Stallman
2008-05-12 14:01   ` Drew Adams
2008-05-13  0:03     ` Juri Linkov
2008-05-13  0:40       ` Lennart Borgman (gmail)
2008-05-13 14:59       ` Richard M Stallman
2008-05-13 23:59         ` Juri Linkov
2008-05-14  1:10           ` Stefan Monnier
2008-05-14 16:40           ` Richard M Stallman
2008-05-15  4:46             ` Drew Adams
2008-05-15 17:39               ` Richard M Stallman
2008-05-16  8:01                 ` Drew Adams
2008-05-16 17:46                   ` Richard M Stallman
2008-05-16 18:00                     ` David Kastrup
2008-05-16 23:58                       ` Drew Adams
2008-05-17  5:00                       ` Richard M Stallman
2008-05-16  7:51               ` Drew Adams [this message]
2008-05-18  1:22                 ` Drew Adams
2008-05-18  9:07                   ` Key/menu bug? (was: customizing key definitions with Customize) David Kastrup
2008-05-13 15:07       ` customizing key definitions with Customize David Reitter
2008-05-13 19:05         ` David Kastrup
2008-05-14  5:23       ` Drew Adams
2008-05-13  5:16     ` Richard M Stallman
2008-05-14  5:23       ` Drew Adams
2008-05-14 16:39         ` Richard M Stallman
2008-05-15  4:36           ` Drew Adams
2008-05-15 17:39             ` Richard M Stallman
2008-05-16  8:02               ` Drew Adams
2008-05-16 17:46                 ` Richard M Stallman
2008-05-16 23:58                   ` Drew Adams
2008-05-12 20:42   ` Lennart Borgman (gmail)

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.gnu.org/software/emacs/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to='004301c8b729$b707c120$0200a8c0@us.oracle.com' \
    --to=drew.adams@oracle.com \
    --cc=emacs-devel@gnu.org \
    --cc=juri@jurta.org \
    --cc=rms@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).