summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohannes Schindelin <johannes.schindelin@gmx.de>2020-01-14 18:43:51 (GMT)
committerJunio C Hamano <gitster@pobox.com>2020-01-15 20:06:17 (GMT)
commite118f06396bb298f2852070f648c6b4bb221a925 (patch)
treef63d338e86ac29a911e0447b5f46d08dba69062c
parent04f816b125dc2649e70aad686d79b05bdc1d1c61 (diff)
downloadgit-e118f06396bb298f2852070f648c6b4bb221a925.zip
git-e118f06396bb298f2852070f648c6b4bb221a925.tar.gz
git-e118f06396bb298f2852070f648c6b4bb221a925.tar.bz2
built-in add -p: handle Escape sequences in interactive.singlekey mode
This recapitulates part of b5cc003253c8 (add -i: ignore terminal escape sequences, 2011-05-17): add -i: ignore terminal escape sequences On the author's terminal, the up-arrow input sequence is ^[[A, and thus fat-fingering an up-arrow into 'git checkout -p' is quite dangerous: git-add--interactive.perl will ignore the ^[ and [ characters and happily treat A as "discard everything". As a band-aid fix, use Term::Cap to get all terminal capabilities. Then use the heuristic that any capability value that starts with ^[ (i.e., \e in perl) must be a key input sequence. Finally, given an input that starts with ^[, read more characters until we have read a full escape sequence, then return that to the caller. We use a timeout of 0.5 seconds on the subsequent reads to avoid getting stuck if the user actually input a lone ^[. Since none of the currently recognized keys start with ^[, the net result is that the sequence as a whole will be ignored and the help displayed. Note that we leave part for later which uses "Term::Cap to get all terminal capabilities", for several reasons: 1. it is actually not really necessary, as the timeout of 0.5 seconds should be plenty sufficient to catch Escape sequences, 2. it is cleaner to keep the change to special-case Escape sequences separate from the change that reads all terminal capabilities to speed things up, and 3. in practice, relying on the terminal capabilities is a bit overrated, as the information could be incomplete, or plain wrong. For example, in this developer's tmux sessions, the terminal capabilities claim that the "cursor up" sequence is ^[M, but the actual sequence produced by the "cursor up" key is ^[[A. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>
-rw-r--r--compat/terminal.c56
1 files changed, 55 insertions, 1 deletions
diff --git a/compat/terminal.c b/compat/terminal.c
index 1b25640..b7f58d1 100644
--- a/compat/terminal.c
+++ b/compat/terminal.c
@@ -161,6 +161,37 @@ static int enable_non_canonical(void)
return disable_bits(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
}
+/*
+ * Override `getchar()`, as the default implementation does not use
+ * `ReadFile()`.
+ *
+ * This poses a problem when we want to see whether the standard
+ * input has more characters, as the default of Git for Windows is to start the
+ * Bash in a MinTTY, which uses a named pipe to emulate a pty, in which case
+ * our `poll()` emulation calls `PeekNamedPipe()`, which seems to require
+ * `ReadFile()` to be called first to work properly (it only reports 0
+ * available bytes, otherwise).
+ *
+ * So let's just override `getchar()` with a version backed by `ReadFile()` and
+ * go our merry ways from here.
+ */
+static int mingw_getchar(void)
+{
+ DWORD read = 0;
+ unsigned char ch;
+
+ if (!ReadFile(GetStdHandle(STD_INPUT_HANDLE), &ch, 1, &read, NULL))
+ return EOF;
+
+ if (!read) {
+ error("Unexpected 0 read");
+ return EOF;
+ }
+
+ return ch;
+}
+#define getchar mingw_getchar
+
#endif
#ifndef FORCE_TEXT
@@ -228,8 +259,31 @@ int read_key_without_echo(struct strbuf *buf)
restore_term();
return EOF;
}
-
strbuf_addch(buf, ch);
+
+ if (ch == '\033' /* ESC */) {
+ /*
+ * We are most likely looking at an Escape sequence. Let's try
+ * to read more bytes, waiting at most half a second, assuming
+ * that the sequence is complete if we did not receive any byte
+ * within that time.
+ *
+ * Start by replacing the Escape byte with ^[ */
+ strbuf_splice(buf, buf->len - 1, 1, "^[", 2);
+
+ for (;;) {
+ struct pollfd pfd = { .fd = 0, .events = POLLIN };
+
+ if (poll(&pfd, 1, 500) < 1)
+ break;
+
+ ch = getchar();
+ if (ch == EOF)
+ return 0;
+ strbuf_addch(buf, ch);
+ }
+ }
+
restore_term();
return 0;
}