@matheusbsbs / matheus-bagliano / commits / e118f06396
Fork 0

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>

Johannes Schindelin committed Jan 14, 2020 at 18:43 UTC e118f06396bb298f2852070f648c6b4bb221a925
1 file changed +55 -1
compat/terminal.c
+55 -1
@@ -161,6 +161,37 @@ static int enable_non_canonical(void)
161 return disable_bits(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
162 }
163
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 +
195 #endif
196
197 #ifndef FORCE_TEXT
@@ -228,8 +259,31 @@ int read_key_without_echo(struct strbuf *buf)
259 restore_term();
260 return EOF;
261 }
231 -
262 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 +
287 restore_term();
288 return 0;
289 }