tags:

views:

147

answers:

3

Hey,

I have a minor mode. If that mode is active and the user hits DEL, I want to do some action, but only if some condition holds. If the condition holds and the action is executed I want to do nothing more after that. But if the condition fails, I don't want to do anything and let the default DEL action execute.

Not sure how I could solve this. But I guess I could do it in two ways:

1) I could rebind the DEL key to a function in the minor mode and then check if the conditions holds ot not. But then how do I know what the default command to DEL is?

2) I could add a pre command hook like this. Execute the command and then break the chain. But how do I break the chain?

(add-hook 'pre-command-hook
          (lambda()
            (when (equal last-input-event 'backspace)
              ;; Do something and then stop (do not execute the
              ;; command that backspace is bound to)
              )))

In what way would you solve it? Thanks!

A: 

There doesn't seem to be a way to do what you want reliably. If your new command is bound to DEL, then whatever was bound to DEL before in the current keymap isn't there anymore. The other approach you proposed won't work because pre-command-hooks don't prevent the following action from taking place. You might also think to interrupt further execution with ^G (Keyboard-Quit), but that's an uncontrolled interrupt that might stop more things than you want.

Even if you make the process of setting up the new binding a little more sophisticated than just re-bind, and remember what was bound there before, so you can call it afterwards, you don't really have what your looking for. If someone wants to rebind the "default" action, they have to do it by modifying to your function rather than replacing the key binding.

What you want to do doesn't fit Emacs' model of how key binding works.

PanCrit
+6  A: 

The way to do this is to temporarily disable your minor mode, then look up the key binding.

Pretend that you've bound 'do-thingy to DEL. Then this would do the trick (assuming the condition you want to trigger off is (equal last-input-event 'backspace):

(defun do-thingy ()
  "Do something, unless last event was backspace."
  (interactive)
  (if (equal last-input-event 'backspace)
      (let* ((my-minor-mode nil)
             (original-func (key-binding (kbd "DEL"))))
        ;; original-func is whatever DEL would be if
        ;; my-minor-mode were disabled
        (call-interactively original-func))
    (message "Here's my minor mode behavior!")))

Note: This behavior assumes you have set up your key bindings the standard way a minor-mode would. Specifically, you should add your keymap to the variable minor-mode-map-alist by adding an element (my-minor-mode . my-minor-mode-keymap). That's how the above let statement works, it looks up the binding you want with your mode temporarily disabled.

If you use define-minor-mode to define your minor mode, the keymap gets set up the "right way" automatically.

Trey Jackson
Perfect, Thanks!
rejeep
+1  A: 

This is what I use for my smart-tab package which does exactly that.

(defun smart-tab-default ()
  "Indents region if mark is active, or current line otherwise."
  (interactive)
  (if mark-active
      (indent-region (region-beginning)
                     (region-end))

    (call-interactively
     (or
      ;; Minor mode maps for tab (without smart-tab-mode)
      (cdar (assq-delete-all 'smart-tab-mode (minor-mode-key-binding "\t")))
      (cdar (assq-delete-all 'smart-tab-mode (minor-mode-key-binding [(tab)])))
      (local-key-binding "\t")
      (local-key-binding [(tab)])
      (global-key-binding "\t")
      (global-key-binding [(tab)])))))

And in the command smart-tab (which is the one bound to tab in the minor mode), it has the following:

(if (smart-tab-must-expand prefix)
    ;; use smart tab
  (smart-tab-default))

It first checks if there are any minor mode bindings for tab (not including smart-tab-mode), then local, and finally global keybindings.

haxney