Merge branch 'pw/add-p-single-key'

Finishing touches to C rewrite of "git add -i" in single-key
interactive mode.

* pw/add-p-single-key:
  terminal: restore settings on SIGTSTP
  terminal: work around macos poll() bug
  terminal: don't assume stdin is /dev/tty
  terminal: use flags for save_term()
This commit is contained in:
Junio C Hamano 2022-03-30 18:01:11 -07:00
commit d723492127
2 changed files with 211 additions and 36 deletions

View File

@ -1,4 +1,4 @@
#include "git-compat-util.h"
#include "cache.h"
#include "compat/terminal.h"
#include "sigchain.h"
#include "strbuf.h"
@ -20,39 +20,171 @@ static void restore_term_on_signal(int sig)
#define INPUT_PATH "/dev/tty"
#define OUTPUT_PATH "/dev/tty"
static volatile sig_atomic_t term_fd_needs_closing;
static int term_fd = -1;
static struct termios old_term;
static const char *background_resume_msg;
static const char *restore_error_msg;
static volatile sig_atomic_t ttou_received;
/* async safe error function for use by signal handlers. */
static void write_err(const char *msg)
{
write_in_full(2, "error: ", strlen("error: "));
write_in_full(2, msg, strlen(msg));
write_in_full(2, "\n", 1);
}
static void print_background_resume_msg(int signo)
{
int saved_errno = errno;
sigset_t mask;
struct sigaction old_sa;
struct sigaction sa = { .sa_handler = SIG_DFL };
ttou_received = 1;
write_err(background_resume_msg);
sigaction(signo, &sa, &old_sa);
raise(signo);
sigemptyset(&mask);
sigaddset(&mask, signo);
sigprocmask(SIG_UNBLOCK, &mask, NULL);
/* Stopped here */
sigprocmask(SIG_BLOCK, &mask, NULL);
sigaction(signo, &old_sa, NULL);
errno = saved_errno;
}
static void restore_terminal_on_suspend(int signo)
{
int saved_errno = errno;
int res;
struct termios t;
sigset_t mask;
struct sigaction old_sa;
struct sigaction sa = { .sa_handler = SIG_DFL };
int can_restore = 1;
if (tcgetattr(term_fd, &t) < 0)
can_restore = 0;
if (tcsetattr(term_fd, TCSAFLUSH, &old_term) < 0)
write_err(restore_error_msg);
sigaction(signo, &sa, &old_sa);
raise(signo);
sigemptyset(&mask);
sigaddset(&mask, signo);
sigprocmask(SIG_UNBLOCK, &mask, NULL);
/* Stopped here */
sigprocmask(SIG_BLOCK, &mask, NULL);
sigaction(signo, &old_sa, NULL);
if (!can_restore) {
write_err(restore_error_msg);
goto out;
}
/*
* If we resume in the background then we receive SIGTTOU when calling
* tcsetattr() below. Set up a handler to print an error message in that
* case.
*/
sigemptyset(&mask);
sigaddset(&mask, SIGTTOU);
sa.sa_mask = old_sa.sa_mask;
sa.sa_handler = print_background_resume_msg;
sa.sa_flags = SA_RESTART;
sigaction(SIGTTOU, &sa, &old_sa);
again:
ttou_received = 0;
sigprocmask(SIG_UNBLOCK, &mask, NULL);
res = tcsetattr(term_fd, TCSAFLUSH, &t);
sigprocmask(SIG_BLOCK, &mask, NULL);
if (ttou_received)
goto again;
else if (res < 0)
write_err(restore_error_msg);
sigaction(SIGTTOU, &old_sa, NULL);
out:
errno = saved_errno;
}
static void reset_job_signals(void)
{
if (restore_error_msg) {
signal(SIGTTIN, SIG_DFL);
signal(SIGTTOU, SIG_DFL);
signal(SIGTSTP, SIG_DFL);
restore_error_msg = NULL;
background_resume_msg = NULL;
}
}
static void close_term_fd(void)
{
if (term_fd_needs_closing)
close(term_fd);
term_fd_needs_closing = 0;
term_fd = -1;
}
void restore_term(void)
{
if (term_fd < 0)
return;
tcsetattr(term_fd, TCSAFLUSH, &old_term);
close(term_fd);
term_fd = -1;
close_term_fd();
sigchain_pop_common();
reset_job_signals();
}
int save_term(int full_duplex)
int save_term(enum save_term_flags flags)
{
struct sigaction sa;
if (term_fd < 0)
term_fd = open("/dev/tty", O_RDWR);
term_fd = ((flags & SAVE_TERM_STDIN)
? 0
: open("/dev/tty", O_RDWR));
if (term_fd < 0)
return -1;
if (tcgetattr(term_fd, &old_term) < 0)
term_fd_needs_closing = !(flags & SAVE_TERM_STDIN);
if (tcgetattr(term_fd, &old_term) < 0) {
close_term_fd();
return -1;
}
sigchain_push_common(restore_term_on_signal);
/*
* If job control is disabled then the shell will have set the
* disposition of SIGTSTP to SIG_IGN.
*/
sigaction(SIGTSTP, NULL, &sa);
if (sa.sa_handler == SIG_IGN)
return 0;
/* avoid calling gettext() from signal handler */
background_resume_msg = _("cannot resume in the background, please use 'fg' to resume");
restore_error_msg = _("cannot restore terminal settings");
sa.sa_handler = restore_terminal_on_suspend;
sa.sa_flags = SA_RESTART;
sigemptyset(&sa.sa_mask);
sigaddset(&sa.sa_mask, SIGTSTP);
sigaddset(&sa.sa_mask, SIGTTIN);
sigaddset(&sa.sa_mask, SIGTTOU);
sigaction(SIGTSTP, &sa, NULL);
sigaction(SIGTTIN, &sa, NULL);
sigaction(SIGTTOU, &sa, NULL);
return 0;
}
static int disable_bits(tcflag_t bits)
static int disable_bits(enum save_term_flags flags, tcflag_t bits)
{
struct termios t;
if (save_term(0) < 0)
goto error;
if (save_term(flags) < 0)
return -1;
t = old_term;
@ -65,20 +197,50 @@ static int disable_bits(tcflag_t bits)
return 0;
sigchain_pop_common();
error:
close(term_fd);
term_fd = -1;
reset_job_signals();
close_term_fd();
return -1;
}
static int disable_echo(void)
static int disable_echo(enum save_term_flags flags)
{
return disable_bits(ECHO);
return disable_bits(flags, ECHO);
}
static int enable_non_canonical(void)
static int enable_non_canonical(enum save_term_flags flags)
{
return disable_bits(ICANON | ECHO);
return disable_bits(flags, ICANON | ECHO);
}
/*
* On macos it is not possible to use poll() with a terminal so use select
* instead.
*/
static int getchar_with_timeout(int timeout)
{
struct timeval tv, *tvp = NULL;
fd_set readfds;
int res;
again:
if (timeout >= 0) {
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout % 1000) * 1000;
tvp = &tv;
}
FD_ZERO(&readfds);
FD_SET(0, &readfds);
res = select(1, &readfds, NULL, NULL, tvp);
if (!res)
return EOF;
if (res < 0) {
if (errno == EINTR)
goto again;
else
return EOF;
}
return getchar();
}
#elif defined(GIT_WINDOWS_NATIVE)
@ -126,7 +288,7 @@ void restore_term(void)
hconin = hconout = INVALID_HANDLE_VALUE;
}
int save_term(int full_duplex)
int save_term(enum save_term_flags flags)
{
hconin = CreateFileA("CONIN$", GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ, NULL, OPEN_EXISTING,
@ -134,7 +296,7 @@ int save_term(int full_duplex)
if (hconin == INVALID_HANDLE_VALUE)
return -1;
if (full_duplex) {
if (flags & SAVE_TERM_DUPLEX) {
hconout = CreateFileA("CONOUT$", GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL);
@ -154,7 +316,7 @@ error:
return -1;
}
static int disable_bits(DWORD bits)
static int disable_bits(enum save_term_flags flags, DWORD bits)
{
if (use_stty) {
struct child_process cp = CHILD_PROCESS_INIT;
@ -191,7 +353,7 @@ static int disable_bits(DWORD bits)
use_stty = 0;
}
if (save_term(0) < 0)
if (save_term(flags) < 0)
return -1;
if (!SetConsoleMode(hconin, cmode_in & ~bits)) {
@ -204,14 +366,15 @@ static int disable_bits(DWORD bits)
return 0;
}
static int disable_echo(void)
static int disable_echo(enum save_term_flags flags)
{
return disable_bits(ENABLE_ECHO_INPUT);
return disable_bits(flags, ENABLE_ECHO_INPUT);
}
static int enable_non_canonical(void)
static int enable_non_canonical(enum save_term_flags flags)
{
return disable_bits(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
return disable_bits(flags,
ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
}
/*
@ -245,6 +408,16 @@ static int mingw_getchar(void)
}
#define getchar mingw_getchar
static int getchar_with_timeout(int timeout)
{
struct pollfd pfd = { .fd = 0, .events = POLLIN };
if (poll(&pfd, 1, timeout) < 1)
return EOF;
return getchar();
}
#endif
#ifndef FORCE_TEXT
@ -267,7 +440,7 @@ char *git_terminal_prompt(const char *prompt, int echo)
return NULL;
}
if (!echo && disable_echo()) {
if (!echo && disable_echo(0)) {
fclose(input_fh);
fclose(output_fh);
return NULL;
@ -361,7 +534,7 @@ int read_key_without_echo(struct strbuf *buf)
static int warning_displayed;
int ch;
if (warning_displayed || enable_non_canonical() < 0) {
if (warning_displayed || enable_non_canonical(SAVE_TERM_STDIN) < 0) {
if (!warning_displayed) {
warning("reading single keystrokes not supported on "
"this platform; reading line instead");
@ -395,12 +568,7 @@ int read_key_without_echo(struct strbuf *buf)
* half a second when we know that the sequence is complete.
*/
while (!is_known_escape_sequence(buf->buf)) {
struct pollfd pfd = { .fd = 0, .events = POLLIN };
if (poll(&pfd, 1, 500) < 1)
break;
ch = getchar();
ch = getchar_with_timeout(500);
if (ch == EOF)
break;
strbuf_addch(buf, ch);
@ -413,10 +581,10 @@ int read_key_without_echo(struct strbuf *buf)
#else
int save_term(int full_duplex)
int save_term(enum save_term_flags flags)
{
/* full_duplex == 1, but no support available */
return -full_duplex;
/* no duplex support available */
return -!!(flags & SAVE_TERM_DUPLEX);
}
void restore_term(void)

View File

@ -1,6 +1,13 @@
#ifndef COMPAT_TERMINAL_H
#define COMPAT_TERMINAL_H
enum save_term_flags {
/* Save input and output settings */
SAVE_TERM_DUPLEX = 1 << 0,
/* Save stdin rather than /dev/tty (fails if stdin is not a terminal) */
SAVE_TERM_STDIN = 1 << 1,
};
/*
* Save the terminal attributes so they can be restored later by a
* call to restore_term(). Note that every successful call to
@ -8,7 +15,7 @@
* attributes have not been changed. Returns 0 on success, -1 on
* failure.
*/
int save_term(int full_duplex);
int save_term(enum save_term_flags flags);
/* Restore the terminal attributes that were saved with save_term() */
void restore_term(void);