Como posso simular um evento de chave arbitrária do Elisp?

26

É possível simular um evento-chave arbitrário do elisp? Estou ciente das maneiras pelas quais posso encontrar a ligação para uma determinada chave e, em seguida, chamar esse comando interativamente, mas e se esse evento de chave não estiver vinculado a um comando?

Como exemplo , e se eu quisesse ligar C-`para me comportar da mesma forma que a ESCchave em todos os contextos ?

nispio
fonte
Parece que key-bindingsé a tag errada se você não está tentando criar um atalho de chave. Além disso, talvez você deva mudar seu exemplo para outra coisa para que não fique confuso.
b4hand
@ b4hand Estou aberto a sugestões para melhores tags. Não há key-eventsetiqueta. Devo fazer um?
Nispio 22/10
me parece razoável, mas os eventos podem ser melhores, pois isso também pode ser aplicável aos eventos do mouse.
b4hand
2
Ainda estou confuso sobre se você deseja simular um evento-chave no elisp ou se deseja especificamente a capacidade de fazer uma chave agir como se fosse outra chave? Os tipos key-translation-mapfacilitam o último; portanto, se é tudo o que você deseja, sugiro usá-lo em vez de fazer algo mais manual.
phils
... e se a tradução de chaves é realmente o que você deseja aqui, acho que é uma pergunta diferente , e que você deve fazer isso separadamente; e, em seguida, redigite seu exemplo para que essa pergunta seja mais apropriada ao problema mais geral de "como simular um evento-chave no elisp?"
phils

Respostas:

24

Você pode alimentar eventos arbitrários (pressionamentos de tecla, cliques do mouse etc.) no loop de comando, colocando-os em unread-command-events. Por exemplo, o seguinte fará com que o loop de comando execute uma interrupção na próxima vez em que for executado:

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

Observe que isso apenas alimenta eventos para o loop de comando, portanto, não fará nada de interessante se você estiver repetindo seu próprio código.

Uma abordagem diferente, da qual você parece estar ciente, é encontrar a função à qual uma determinada chave está vinculada e executá-la:

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

Isso executará o comando imediatamente. Cuidado, no entanto, que alguns comandos têm um comportamento diferente, dependendo de serem chamados interativamente, como argumentos padrão. Você deseja compensar isso usando call-interactively:

(call-interactively (global-key-binding "\C-g"))
jch
fonte
Eu li sobre, unread-command-eventsmas não consegui descobrir como usá-lo. Defini-lo não teve efeito para mim. Existe algum bom exemplo de como é usado?
Nispio 23/10/2014
Eu já o vi usado ao pedir ao usuário que pressione espaço para continuar - se o usuário pressionar qualquer outra coisa, ele continuará unread-command-events.
JCH
@nispio: unread-command-eventsé exatamente o que o nome diz. Você pode examinar um evento e, dependendo do que é, enviar de volta condicionalmente u-c-epara que ele seja processado normalmente. Existem muitos exemplos de seu uso no código fonte do Emacs - grepé seu amigo.
Drew
11
Eu fui capaz unread-command-eventsde trabalhar. A peça que faltava antes era a listify-key-sequencefunção. Eu estava usando o vetor de chave bruta.
Nispio 23/10/14
11
Obrigado por esta resposta. Como eu queria implementar testes não interativos do meu sistema de conclusão, usei essa ideia para implementar uma with-simulated-inputmacro que avalia qualquer expressão com unread-command-eventslet-bound para uma sequência de teclas especificada: github.com/DarwinAwardWinner/ido-ubiquitous/blob/…
Ryan C. Thompson
8

A maneira mais simples que conheço é apenas usar execute-kbd-macro:

(defun foo () (interactive) (execute-kbd-macro (kbd "<escape>")))
(global-set-key (kbd "C-`") 'foo)
shosti
fonte
Avaliando o que foi dito acima e pressionando, C-` ocorre um erro apply: Wrong number of arguments: #[(ad--addoit-function ....
Nispio 23/10/14
11
@nispio Não é para mim. Esse erro parece um conselho.
Malabarba 23/10
@ Malabarba Eu acho que você está certo. Depois de começar de novo com emacs -Qesse erro não está presente. No entanto, ainda recebo este erro:After 0 kbd macro iterations: foo: Lisp nesting exceeds `max-lisp-eval-depth'
nispio 23/10
Na verdade, era isso que eu estava procurando. Por alguma estranha razão (provavelmente alguns detalhes de interação com evil), chamando diretamente a função desejada teve um efeito inesperado no meu caso ( evilmi-jump-items), e eu tive que usar(execute-kbd-macro (kbd "%"))
XJI
4

Retirado desta resposta , você pode usar global-set-key como este

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

Que tratará C-`comoescape

Isso parece ter alguns problemas, se a segunda combinação não executar uma função. Portanto, se escapeestiver sendo usado como Meta, então não funcionará corretamente. Mas parece funcionar para comandos vinculados a funções.

resueman
fonte
@nispio: Na verdade, ele funciona, pois o segundo argumento é implicitamente convertido em uma macro do teclado.
Shosti
11
@shosti Avaliando o exposto, e em seguida, pressionando C-` me dá um erro: After 0 kbd macro iterations: command-execute: Lisp nesting exceeds `max-lisp-eval-depth'.
Nispio 23/10/14
@nispio: Você provavelmente já deve ter C-`vinculado ESCpor algum outro método, então ele entra em um loop infinito.
Shosti
@shosti Você estava certo. Muitos eval-sexpacontecendo em uma sessão. :-) Mas tentar novamente com emacs -Qcausas C-` simplesmente não faz nada.
Nispio 23/10/14
Dependendo do seu sistema, (kbd "<escape>")e (kbd "ESC")pode significar coisas diferentes - você já tentou os dois?
Shosti
2

Depois de ler a sugestão de jch para usar unread-command-events, pude criar uma solução que fizesse algumas das coisas que eu estava procurando.

(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)))

Ainda há uma série de dobras para resolver. Ou seja, não obtenho o resultado correto se eu chamar essa função duas vezes seguidas em uma única defun.


Nota:

Depois de verificar a sugestão de uso do phils,key-translation-map consegui descobrir o local-function-key-mapque também é muito útil para alcançar alguns dos meus objetivos mais amplos.

nispio
fonte