5
votes

Context: I want to make a minor mode where pressing f twice fast results in whatever the pressing of ( should do at that time. This doesn't always mean just insertion of (. For example, in buffers where paredit mode or autopair mode is enabled, pressing of ( usually results in insertion of (). In a paredit mode buffer, that sometimes results in wrapping the selected text: for example, if I select a b and press (, that should result in replacing the selection with (a b).

For detection of f being pressed twice, I just need to take the logic in the short code in http://www.emacswiki.org/emacs/electric-dot-and-dash.el

So the only missing piece is a Lisp code snippet that tells Emacs "Trigger pressing of ( now!"

The first thing that came to my mind was that the snippet should do

  1. find the command bound to the key (
  2. and then call call-interactively on that command.

but that breaks down if the auto pairing package (autopair or paredit or other similar package) binds ( to a command that has a logic that looks up what key was used to call the command, or if the package simply relies on post-self-insert-hook or post-command-hook instead of binding (.


update

I've looked up Key Chord documentation and it turns out what I am trying to do with answers to this question has a simpler solution:

(require 'key-chord)
(key-chord-mode 1)

(defvar my-easy-open-paren-mode-map
  (let ((map (make-sparse-keymap)))
    (key-chord-define map ",." (kbd "("))
    map))
(define-minor-mode my-easy-open-paren-mode
  "In this mode, pressing . and , together is another way of pressing the open paren.")

(defvar my-easy-semicolon-mode-map
  (let ((map (make-sparse-keymap)))
    (key-chord-define map ";;" (kbd "C-e ;"))
    map))
(define-minor-mode my-easy-semicolon-mode
  "In this mode, pressing semicolon twice fast is another way of pressing C-e and semicolon.")

(add-hook 'prog-mode-hook 'my-easy-open-paren-mode)

(add-hook 'c-mode-common-hook 'my-easy-semicolon-mode)

Triggering key press may still be useful in other contexts though.

2
Aren't you complicating the situation a bit? Won't a key binding that inserts a pair of parens everywhere suffice? I've had a pair of parens on ;-f for years now and haven't touched S-9 once.abo-abo
@abo-abo, for this particular case, re-implementing the autopair feature for ( of auto pairing and wrapping may be simpler. A more general case may involve making some key chord do whatever ( does, and then making another key chord do whatever ) does, and maybe some other things as well, as a minor mode which can then be enabled in only prog mode buffers.Jisang Yoo
IMO, such things as text insertion shouldn't be fiddled with too much. For instance, I stopped using autopair since it was throwing errors while being used with slime. And as @phils mentioned, binding ff with key-chord will cause you a world of annoyance.abo-abo

2 Answers

6
votes

You might appreciate the Key Chord library for binding functions to a double key-press. (I wouldn't recommend using f if you'll be writing in English, mind you; but YMMV.)

post-self-insert-hook would still run if the binding was self-insert-command. post-command-hook will run in any case, but if you're worried about it seeing an incorrect function and/or input event, you can manipulate those...

After looking up the binding, your function can set this-command to the function you're about to call-interactively, and last-command-event to the required key. e.g.:

(defun my-fake-paren ()
  (interactive)
  (let ((command (key-binding "(")))
    (setq last-command-event ?\()
    (setq this-command command)
    (call-interactively command)))
1
votes

I use Key Chord for this sort of thing, although the page you link appears to do the same thing. The trick is getting the call to call-interactively to work correctly. I wrapped it in a let that reset the variable last-command-event, such that call-interactively thinks it was a "(". This works for me in paredit and fundamental modes.

(require 'key-chord)
(key-chord-mode 1)

(defun my-paren-call ()
  (interactive)
  (let ((last-command-event ?\())
    (call-interactively (key-binding "("))))

(key-chord-define-global "ff" 'my-paren-call)