Comment puis-je simuler un événement clé arbitraire depuis Elisp?

26

Est-il possible de simuler un événement clé arbitraire depuis elisp? Je connais les moyens de trouver la liaison pour une clé donnée, puis d'appeler cette commande de manière interactive, mais que se passe-t-il si cet événement clé n'est pas lié à une commande?

À titre d' exemple , que se passe-t-il si je souhaite lier C-`pour se comporter de la même manière que la ESCclé dans tous les contextes ?

nispio
la source
Il semble que ce key-bindingssoit la mauvaise balise si vous n'essayez pas d'alias une liaison de clé. De plus, vous devriez peut-être changer votre exemple en quelque chose d'autre afin qu'il ne soit pas confondu.
b4hand
@ b4hand Je suis ouvert aux suggestions de meilleures balises. Il n'y a pas de key-eventsbalise. Dois-je en faire un?
nispio
Cela me semble raisonnable, mais les événements pourraient être meilleurs, car cela pourrait également s'appliquer aux événements de souris.
b4hand
2
Je ne sais toujours pas si vous voulez simuler un événement clé dans elisp, ou si vous voulez spécifiquement la possibilité de faire agir une clé comme s'il s'agissait d'une autre clé? Les goûts de key-translation-mapfaciliter ce dernier, donc si c'est tout ce que vous voulez, je suggère de l'utiliser plutôt que de faire quelque chose de plus manuel.
phils
... et si la traduction clé est vraiment ce que vous voulez ici, je pense que c'est une question différente , et que vous devriez la poser séparément; puis reformulez votre exemple pour que cette question soit plus appropriée au problème plus général de "comment puis-je simuler un événement clé dans elisp?"
phils

Réponses:

24

Vous pouvez envoyer des événements arbitraires (frappes, clics de souris, etc.) à la boucle de commande en les plaçant dessus unread-command-events. Par exemple, ce qui suit entraînera la boucle de commande pour exécuter une pause lors de sa prochaine exécution:

(setq unread-command-events (listify-key-sequence "\C-g"))

Notez que cela ne fait que nourrir les événements dans la boucle de commande, donc cela ne fera rien d'intéressant si vous bouclez dans votre propre code.

Une approche différente, dont vous semblez être au courant, consiste à trouver la fonction à laquelle une touche donnée est liée et à l'exécuter vous-même:

(funcall (global-key-binding "\C-g"))

Cela exécutera la commande immédiatement. Attention, cependant, certaines commandes ont un comportement différent selon qu'elles sont appelées de manière interactive, comme les arguments par défaut. Vous voudrez compenser cela en utilisant call-interactively:

(call-interactively (global-key-binding "\C-g"))
jch
la source
J'ai lu unread-command-eventsmais je n'ai pas réussi à comprendre comment l'utiliser. Le régler n'a eu aucun effet pour moi. Y a-t-il un bon exemple de la façon dont il est utilisé?
nispio
Je l'ai vu utilisé en demandant à l'utilisateur d'appuyer sur espace pour continuer - si l'utilisateur appuie sur autre chose, il passe unread-command-events.
jch
@nispio: unread-command-eventsc'est exactement ce que son nom l'indique. Vous pouvez examiner un événement, puis, en fonction de ce qu'il est, le repousser conditionnellement u-c-epour qu'il soit ensuite traité normalement. Il existe de nombreux exemples de son utilisation dans le code source d'Emacs - grepest votre ami.
Tiré
1
J'ai pu me mettre unread-command-eventsau travail. La pièce qui me manquait avant était la listify-key-sequencefonction. Je venais d'utiliser le vecteur clé brut.
nispio
1
Merci pour cette réponse. Je voulais implémenter des tests non interactifs de mon système d'achèvement, j'ai donc utilisé cette idée pour implémenter une with-simulated-inputmacro qui évalue n'importe quelle expression avec unread-command-eventslet-bound à une séquence de touches spécifiée: github.com/DarwinAwardWinner/ido-ubiquitous/blob/…
Ryan C. Thompson
8

La façon la plus simple que je connaisse est simplement d'utiliser execute-kbd-macro:

(defun foo () (interactive) (execute-kbd-macro (kbd "<escape>")))
(global-set-key (kbd "C-`") 'foo)
shosti
la source
Évaluer ce qui précède puis appuyer sur C-` me donne une erreur apply: Wrong number of arguments: #[(ad--addoit-function ....
nispio
1
@nispio Pas pour moi. Cette erreur ressemble à un conseil.
Malabarba
@Malabarba Je pense que vous avez raison. Après un nouveau démarrage, emacs -Qcette erreur n'est pas présente. Je reçois toujours cette erreur cependant:After 0 kbd macro iterations: foo: Lisp nesting exceeds `max-lisp-eval-depth'
nispio
C'est en fait ce que je cherchais. Pour une raison étrange (probablement quelques détails d'interaction avec evil), appeler directement la fonction souhaitée a eu un effet inattendu dans mon cas ( evilmi-jump-items), et j'ai dû utiliser(execute-kbd-macro (kbd "%"))
xji
4

Tiré de cette réponse , vous pouvez utiliser global-set-key comme ceci

(global-set-key (kbd "C-`") (kbd "<escape>"))

Qui traitera C-`commeescape

Cela semble avoir quelques problèmes si la deuxième combinaison n'exécute pas de fonction. Donc, si escapeest utilisé comme Meta, alors cela ne fonctionne pas correctement. Mais cela semble fonctionner pour les commandes liées aux fonctions.

resueman
la source
@nispio: En fait, cela fonctionne, car le deuxième argument est implicitement converti en macro clavier.
shosti
1
@shosti L' évaluation ci - dessus puis en appuyant sur C-` me donne une erreur: After 0 kbd macro iterations: command-execute: Lisp nesting exceeds `max-lisp-eval-depth'.
nispio
@nispio: Vous avez probablement déjà C-`lié à ESCune autre méthode, donc ça va dans une boucle infinie.
shosti
@shosti Vous aviez raison. Trop de eval-sexpchoses se passent en une seule session. :-) Mais réessayer avec des emacs -Qcauses C-` pour ne rien faire.
nispio
En fonction de votre système (kbd "<escape>")et (kbd "ESC")peut signifier différentes choses - avez-vous essayé les deux?
shosti
2

Après avoir lu la suggestion de jch à utiliser unread-command-events, j'ai pu pirater ensemble une solution qui fera certaines des choses que je recherche.

(defun my-simulate-key-event (event &optional N)
  "Simulate an arbitrary keypress event.

This function sets the `unread-command-events' variable in order to simulate a
series of key events given by EVENT. Can also For negative N, simulate the
specified key EVENT directly.  For positive N, removes the last N elements from
the list of key events in `this-command-keys' and then appends EVENT.  For N nil,
treat as N=1."
  (let ((prefix (listify-key-sequence (this-command-keys)))
         (key (listify-key-sequence event))
         (n (prefix-numeric-value N)))
     (if (< n 0)
         (setq prefix key)
       (nbutlast prefix n)
       (nconc prefix key))
       (setq unread-command-events prefix)))

Il y a encore un certain nombre de problèmes à résoudre. À savoir, je n'obtiens pas le résultat correct si j'appelle cette fonction deux fois de suite dans un seul defun.


Note latérale:

Après avoir vérifié la suggestion d'utilisation de phils,key-translation-map j'ai pu trouver local-function-key-mapce qui est également très utile pour atteindre certains de mes objectifs plus larges.

nispio
la source