]> git.ipfire.org Git - thirdparty/git.git/commitdiff
built-in add -p: handle Escape sequences in interactive.singlekey mode
authorJohannes Schindelin <johannes.schindelin@gmx.de>
Tue, 14 Jan 2020 18:43:51 +0000 (18:43 +0000)
committerJunio C Hamano <gitster@pobox.com>
Wed, 15 Jan 2020 20:06:17 +0000 (12:06 -0800)
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>
compat/terminal.c

index 1b2564042ac60c1f2a2e6b7f7ebf85b992b59a3e..b7f58d1781e0cbd6da3d35bed6692ada9ca5c718 100644 (file)
@@ -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;
 }