1

I want to generate some input from my command programmatically. I have tried using unread-command-events variable for that, see:

(defun unread_test ()
  "Repeat input after toggling input method"
  (interactive)
  (let ((kseq (listify-key-sequence "Hello world!")))
    (nconc unread-command-events kseq)))

However invocation of command does nothing.

How can i achieve my goal? Should i take another approach?

  • 2
    What should invoking unread-test (elisp uses lisp-case) do? How / where is it called? The value of unread-command-events is often nil, so passing it to nconc will not modify it. For programmatically entering the input of an interactive command, unread-command-events is usually set right before calling said interactive command. – Basil Oct 17 '17 at 12:56
  • Type out given keycodes (according to current input method) – Srv19 Oct 17 '17 at 13:02
  • 1
    So you want to write a command which inserts some predetermined string ("Hello World!" in your example) into the current buffer whilst obeying the current input method? If so, I suggest editing your title, description and tags accordingly, because the solution may or may not be related to unread-command-events. – Basil Oct 17 '17 at 13:05
  • I suggest you write your comment as an answer, because it resolves issue i have asked about. I reserve right to ask about the correctness of the method i am currently trying to use when solving my tasks and issues – Srv19 Oct 17 '17 at 13:43
  • I never said you don't have the right to ask about the correctness of your current approach. What I said is your question's title, description and tags may not convey clearly what your goal is and what you want to see in an answer, if my interpretation of your comment is correct. Can you please answer my previous comment's question re: what you would like your custom command to do? – Basil Oct 17 '17 at 13:47
2

How can i achieve my goal? Should i take another approach?

IIUC the problem arises from a misuse of nconc. The value of unread-command-events is often nil, in which case nconc will not alter it in-place, as there is no cdr to modify. Instead, you should probably be using push or setq to record the value change of unread-command-events.

invocation of command does nothing

If unread-test is rewritten as

(defun unread-test ()
  "Repeat input after toggling input method."
  (interactive)
  (setq unread-command-events
        (nconc (listify-key-sequence "Hello, World!") unread-command-events)))

then invoking it will cause the characters corresponding to Hello, World! to be inserted into the current (writable) buffer.


From OP's comment:

I would like my command to allow me to fix strings that were typed with wrong input method. See http://paste.lisp.org/+7OXT for example

Here is a listing of the linked example, for posterity:

(defun krv/punto (p m)
  "Repeat input after toggling input method"
  (interactive (if (use-region-p)
                   (list (region-beginning) (region-end))
                 nil))
  (if (use-region-p)
      (let* ((region-string (buffer-substring p m))
             (old-input-method current-input-method)
             (input-keys
              (if (not old-input-method)
                  ;; if there is no current input method, just take character
                  (listify-key-sequence region-string)
                ;; we have imput method, try to translate characters back
                (cl-mapcan
                 (lambda (ch)
                   (let ((quail-list (quail-find-key ch)))
                     (if (equal quail-list t)
                         (list ch)
                       (cl-mapcan (lambda (str) (listify-key-sequence str))
                                  quail-list))))
                 region-string))))
        (delete-region p m)
        (toggle-input-method)
        (setq unread-command-events (nconc unread-command-events input-keys)))
    (call-interactively 'toggle-input-method)))

This example behaved as intended for me after only a little bit of tweaking. Specifically, having mandatory arguments means the interactive spec cannot just return nil; either it must return '(nil nil) or the arguments must be optional. The latter option makes more sense to me in this case, as the function changes its behaviour subject to whether the region is active.

Below is my attempt at correcting this hiccup and simplifying the code overall:

(defun krv/punto (&optional beg end)
  "Toggle input method and repeat any input in region."
  (interactive (and (use-region-p)
                    (list (region-beginning) (region-end))))
  (let ((events (and beg end
                     (mapcan (lambda (char)
                               (let ((keys (quail-find-key char)))
                                 (if (consp keys)
                                     (mapcan #'listify-key-sequence keys)
                                   (list char))))
                             (delete-and-extract-region beg end)))))
    (toggle-input-method)
    (setq unread-command-events (nconc events unread-command-events))))

Note:

  • This follows the suggestion in (elisp) Event Input Misc of prepending, instead of appending, to unread-command-events.
  • If you are not yet running Emacs 25 you can use cl-mapcan in place of mapcan as you did before.
  • You can invert the innermost if form as follows, if you prefer:

    (if (booleanp keys)
        (list char)
      (mapcan #'listify-key-sequence keys))
    

What's interesting is that Emacs includes the package robin.el, which is a newer and simpified version of quail.el supporting precisely your intended behaviour - translating buffer regions back and forth between input methods. The package implementation can afford to be very simple because it only strives to achieve a subset of its predecessor's functionality. In particular, it forgoes CJK character support. I think it's a (understandable) shame that barely any input methods use it.

  • Thank you for your answer! I find your version of the command both more concise and more lisp-y then one before – Srv19 Oct 19 '17 at 11:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.