unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
From: "Drew Adams" <drew.adams@oracle.com>
To: <emacs-devel@gnu.org>
Subject: RE: customizing key definitions with Customize
Date: Tue, 13 May 2008 22:24:09 -0700	[thread overview]
Message-ID: <005c01c8b582$bcf2bde0$0200a8c0@us.oracle.com> (raw)
In-Reply-To: <001501c8b3bb$0e33c830$0200a8c0@us.oracle.com>

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

> > >> I like the idea, but can't it be taken one step further: 
> > >> Wouldn't it be nice with a "sparse-keymap widget"? And then
> > >> of course `customize-sparse-keymap'.
> > >>
> > >> Or have you already done this too, Drew?
> > > 
> > > No, I haven't taken it further than what I wrote. But I 
> > > don't really know what you have in mind. Perhaps you can
> > > elaborate or go further with the idea yourself?
> > 
> > I was just thinking `customize-sparse-keymap'. That would 
> > bring up the current bindings in that keymap in a custom
> > buffer using widgets similar to your `key-definition' widget.
> 
> I see. Yes, that could be easily done.

Not quite as easily as I thought, at least for me. ;-)

I did take a look, however, and came up with the following code (attached).
There are no doubt more elegant and more direct ways to do this than to use
`substitute-command-keys' and `eval'. Probably something like `map-keymap' could
be used, but its automatic ancestor keymap recursion scared me off. 

The approach I used is certainly ugly and perhaps fragile, but it seems to work.
It works for menu maps also. It excludes prefix keys. It special-cases
`mouse-face' and `ignore-event', since they are not in fact commands. (Hence,
the `key-definition' widget is different from what I sent before - it no longer
requires the `Command' sexp to be a command.)

If nothing else, this gives an idea of what a UI for customizing keymaps might
be like. Please try it out, as follows:

Command `custom-create-keymap-option' creates a user option of the same name as
its keymap argument, but with `-defs' appended. E.g.,

 M-x custom-create-keymap-option RET bookmark-map

Completion is available for keymap-valued symbols. Then you can customize the
new option that corresponds to the keymap:

 M-x customize-option RET bookmark-map-defs

Note that, as I mentioned before, you can specify a command to remap instead of
a key to bind (use Value Menu).

And yes, the keymap itself is modified when you change key definitions in
Customize - the option's :set function does that. 

However, the option is currently decoupled from the keymap to some extent: The
:set function currently just does `define-key' for each of its key definitions.
It does not also reinitialize (empty out) the keymap so that it will have only
those definitions. That could be done, but it's worth thinking about whether
that is always what is wanted (probably).

This means that, currently: If you change the command part of a key definition,
the same key becomes bound to the new command. But if you change the key part of
a key definition, the new key becomes bound to the same command - but the old
key is not unbound from the command. Similarly, DEL deletes the key definition
from the user option, but it does not unbind the key in the keymap. IOW, you are
editing the option, and the :set function for the option currently just does
`define-key' for whatever definitions are present.

Also, in a new Emacs session, after saving the option, there is nothing that
automatically defines the corresponding keymap from the option. Currently, the
library that defines the keymap would need to explicitly initialize it from the
option, in order for the saved value to be picked up in a future session. 

In Icicles, for instance, the bindings are picked up from the option whenever
you enter Icicle mode. In Icicles, I use code similar to what I sent previously,
which provides users only some of the key definitions, by default. It is not my
intention to invite users to customize the whole keymap (but they are of course
able to do that via INS).

That I think is a useful part of this approach of having an option separate from
the keymap: Whereas `custom-create-keymap-option' blindly includes all of a
keymap's bindings in the option it creates, libraries could instead choose to
provide a hand-rolled (or pruned) option that includes only some of the
bindings.

Defining keymaps from their corresponding options could perhaps be built into
Emacs somehow - either generally or just for specific contexts, such as minor
mode keymaps (e.g. via macro `define-minor-mode'). The code responsible for
this, whether automatic or not, would check whether a keymap option existed and,
if so, would use it to define the keymap.

There are lots of possibilities, depending on what uses are seen for something
like this.

While testing the code, BTW, I uncovered 2 or 3 Emacs bugs. These are the bug
report threads:

1. "kbd returns wrong value"
2. "substitute-command-keys incorrect for self-insert-command"
3. "\ is considered whitespace syntax in Lisp mode?" (maybe only a doc bug - not
clear to me, anyway)

#1 is a bug in `edmacro-parse-keys' (or in the `Describe' menu, depending on how
you look at it). It causes a bogus entry if you try `M-x
custom-create-keymap-option menu-bar-help-menu', because `<Brazilian
Portuguese>' has a space in it.

Related to #2 and #3: if you try `custom-create-keymap-option' on a non-sparse
keymap, such as `global-map' or `dired-mode-map', you will see an entry that
looks like this in Customize:

INS DEL Key Definition: List:
 Choice: Value Menu Key: \200 . . ÿ

I haven't tried to make such an entry do anything useful. That is, the custom
type here does not recognize \200 . . ÿ as a key range - it treats it as an
ordinary, single key sequence. Such an entry should probably just be filtered
out, unless it can be made useful.



[-- Attachment #2: keymap-option.el --]
[-- Type: application/octet-stream, Size: 5591 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."
  ;; $$$ How to exclude full keymaps? Is it worth it?
  (interactive
   (list (intern
          (completing-read
           "Keymap (symbol): " obarray
           (lambda (s) (and (boundp s) (keymapp (symbol-value s))))
           t nil 'variable-name-history))))
  (unless (and (symbolp map) (boundp map)
               (keymapp (symbol-value map)))
    (error "`%S' is not a keymapp" map))
  (let ((opt-name (intern (concat (symbol-name map) "-defs")))
        (defs ()))
    (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")))
              (when (eolp) (delete-region (line-beginning-position)
                                          (1+ (line-end-position))))
              (while (looking-at "^\\S-+.+\\s-+Prefix Command$")
                (delete-region (line-beginning-position)
                               (1+ (line-end-position))))
              (end-of-line)
              (skip-chars-backward "^ \t\n")
              (when (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)))
                             (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)
                               (beginning-of-line)
                               (delete-region
                                (line-beginning-position)
                                (1+ (line-end-position)))))))
                  (forward-line)
                  (if (looking-at "^\\s-+\\S-+$")
                      (custom-create-keymap-option-1)
                    (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)
                 (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 ()
  (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 ((fboundp sym)
                 (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-14  5:24 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 [this message]
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
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='005c01c8b582$bcf2bde0$0200a8c0@us.oracle.com' \
    --to=drew.adams@oracle.com \
    --cc=emacs-devel@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).