2
votes

I always had this strange problem with Emacs that I really want to find a solution but unable to find any. By default key map ESC ESC ESC (three escapes) are mapped to keyboard-escape-quit, which works fine but, if I press ESC only two types and then press an arrow key, it inserts special characters into my text that I always have to redo of delete to get rid of. In other words I get this behavior:

ESC ESC up -> OA

ESC ESC down -> OB

ESC ESC right -> OC

ESC ESC left -> OD

When I use \C-H K to find which functions these keys have been mapped to it shows me ESC ESC ESC which is mapped to keyboad-escape-quit.

Does anyone have any solution how to get rid of this annoying key-binding? Note that I use Emacs in terminal.

Thank you

2

2 Answers

2
votes

The arrow keys generate escape sequences. E.g., if you start cat and hit the up arrow, you will see on your screen something like this:

$ cat
^[[A

i.e., escape, open bracket, A (it will be different depending on the terminal).

This means that if you hit ESC ESC up, Emacs sees ESC ESC ESC [ A and reacts accordingly (keyboard quit, then insert [A).

So, the Emacs behaves as per the docs.

If you want to disable the key binding, you can do

(define-key esc-map (kbd "<ESC><ESC>") nil)

This is not a very good solution, IMO, but not a disaster since you can always use C-g instead.

1
votes

Sam has described the problem, but as for a general solution, you basically need to teach Emacs to distinguish the ESC received in response to you hitting the "escape key" from the ESC received as part of an "escape sequence". Of course, in general it can't be done, but in practice, you can check the timing: if ESC is followed by a bit of idle time, then it's probably an "escape key", and otherwise it's probably part of an "escape sequence".

This trick is used in VI emulators such as Viper or Evil:

(defvar viper-fast-keyseq-timeout 200)

(defun viper--tty-ESC-filter (map)
  (if (and (equal (this-single-command-keys) [?\e])
           (sit-for (/ viper-fast-keyseq-timeout 1000.0)))
      [escape] map))

(defun viper--lookup-key (map key)
  (catch 'found
    (map-keymap (lambda (k b) (if (equal key k) (throw 'found b))) map)))

(defun viper-catch-tty-ESC ()
  "Setup key mappings of current terminal to turn a tty's ESC into `escape'."
  (when (memq (terminal-live-p (frame-terminal)) '(t pc))
    (let ((esc-binding (viper--lookup-key input-decode-map ?\e)))
      (define-key input-decode-map
        [?\e] `(menu-item "" ,esc-binding :filter viper--tty-ESC-filter)))))

If you call viper-catch-tty-ESC, it will setup the decoding such that hitting the escape key should now generate an escape event (instead of an ESC event). This will automatically be mapped back to ESC if there is no binding for escape, thanks to a binding in function-key-map (this is used in GUI mode where the escape key indeed sends to escape event).

Note that this will not fix your problem: "ESC ESC up" will still insert "OA". The problem there is that Emacs's keyboard translation will still see "ESC ESC ESC O A" (tho the first two appeared in a round-about way going through escape and back). So to finally fix the problem, you additionally need to remove the "ESC ESC ESC" binding and replace it with a binding that will only be triggered with the new escape event:

(global-unset-key [?\e ?\e ?\e])
(global-set-key [?\e ?\e escape] 'keyboard-escape-quit)

Note: This is all tricky business. I'm intimately familiar with the corresponding code, yet my first two attempts while writing this answer failed because of some interaction I did not anticipate.