Skip to content

Commit 8cf5fd6

Browse files
committed
built-in add -p: handle Escape sequences in interactive.singlekey mode
This recapitulates part of b5cc003 (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 <[email protected]>
1 parent 597ac69 commit 8cf5fd6

File tree

1 file changed

+56
-1
lines changed

1 file changed

+56
-1
lines changed

compat/terminal.c

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "strbuf.h"
55
#include "run-command.h"
66
#include "string-list.h"
7+
#include "argv-array.h"
78

89
#if defined(HAVE_DEV_TTY) || defined(GIT_WINDOWS_NATIVE)
910

@@ -161,6 +162,37 @@ static int enable_non_canonical(void)
161162
return disable_bits(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
162163
}
163164

165+
/*
166+
* Override `getchar()`, as the default implementation does not use
167+
* `ReadFile()`.
168+
*
169+
* This poses a problem when we want to see whether the standard
170+
* input has more characters, as the default of Git for Windows is to start the
171+
* Bash in a MinTTY, which uses a named pipe to emulate a pty, in which case
172+
* our `poll()` emulation calls `PeekNamedPipe()`, which seems to require
173+
* `ReadFile()` to be called first to work properly (it only reports 0
174+
* available bytes, otherwise).
175+
*
176+
* So let's just override `getchar()` with a version backed by `ReadFile()` and
177+
* go our merry ways from here.
178+
*/
179+
static int mingw_getchar(void)
180+
{
181+
DWORD read = 0;
182+
unsigned char ch;
183+
184+
if (!ReadFile(GetStdHandle(STD_INPUT_HANDLE), &ch, 1, &read, NULL))
185+
return EOF;
186+
187+
if (!read) {
188+
error("Unexpected 0 read");
189+
return EOF;
190+
}
191+
192+
return ch;
193+
}
194+
#define getchar mingw_getchar
195+
164196
#endif
165197

166198
#ifndef FORCE_TEXT
@@ -228,8 +260,31 @@ int read_key_without_echo(struct strbuf *buf)
228260
restore_term();
229261
return EOF;
230262
}
231-
232263
strbuf_addch(buf, ch);
264+
265+
if (ch == '\033' /* ESC */) {
266+
/*
267+
* We are most likely looking at an Escape sequence. Let's try
268+
* to read more bytes, waiting at most half a second, assuming
269+
* that the sequence is complete if we did not receive any byte
270+
* within that time.
271+
*
272+
* Start by replacing the Escape byte with ^[ */
273+
strbuf_splice(buf, buf->len - 1, 1, "^[", 2);
274+
275+
for (;;) {
276+
struct pollfd pfd = { .fd = 0, .events = POLLIN };
277+
278+
if (poll(&pfd, 1, 500) < 1)
279+
break;
280+
281+
ch = getchar();
282+
if (ch == EOF)
283+
return 0;
284+
strbuf_addch(buf, ch);
285+
}
286+
}
287+
233288
restore_term();
234289
return 0;
235290
}

0 commit comments

Comments
 (0)