Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Escape as meta #1356

Closed
creack opened this issue Mar 23, 2014 · 37 comments
Closed

Support Escape as meta #1356

creack opened this issue Mar 23, 2014 · 37 comments
Assignees
Labels
enhancement release notes Something that is or should be mentioned in the release notes
Milestone

Comments

@creack
Copy link

creack commented Mar 23, 2014

I am using fish 2.1.0 and Escape is not considered meta.

This is really annoying, especially on OSX. You could configure iTerm to make 'alt' behave like meta, however, and I quote fish website,

configuration is source of all evil.

Bash, zsh, emacs, and many more support out of the box Escape as meta, which allow a proper usage without having to temper with the terminal configuration.

I understand why Fish is not POSIX, however, I don't see any argument why this should not be supported.

After discussing on IRC, it seems that it IS supported. However, it does not work on OSX with terminal, iTerm2 or with direct TTY within virtualbox.

Is it a bug? Is it expected? If yes, how could I work around it to make it work?

And please, do not tell me to configure my iTerm. I want to use Escape, not Alt.

@ridiculousfish
Copy link
Member

I'm was hoping someone else would chime in, so I wouldn't have to admit that I don't understand this at all :)

Can you please give a concrete example or steps to reproduce? What is something you type, what do you hope to see, what do you see instead?

@creack
Copy link
Author

creack commented Mar 26, 2014

@ridiculousfish start fish, write a word, then press <escape> then <backspace> or <escape> then <left arrow>
Expected result: remove word or move the cursor by a word
Actual result: single character remove/move

@KamilaBorowska
Copy link
Contributor

Oh, I understand what you wanted to say on IRC before. Well, yes, there is a special feature that analyzes the timing to let you bind Escape button, but this shows it's probably more trouble than it's worth. Perhaps it could be disabled if there is no binding for Escape button?

@creack
Copy link
Author

creack commented Mar 26, 2014

This feature does not work for me:
bind \e ls, press <escape>, it displays the ls output.

However, from default binding:
bind \et transpose-words, press <escape> then t
Expected result: transpose words
Actual result: displays t

try again with a binary instead of a builtin:
bind -e \et; and bind \et ls, press <escape> then t
Expected result: ls output
Actual result: displays t

@Debilski
Copy link

See also: #153
Interestingly enough, it works in Terminal.app when run inside a tmux session.

@creack
Copy link
Author

creack commented Mar 27, 2014

@Debilski Can you develop a bit more? I tried with or without tmux on Terminal.app as well and it does not work any better than iTerm2.

@Debilski
Copy link

$ tmux -V
tmux 1.9a
$ fish -v
fish, version 2.1.0

Basically, what I do is starting a tmux session (which defaults to having bash as a shell, as I’m still in the process of switching) and then run fish inside that. Then, I can press <escape>, p and fish will print |less;, for example. Works both in iTerm2 and Terminal.app. (I also tested with configuration for tmux and fish deactivated and it still worked.)

Still, I’d also rather have this functionality in plain fish without having to fire up tmux.

@creack
Copy link
Author

creack commented Mar 27, 2014

I see, I tried with an empty tmux configuration and indeed it works.

However, it should be working without tmux and hopefully with my configuration :p

I'll investigate which configuration exactly breaks this.

In the meantime, if you are curious, here is my tmux conf: https://github.com/creack/dotfiles/blob/master/dot.tmux.conf

@Debilski
Copy link

It’s set -s escape-time 0 which breaks it. :)

@creack
Copy link
Author

creack commented Mar 27, 2014

Thanks. It indeed work. However, this is still an issue for people not using tmux.
IMHO, we should not rely on timing to support this. <escape> should set a flag somewhere and affect the next event (keystroke or other).
This might cause the lost of the ability to bind <escape> by itself (bind \e ls). But I don't see any usecase for this. (but I am sure there are).
@xfix Suggested to do something like this in case \e is not bind to anything. A fallback to current behavior (but working everywhere this time) would also work.

@Debilski
Copy link

Any official news on this?

@jbulow
Copy link

jbulow commented Aug 20, 2014

I'm new to fish. Very annoying that ESC does not work. Is this not important?

@zanchey zanchey added this to the fish-future milestone Oct 5, 2014
@glehmann
Copy link

any update on this?

I just discovered fish, which looks really great, but without ESC as META, there is so much we can't do on mac!

Is there something that I can do to help to find what is going wrong?

@glehmann
Copy link

Sorry I forgot to say this is with fish master branch on Mac OS X 10.10.3. I can reproduce the behavior with and without tmux.

I've tried a bit to look at what is going on. I've found the input_readch function that mention the escape key in its doc, so I've done these small changes in it to log what is happening, run fish with the simple key sequence a-a-ESC-Backspace-Enter-^D. The results are indeed different with and without tmux. I have to say I don't fully understand the input process, and I'm not able to go further. Any help would be appreciated!

  • Without tmux, ESC seems to do nothing (only one a is deleted):
  input_common_readch(0): 97
  input_common_readch(0): 57378
  R_SELF_INSERT - input_common_readch(0): 97
97
  input_common_readch(0): 97
  input_common_readch(0): 57378
  R_SELF_INSERT - input_common_readch(0): 97
97
  input_common_readch(0): 27
  input_common_readch(0): 57401
  default
57401
  input_common_readch(0): 127
  input_common_readch(0): 57363
  default
57363
  input_common_readch(0): 10
  input_common_readch(0): 57386
  default
57386
  input_common_readch(0): 4
  input_common_readch(0): 57344
  default
57344

and with tmux, ESC is actually acting as the META key (the two a are deleted):

  input_common_readch(0): 97
  input_common_readch(0): 57378
  R_SELF_INSERT - input_common_readch(0): 97
97
  input_common_readch(0): 97
  input_common_readch(0): 57378
  R_SELF_INSERT - input_common_readch(0): 97
97
  input_common_readch(0): 27
  input_common_readch(0): 57374
  default
57374
  input_common_readch(0): 10
  input_common_readch(0): 57386
  default
57386
  input_common_readch(0): 4
  input_common_readch(0): 57344
  default
57344

The patch:

diff --git a/input.cpp b/input.cpp
index 02b5268..4286b76 100644
--- a/input.cpp
+++ b/input.cpp
@@ -3,7 +3,7 @@
     Functions for reading a character of input from stdin.

 */
-
+#include <fstream>
 #include "config.h"

 #include <stdlib.h>
@@ -722,7 +722,9 @@ static void input_mapping_execute_matching_or_generic(bool allow_commands)
     }
 }

-wint_t input_readch(bool allow_commands)
+std::ofstream out("/tmp/out.txt");
+
+wint_t input_readch2(bool allow_commands)
 {
     CHECK_BLOCK(R_NULL);

@@ -738,24 +740,30 @@ wint_t input_readch(bool allow_commands)
     while (1)
     {
         wchar_t c = input_common_readch(0);
-
+        out << "  input_common_readch(0): " << c << std::endl;
         if (c >= R_MIN && c <= R_MAX)
         {
             switch (c)
             {
                 case R_EOF: /* If it's closed, then just return */
                 {
+                    out << "  R_EOF " << std::endl;
                     return R_EOF;
                 }
                 case R_SELF_INSERT:
                 {
-                    return input_common_readch(0);
+                    wint_t c2 = input_common_readch(0);
+                    out << "  R_SELF_INSERT - input_common_readch(0): " << c2 << std::endl;
+                    return c2;
                 }
                 case R_AND:
                 {
+                    out << "  R_AND " << std::endl;
                     if (input_function_status)
                     {
-                        return input_readch();
+                        wint_t c2 = input_readch();
+                        out << "    input_readch(): " << c2 << std::endl;
+                        return c2;
                     }
                     else
                     {
@@ -766,6 +774,7 @@ wint_t input_readch(bool allow_commands)
                 }
                 default:
                 {
+                    out << "  default" << std::endl;
                     return c;
                 }
             }
@@ -781,6 +790,14 @@ wint_t input_readch(bool allow_commands)
     }
 }

+wint_t input_readch(bool allow_commands)
+{
+   wint_t c = input_readch2(allow_commands);
+   out << (int)c << std::endl;
+   return c;
+}
+
+
 std::vector<input_mapping_name_t> input_mapping_get_names()
 {
     // Sort the mappings by the user specification order, so we can return them in the same order that the user specified them in

@yannk
Copy link

yannk commented Oct 12, 2015

I was giving a shot to fish (and really liked it), and encountered the same show-stopper. Do we have an idea of where the problem is, and how big of a fix it is?

@creack
Copy link
Author

creack commented Oct 12, 2015

I just tried brew install fish and meta works fine now.
@yannk make sure you have the latest version (and if you do, try the latest stable instead: 2.2.0)

@creack creack closed this as completed Oct 12, 2015
@faho
Copy link
Member

faho commented Oct 12, 2015

@creack: Nope, this is not solved. There's probably something terminal-specific about it - I can't get it to work on konsole, urxvt and xterm.

@faho faho reopened this Oct 12, 2015
@yannk
Copy link

yannk commented Oct 12, 2015

Yeah, I tried Terminal.app and iTerm2 on El Capitan with the latest brew and a port build on freebsd. I threw tmux 2.0 into the mix too, then my hands in the air because it didn't work like I wanted.

@Debilski
Copy link

@yannk It may be some tmux configuration issue. With tmux 2.0 and Terminal.app on El Capitan it works for me.

Without tmux: No luck for me. But ESC will immediately delete a line when in history mode (eg. after pressing the up arrow a couple of times). Once I add characters to that line ESC resorts to doing nothing.

@faho
Copy link
Member

faho commented Oct 12, 2015

But ESC will immediately delete a line when in history mode

$ bind \e
bind \e cancel

It may be some tmux configuration issue

It's more general, I assure you.

@oranja
Copy link

oranja commented Dec 20, 2015

I think I am joining this request to support Esc as Meta.
Using Fedora23/Konsole/Byobu-tmux, Fish 2.2.0:

  1. Won't take Esc+X the same as Alt+X. For example, standing on the word "fish" and pressing Esc+u or Esc,u changes "fish" to "ufish", while Alt+u puts "FISH", as expected.
  2. Specifically, and I'm not sure if this is the same issue or something different, won't activate key-bindings when pressing Esc twice. Again, bind \e\e 'commandline -r surprise doesn't do anything with Esc,Esc or Esc+Esc, but works well with Alt+Esc.

* I tried these both with bind \e cancel set and without it.

  • I know that the setup sounds complicated, but bash has no problem with Esc,Esc. The binding works when pressing Esc twice.
  • Also, cat reports "^[^[" for pressing Esc twice, exactly the same as when pressing Alt+Esc.
  • Trying to alter the set -s escape-time 0 in tmux config doesn't seem to help at all.

Personally, I prefer Alt+Esc rather than Esc,Esc, but I've encountered people who wanted to make the transition from zsh to fish and turned on their heels.

@krader1961
Copy link
Contributor

There is a real problem. But identifying that problem and a solution has been hindered by a lot of confusing statements and bad conclusions. Let's start with the subject line of this issue "Support Escape as meta". You cannot use the [esc] key as a meta key. At least not without reconfiguring your terminal emulator to treat it as such which would be a pretty stupid thing to do. A meta key has to be held down while pressing a second key in order to modify the meaning of the second key. You cannot use the [esc] key in that fashion.

You can configure your terminal emulator (and many are configured this way by default) to cause the [alt], [apple], or [windows] key (which are meta keys) to cause the next key pressed to send its normal character sequence preceded by an \e (i.e., \c[, aka the escape character). So [alt-a] would transmit the sequence \ea. Another behavior that was frequently seen in the not too distant past is for the meta key to cause the character(s) sent by the modified key to have its high-bit set. For example, [alt-a] would result in \xE1 being sent (the value for lowercase "a" with bit 7 set). That, however, is less common these days now that UTF-8 encoding is in widespread use.

The reason that tmux seems to make a difference is that it alters the timing of the characters that pass through it. That's because tmux itself has to recognize multi-character sequences. You absolutely do not need to run tmux inside the Mac OS X standard Terminal.app to get the desired behavior. I can use tmux to produce the desired behavior inside iTerm2. But it is highly timing sensitive. If I wait too long after pressing [esc] before pressing the next character tmux does not bundle the characters tightly in the time domain and it fails to have a beneficial effect. On the other hand if I do type [esc] [some char] in quick succession then tmux forwards the two characters to fish with an even shorter interval between the chars.

If you configure iTerm2 to treat the left and right [alt] keys as a "+Esc" modifier that will cause iTerm2 to treat use of the [alt] key as requiring the sequence emitted by the modified key to be prepended by an \e character. If you do that you should find that works reliably (I did) at least if communicating with a local fish shell rather than one reached via ssh.

It's pretty obvious that fish is using a too short timeout after seeing an escape character (and possibly any other character) before deciding that no other relevant character is going to be received in its multi-char detection FSM (finite state machine).

@krader1961
Copy link
Contributor

And I see that input_common.cpp has these lines:

/**
   Time in milliseconds to wait for another byte to be available for
   reading after \\x1b is read before assuming that escape key was
   pressed, and not an escape sequence.
*/
#define WAIT_ON_ESCAPE 10

Ten milliseconds in an interactive shell is far too short. This should be an order of magnitude (i.e., 100 ms or 1/10th of a second) longer. To be determined is what other shells like bash use for this timeout.

@faho
Copy link
Member

faho commented Dec 21, 2015

Let's start with the subject line of this issue "Support Escape as meta". You cannot use the [esc] key as a meta key. At least not without reconfiguring your terminal emulator to treat it as such which would be a pretty stupid thing to do. A meta key has to be held down while pressing a second key in order to modify the meaning of the second key.

It's an emacs-ism. Emacs was invented on a Space-cadet keyboard, which had quite a few modifier keys, including meta. Nowadays meta is usually replaced by alt, but emacs retains the terminology. It also (by default - mine doesn't) reads escape as it if were meta, which means when emacs says to press "M-x", you can either hold down alt and press x or press escape then x. So in emacs-speak, escape is effectively meta (by default, but then again there isn't much in emacs that you can't change). (All this means that the old joke about "emacs" standing for "Escape Meta Alt Control Shift" is kinda pointless as the first three are the same thing)

Ten milliseconds in an interactive shell is far too short. This should be an order of magnitude (i.e., 100 ms or 1/10th of a second) longer.

I'm not sure this would be a great thing to do given we also have a vi-mode, where you want escape to immediately change to normal mode.

@oranja
Copy link

oranja commented Dec 21, 2015

I'm not sure this would be a great thing to do given we also have a vi-mode, where you want escape to immediately change to normal mode.

Couldn't the time interval be set in run-time, according to the current mode?

This old evidence suggests that vim uses a 25ms window:
http://en.chys.info/2009/09/esdelay-ncurses/
Maybe a small bump in WAIT_ON_ESCAPE could make a positive difference without negative implications for vi-mode?

@krader1961
Copy link
Contributor

A 25 ms window is way too short, @oranja. You cannot reliably press [esc] + some other key within that window. Also, that blog post does not show that Vim uses a 25 ms window. Only that when checking whether input is available on one of several file descriptors it waits at most 25 ms before doing other work. That is not the same thing as vim assuming a bare escape is present if 25 ms has gone by since an escape was read. The author jumped to a hasty conclusion.

I checked the readline source code and it uses a 500 ms default timeout for this purpose. From readline.c:

/* Timeout (specified in milliseconds) when reading characters making up an
   ambiguous multiple-key sequence */
int _rl_keyseq_timeout = 500;

It is configurable via the keyseq-timeout option that can appear in the .inputrc file.

... you want escape to immediately change to normal mode.

At normal human (i.e., non X-men) speeds a delay of 100 ms is immediate, @faho. Even 500 ms will be unnoticed by most people and is thus immediate for all intents and purposes. The default timeout should be much larger and I propose following readline's lead and setting it to 500 ms. The only question is whether it should be customizable via something like the ESCDELAY env var recognized by the curses library and/or parsing $HOME/.inputrc for relevant values like keyseq-timeout. I'm mildly opposed to either since it's not clear this really needs to be configurable and we should minimize our reliance on idioms set by other projects.

P.S., I built a custom fish with WAIT_ON_ESCAPE set to 500 and the bindings reported by others above work reliably in emacs mode. A couple of them (e.g., bind \e\e) don't work in vi mode but that is a different problem.

@krader1961
Copy link
Contributor

I'd like everyone who has commented on this issue to cherrypick the trivial change in 814cb51 and report on their experience.

@oranja
Copy link

oranja commented Dec 23, 2015

Thanks @krader1961
The change in timeout solves the issue for me!
Esc,Esc is a bit slow to respond, but works.
Other Esc,X sequences (e.g. Esc,u for uppercase token) work flawlessly.
Normal behavior, as far as I can tell, is not affected.

The only catch is that I'm not using vi-mode, so I can't speak in the name of those who do.

@glehmann
Copy link

Works great with 814cb51!
I can finally use Esc-. again!
Thanks!

@krader1961
Copy link
Contributor

FWIW, I personally prefer a timeout of 300 ms. But that's mostly because fish currently has a somewhat weird behavior of inheriting the default bindings while in vi-mode. Which means, for example, that \ed is a valid sequence in vi-mode. However, that short of an escape timeout could be a problem for some people unable to press consecutive keys that fast (as opposed to relying on real meta keys that convert the keystrokes to an escape sequence).

@krader1961
Copy link
Contributor

After several more days using a fish built with my change I think the escape timeout needs to be configurable. I find a 500 ms timeout is just a tad too long (as in annoying) when wanting to switch from vi insert to normal mode and I can reliably press escape plus some other char in ~200 ms. But there will be people who will need that long of a delay to reliably press escape followed by another character in a key binding. So I'd like to leave the default at 500 ms but allow the user to set a fish_escape_delay_ms var to the number of milliseconds they prefer be used (obviously with reasonable limits such as not less than 10 ms and not more than 1000 ms). Any objections?

@oranja
Copy link

oranja commented Jan 1, 2016

Perhaps setting the normal mode default to ~500ms and vi-mode default to ~250ms would please all newcomers? I believe something like this should give the great-out-of-the-box experience that Fish aims for. Together with an option for customization (fish_escape_delay_ms), it's probably as good as anyone could ask for.

@krader1961
Copy link
Contributor

@oranja I don't think there is enough value in a mode specific default to justify the added complexity. This basically boils down to fast versus slow typists. That is, if we want to support escape as a "meta key" then we have to allow a significant delay between the escape key and the next key stroke. The optimal delay depends on the individual. Some people can consistently press [escape] with very little delay before pressing some other key (e.g., less than 300 ms). Other people need at least a third of a second.

This wouldn't be as much of an issue if it weren't for the need to support "vi" mode.

@faho faho modified the milestones: next-2.x, next-major Feb 5, 2016
@faho faho added enhancement release notes Something that is or should be mentioned in the release notes labels Feb 5, 2016
@krader1961 krader1961 self-assigned this Apr 8, 2016
@nijotz
Copy link
Contributor

nijotz commented Jun 30, 2017

A value of 500 ms provides a nice experience even for people using "fish_vi_mode" bindings as a half second to switch from insert to normal mode is still fast enough that most people won't even notice.

Oh my.

@nijotz
Copy link
Contributor

nijotz commented Jun 30, 2017

Tried to figure out why escape had a very noticeable delay. There it is. So I suppose I have to custom compile to remove the delay?

@nijotz
Copy link
Contributor

nijotz commented Jun 30, 2017

Aha, found this for anyone else that comes across this issue: https://fishshell.com/docs/current/commands.html#special-case-escape

It can be configured by setting the fish_escape_delay_ms variable to a value between 10 and 5000 ms.

@krader1961
Copy link
Contributor

Note that we ultimately set the default to 300 ms:

#define WAIT_ON_ESCAPE_DEFAULT 300

If you set it too low (e.g., less than 10 ms) you may find it hard to reliably use any key which sends a sequence starting with escape (which is most of them).

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 17, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
enhancement release notes Something that is or should be mentioned in the release notes
Projects
None yet
Development

Successfully merging a pull request may close this issue.