6

First, here is the full source code of my package.

Requirement

Currently I'm working on a package that provides a feature which upon the user invoking several times in a row certain command in a short amount of time, an alternative command is invoke instead. The assumption is made on the basis that the user has forgotten that the alternative command exist and that it is better/more efficient to achieve what he wants to achieve than the command he is using now.

Example use case

many invocations of next-line or previous-line through its bindings (either , C-n or , C-p), instead of letting the user keep invoking those two commands many more times, my package comes in and calls avy-goto-line (because that's how the user previously configured it to work).

The problem

I want to force the user to comply with whatever input a command request, right now if the user fall asleep on any binding for next-line, the alternative command goto-line never executes, or at least that's how it looks. If the alternative command were avy-goto-line it will throw an wrong-type-argument rendering my feature useless for next invocations if I don't ignore-errors.

I tried something like this:

(let* ((exit nil))
  (while (not exit)
    (setf exit (ignore-errors (avy-goto-line)))))

And it works, it does what I want to achieve. But for functions like goto-line or even avy-goto-word-0 Emacs gets freeze and the mini-buffer never popups asking something to the user.

the code

Here is the relevant parts of my code:

(setf schrute-shortcuts-commands '((goto-char  (left-char right-char))
                                   (goto-line  (next-line previous-line))))

(defun schrute--run-command ()
  "Helper that will run an alternative-command."
  (let* ((alternative-command)
         (command-list))
    (dolist (elem schrute-shortcuts-commands)
      (setf alternative-command (car elem))
      (setf command-list (cadr elem))
      (when (or (member this-command command-list)
               (eq this-command command-list))
        (funcall-interactively alternative-command)))))

(defun schrute-check-last-command ()
  "Check what command was used last time.

It also check the time between the last two invocations of the
same command and use the alternative command instead."
  (when (eq this-command last-command)
    (if (member this-command schrute--interesting-commands)
        (let* ((time-passed (float-time (time-subtract (current-time) schrute--time-last-command))))
          (if (<= time-passed 1.0)
              (setf schrute--times-last-command (1+ schrute--times-last-command)))
          (setf schrute--time-last-command (current-time)))))
  (when (> schrute--times-last-command 2)
    (setf schrute--times-last-command 0)
    ;; Call the alternative command for `this-command'
    (ignore-errors (schrute--run-command))))
  • You know the command for the single keystroke, you know the command for the key sequence, and you know the requirements on the user input for both commands. Just check the requirements for the key sequence in your functions. You could even write per-function advices or rebind the keys such that the wrong user-input is gobbled. If you want to keep your construction you could add that information in some form to schrute-shortcuts-commands. – Tobias Oct 18 '16 at 14:05
3
+100

The following is an easy way to achieve what you want. The problem seems to be that some commands like avy-goto-char leave unprocessed input around so you have to make sure to clear that if the command failed.

(defun schrute--call-until-success (cmd)
  (when (not (ignore-errors (call-interactively cmd) t))
    (discard-input)
    (schrute--call-until-success cmd)))

;; example usages
(schrute--call-until-success 'avy-goto-symbol-1)
(schrute--call-until-success 'avy-goto-line)
(schrute--call-until-success 'goto-line)

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.