Skip to content

Commit cdc609f

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 8ed4487 commit cdc609f

File tree

1 file changed

+55
-1
lines changed

1 file changed

+55
-1
lines changed

compat/terminal.c

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,37 @@ static int enable_non_canonical(void)
161161
return disable_bits(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
162162
}
163163

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

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

0 commit comments

Comments
 (0)