|
4 | 4 | #include "strbuf.h"
|
5 | 5 | #include "run-command.h"
|
6 | 6 | #include "string-list.h"
|
| 7 | +#include "hashmap.h" |
7 | 8 |
|
8 | 9 | #if defined(HAVE_DEV_TTY) || defined(GIT_WINDOWS_NATIVE)
|
9 | 10 |
|
@@ -238,6 +239,71 @@ char *git_terminal_prompt(const char *prompt, int echo)
|
238 | 239 | return buf.buf;
|
239 | 240 | }
|
240 | 241 |
|
| 242 | +/* |
| 243 | + * The `is_known_escape_sequence()` function returns 1 if the passed string |
| 244 | + * corresponds to an Escape sequence that the terminal capabilities contains. |
| 245 | + * |
| 246 | + * To avoid depending on ncurses or other platform-specific libraries, we rely |
| 247 | + * on the presence of the `infocmp` executable to do the job for us (failing |
| 248 | + * silently if the program is not available or refused to run). |
| 249 | + */ |
| 250 | +struct escape_sequence_entry { |
| 251 | + struct hashmap_entry entry; |
| 252 | + char sequence[FLEX_ARRAY]; |
| 253 | +}; |
| 254 | + |
| 255 | +static int sequence_entry_cmp(const void *hashmap_cmp_fn_data, |
| 256 | + const struct escape_sequence_entry *e1, |
| 257 | + const struct escape_sequence_entry *e2, |
| 258 | + const void *keydata) |
| 259 | +{ |
| 260 | + return strcmp(e1->sequence, keydata ? keydata : e2->sequence); |
| 261 | +} |
| 262 | + |
| 263 | +static int is_known_escape_sequence(const char *sequence) |
| 264 | +{ |
| 265 | + static struct hashmap sequences; |
| 266 | + static int initialized; |
| 267 | + |
| 268 | + if (!initialized) { |
| 269 | + struct child_process cp = CHILD_PROCESS_INIT; |
| 270 | + struct strbuf buf = STRBUF_INIT; |
| 271 | + char *p, *eol; |
| 272 | + |
| 273 | + hashmap_init(&sequences, (hashmap_cmp_fn)sequence_entry_cmp, |
| 274 | + NULL, 0); |
| 275 | + |
| 276 | + argv_array_pushl(&cp.args, "infocmp", "-L", "-1", NULL); |
| 277 | + if (pipe_command(&cp, NULL, 0, &buf, 0, NULL, 0)) |
| 278 | + strbuf_setlen(&buf, 0); |
| 279 | + |
| 280 | + for (eol = p = buf.buf; *p; p = eol + 1) { |
| 281 | + p = strchr(p, '='); |
| 282 | + if (!p) |
| 283 | + break; |
| 284 | + p++; |
| 285 | + eol = strchrnul(p, '\n'); |
| 286 | + |
| 287 | + if (starts_with(p, "\\E")) { |
| 288 | + char *comma = memchr(p, ',', eol - p); |
| 289 | + struct escape_sequence_entry *e; |
| 290 | + |
| 291 | + p[0] = '^'; |
| 292 | + p[1] = '['; |
| 293 | + FLEX_ALLOC_MEM(e, sequence, p, comma - p); |
| 294 | + hashmap_entry_init(&e->entry, |
| 295 | + strhash(e->sequence)); |
| 296 | + hashmap_add(&sequences, &e->entry); |
| 297 | + } |
| 298 | + if (!*eol) |
| 299 | + break; |
| 300 | + } |
| 301 | + initialized = 1; |
| 302 | + } |
| 303 | + |
| 304 | + return !!hashmap_get_from_hash(&sequences, strhash(sequence), sequence); |
| 305 | +} |
| 306 | + |
241 | 307 | int read_key_without_echo(struct strbuf *buf)
|
242 | 308 | {
|
243 | 309 | static int warning_displayed;
|
@@ -271,7 +337,12 @@ int read_key_without_echo(struct strbuf *buf)
|
271 | 337 | * Start by replacing the Escape byte with ^[ */
|
272 | 338 | strbuf_splice(buf, buf->len - 1, 1, "^[", 2);
|
273 | 339 |
|
274 |
| - for (;;) { |
| 340 | + /* |
| 341 | + * Query the terminal capabilities once about all the Escape |
| 342 | + * sequences it knows about, so that we can avoid waiting for |
| 343 | + * half a second when we know that the sequence is complete. |
| 344 | + */ |
| 345 | + while (!is_known_escape_sequence(buf->buf)) { |
275 | 346 | struct pollfd pfd = { .fd = 0, .events = POLLIN };
|
276 | 347 |
|
277 | 348 | if (poll(&pfd, 1, 500) < 1)
|
|
0 commit comments