tags:

views:

428

answers:

3

Most emacs modes include some sort of prefix to activate their features. For example, when using GUD "next" is "C-c C-n". Of these modes, many provide special buffers where one can use a single key to activate some functionality (just 'n' or 'p' to read next/previous mail in GNUS for example).

Not all modes provide such a buffer, however, and repeatedly typing the prefix can be tiresome. Is there a well-known bit of elisp that will allow for ad-hoc specification of prefix keys to be perpended to all input for some time? (Until hitting ESC or some other sanctioned key, for example)

+1  A: 

The mode-specific (modal?) part of key-bindings in Emacs is realized by the various local maps that overshadow the universal global-map. Most major and minor modes define their own local maps; for example, there is a gud-mode-map. Key bindings in a local keymap will be activated only when the current-buffer is in the respective mode. You can customize a mode specific keymap through the mode's hook. For example, you may put this snippet into your ~/.emacs

    (add-hook 'gud-mode-hook
      (lambda () 
        (local-set-key (kbd "C-n") 
                       (lookup-key (current-local-map) (kbd "C-c C-n")))))

More details about keymaps can be found in Elisp reference manual.

huaiyuan
This is useful information, but not quite what I'm looking for. Consider folding-mode, which has things like "C-c C-@ >". I'd like to quickly specify "C-c C-@" as my prefix so I can just type ">" to enter a fold without binding keys in my .emacs every mode one by one.
fdr
+1  A: 

The basic issue here is that there is no current keymap per-se. There's the global keymap which is overridden by the major mode's keymap which in turn is overridden by one or more minor mode keymaps (and they can step on each other in some defined way, I'm sure). Defining a new major mode will still leave the minor mode keys functional, and defining a new minor mode will only affect whatever keys you define in the minor mode's keymap.

For example, you could define a minor mode that will do what you want as long as the minor mode is active. You define a new minor mode my-gud-mode which will have its own keymap. You would then have to define all of your key mappings for it (e.g. n, p, etc) and you would also have to define all of the keys that you didn't want to work to be bound to the function ignore. That's the real pain of this, remapping all of the other keys. The minor mode is easy to switch on and off, though; that's the advantage.

Defining a new major mode would be easier at first blush, as it will let you override more of the "current keymap" in one shot. It should note the current major mode in a buffer-local variable so it can be restored later when the temporary major mode is turned off. But you'll still have other minor modes intruding into your keymap, so it won't be "pure".

What I do in this situation is define an easier prefix! For stuff I use all of the time, all day every day, I give them a function key all on their own (e.g. I have F1 set aside as my jabber-mode key). For less immediately useful things, I have two other function keys set aside, F3 and F12 (I'm sure there was some reason I picked them long ago, but I no longer remember why). F3 defines keys that are always available, regardless of major mode. F12 defines keys that are major-mode-dependent. Some examples:

I have set up F3-m- as a prefix to switch major modes (e.g. F3-m-p switches to cperl-mode) and F3-M- as a prefix for minor modes (e.g. F3-M-v toggles view-mode). These are always available, so you could do something like bind F3-g- to be your gud prefix, and type F3-g-p for previous and so on.

My F12 key is mode-dependent. So, in dired mode F12-e will call dired-nt-open-in-excel on the current file, and in emacs-lisp-mode F12-e will call elint-current-buffer. Somehow I never get them confused.

If you need help in defining keymaps like this, let me know.

Joe Casadonte
+8  A: 

I agree with Joe Casadonte's answer that the way to go is to define your own minor (or major) mode.

That being said, your question is interesting.

Here's a solution that prompts you for a key sequence and it takes the prefix keystrokes and promotes that keymap to the top level.

e.g. Assume the following keymap:

M-g ESC         Prefix Command
M-g g           goto-line
M-g n           next-error
M-g p           previous-error

When you run M-x semi-modal-minor-mode, it will prompt you for some keystrokes. If you enter M-g n, then the following keybindings are set:

ESC         Prefix Command   (same as M-g ESC)
g           goto-line
n           next-error
p           previous-error

So now n doesn't self-insert, but jumps to the next error. See the code below.

Note: when this minor mode is enabled, <f12> is bound to a command which disables the minor mode. This is because the keybindings might very well disable your Emacs (for instance, what if there was a new keybinding for M-x).

Edited to add these thoughts: the minor mode variable was originally made buffer local, but that doesn't work unless you also make the minor-mode-alist variable buffer local (duh). But, you also (probably) don't want these bindings in the minibuffer... So, I'm not going to test it b/c it really depends on what you want, but I've added a comment to the code reflecting this thought.

Without further ado:

(defvar semi-modal-minor-mode-keymap (make-sparse-keymap)
  "keymap holding the prefix key's keymapping, not really used")
(defvar semi-modal-minor-mode-disable-key (kbd "<f12>")
  "key to disable the minor mode")
(defun semi-modal-minor-mode-disable ()
  "disable the minor mode"
  (interactive)
  (semi-modal-minor-mode 0))

(define-minor-mode semi-modal-minor-mode
  "local minor mode that prompts for a prefix key and promotes that keymap to the toplevel
e.g. If there are bindings like the following:

M-g ESC         Prefix Command
M-g g           goto-line
M-g n           next-error
M-g p           previous-error

And you enter 'M-g n' when prompted,
then the minor mode keymap has the bindings
  g   ->   goto-line
  n   ->   next-error
  p   ->   previous-error
  ESC ->   Prefix Command (same as M-g ESC)

The variable semi-modal-minor-mode-disable-key is bound to disable the minor mode map.
This is provided because often the mappings make the keyboard unusable.
Use at your own risk."
  nil " Semi" semi-modal-minor-mode-keymap
  (make-local-variable 'semi-modal-minor-mode)
  (make-local-variable 'minor-mode-map-alist)
  (let ((pair-holding-keymap-to-modify (assq 'semi-modal-minor-mode minor-mode-map-alist)))
    (setcdr pair-holding-keymap-to-modify (make-sparse-keymap))
    (if semi-modal-minor-mode
        (let (key
              keymap)
          ;; all but last (b/c we want a prefix
          (setq key (substring (read-key-sequence "Enter a full key combination, the prefix will be used: ") 0 -1))
          (if (and (not (equal "" key))
                   (not (equal (kbd "C-g") key))
                   (let ((semi-modal-minor-mode nil))
                     (keymapp (setq keymap (key-binding key)))))
              (progn
                (setcdr pair-holding-keymap-to-modify (copy-keymap keymap))
                (when semi-modal-minor-mode-disable-key
                  (define-key (cdr pair-holding-keymap-to-modify)
                    semi-modal-minor-mode-disable-key 'semi-modal-minor-mode-disable)))
            (semi-modal-minor-mode 0))))))
Trey Jackson
This is what I have been looking for. I may make some minormodifications to make it more global (do you have any suggestions?When using cscope jumping out of the semi-modal buffer will make melose the modality...) but the local-ish minor-mode feel seems usefultoo.
fdr
(It seems that the global keymap would suffice, but I thought I shouldask just in case there was some other alternate consideration)
fdr
Dude, that is friggin' awesome! I don't know if I'd ever use it, but it is a great piece of code. If I could give you more than one up-vote, I surely would!
Joe Casadonte
The nice thing about having this be a minor mode is that you can turn it on/off as needed. What you probably want is to have this associated with other modes, e.g. your folding example. Something like: (add-hook 'folding-mode-hook '(lambda () (semi-modal-minor-mode (kbd "C-c@")))
Trey Jackson
I agree, but a global-ish version may also be useful, particularly when it comes to source navigation shortcuts that may be valid as part of the global keymap. On the other hand, come to think of it, that's not very common, so forget what I just said. Thanks greatly!
fdr