Hello! I'm currently investigating this gdb issue:
https://sourceware.org/bugzilla/show_bug.cgi?id=28833 the issue relates to bracketed-paste-mode. In gdb we currently use the callback mode of readline. When in the line handler callback, we spot that we received eof (by the line pointer being NULL), and we do two things: 1. Print out "quit\n", and 2. Cause gdb to actually quit. The expected behaviour goes like this, the user is sat at a prompt: (gdb) The user presses Ctrl-D, and then: (gdb) quit ... gdb shuts down ... notice the "quit" appears on the same line as the prompt, just as if the user entered the quit command. With a move to readline 8.x, and bracketed-paste-mode being on by default, what we now see, after the Ctrl-D, is this: quit) ... gdb shuts down ... The '\r' in BRACK_PASTE_FINI is responsible for sending the cursor back to column 0 here, which causes the prompt to be overwritten. For background I've read: https://lists.gnu.org/archive/html/bug-readline/2022-01/msg00001.html https://lists.gnu.org/archive/html/bug-bash/2018-01/msg00097.html The problem is that the BRACK_PASTE_FINI is printed as part of rl_deprep_terminal, which is done after the complete command line has been entered, and before the command line is processed. As such, the expectation is that, by the time the line handling callback is invoked, a '\n' will have been seen, and the cursor will now be on the next line. As such, printing things from the line handling callback, and expecting them to appear on the same line as the prompt is probably a bad idea. The problem with the ctrl-d, eof, case, is that this used to work, and still, almost does. Still, I wonder if there's a way we can get the old functionality back, without trying to print to the prompt line from the line handler callback. I've tried two things, neither seem to quite work, as I don't think I quite have all the information that I need. I have a rough readline patch below which I think solves the issue, but I'd like to explain what I've tried so far, I'm hoping there might be a solution that doesn't involve changing readline. My first thought was that I could use rl_deprep_term_function, something like: rl_deprep_term_function = gdb_rl_deprep_term_function; void gdb_rl_deprep_term_function () { if (/* Did we just receive eof? */) printf ("quit\n"); rl_deprep_terminal (); } Obviously, I'm struggling with what the if condition should be. My next idea was to use rl_getc_function, something like: rl_getc_function = gdb_rl_getc; int gdb_rl_getc (FILE *fp) { int ch = rl_getc (fp); if (/* Is `ch` eof? */) printf ("quit\n"); return ch; } This seems possible. In theory the two values of `ch` I care about are either EOF itself, or _rl_eof_char (as in, the variable from rlprivate.h). Now, clearly, I can't use _rl_eof_char directly, but, I could recreate its value. But, that would require me to issue some ioctl calls, which would require autoconf probing for their support. And, I'd forever have to make sure I figure out _rl_eof_char exactly as readline does, or I'll get weird edge case bugs. So, what I really want, is for readline to tell me when its found eof, rather than me having to figure it out. And that's where I go to. In the patch I have below added a new state flag RL_STATE_EOF, this is set before rl_deprep_term_function and rl_linefunc are called if readline has seen eof, and is cleared if eof was not seen. I would then use option #1 above (rl_deprep_term_function), like this: rl_deprep_term_function = gdb_rl_deprep_term_function; void gdb_rl_deprep_term_function () { if (RL_ISSTATE(RL_STATE_EOF) printf ("quit\n"); rl_deprep_terminal (); } The patch below is not in any way close to a "final" version, it's just a rough prototype for discussion. I'm hoping there might be a neat way to do what I want without changing readline at all. But, if there's not, then I'm happy to try and improve the patch below for possible inclusion into readline. I've also included an example application that exhibits the same problem gdb has. This is based on the Alternate Interface Example from the readline manual. The example detects if RL_STATE_EOF is defined, so building it against an unpatched readline will show the problem I have with gdb, and building against a patched readline will show the solution in action. I'd be grateful if there are ideas for better ways to achieve what I want, or failing that, general feedback on the approach taken in the patch would be great. Thanks, Andrew ---- /* Standard include files. stdio.h is required. */ #include <stdlib.h> #include <string.h> #include <unistd.h> #include <locale.h> #include <errno.h> /* Used for select(2) */ #include <sys/types.h> #include <sys/select.h> #include <signal.h> #include <stdio.h> /* Standard readline include files. */ #include <readline/readline.h> #include <readline/history.h> static void cb_linehandler (char *); static void sighandler (int); int running; int sigwinch_received; const char *prompt = "rltest$ "; /* Handle SIGWINCH and window size changes when readline is not active and reading a character. */ static void sighandler (int sig) { sigwinch_received = 1; } /* Callback function called for each line when accept-line executed, EOF seen, or EOF character read. This sets a flag and returns; it could also call exit(3). */ static void cb_linehandler (char *line) { #ifndef RL_STATE_EOF if (line == NULL) printf ("quit\n"); #endif /* Can use ^D (stty eof) or `exit' to exit. */ if (line == NULL || strcmp (line, "exit") == 0) { /* This function needs to be called to reset the terminal settings, and calling it from the line handler keeps one extra prompt from being displayed. */ rl_callback_handler_remove (); running = 0; } else { if (*line) add_history (line); printf ("input line: %s\n", line); free (line); } } void cb_deprep_terminal (void) { #ifdef RL_STATE_EOF if (RL_ISSTATE (RL_STATE_EOF)) printf ("quit\n"); #endif rl_deprep_terminal (); } int main (int c, char **v) { fd_set fds; int r; /* Set the default locale values according to environment variables. */ setlocale (LC_ALL, ""); /* Handle window size changes when readline is not active and reading characters. */ signal (SIGWINCH, sighandler); /* Install the line handler. */ rl_callback_handler_install (prompt, cb_linehandler); rl_deprep_term_function = cb_deprep_terminal; /* Enter a simple event loop. This waits until something is available to read on readline's input stream (defaults to standard input) and calls the builtin character read callback to read it. It does not have to modify the user's terminal settings. */ running = 1; while (running) { FD_ZERO (&fds); FD_SET (fileno (rl_instream), &fds); r = select (FD_SETSIZE, &fds, NULL, NULL, NULL); if (r < 0 && errno != EINTR) { perror ("rltest: select"); rl_callback_handler_remove (); break; } if (sigwinch_received) { rl_resize_terminal (); sigwinch_received = 0; } if (r < 0) continue; if (FD_ISSET (fileno (rl_instream), &fds)) rl_callback_read_char (); } printf ("rltest: Event loop has exited\n"); return 0; } --- diff --git a/callback.c b/callback.c index a466cf9..4653f5b 100644 --- a/callback.c +++ b/callback.c @@ -261,6 +261,11 @@ rl_callback_read_char (void) else eof = readline_internal_char (); + if (eof) + RL_SETSTATE (RL_STATE_EOF); + else + RL_UNSETSTATE (RL_STATE_EOF); + RL_CHECK_SIGNALS (); if (rl_done == 0 && _rl_want_redisplay) { diff --git a/readline.h b/readline.h index 78fa39d..4c0aa31 100644 --- a/readline.h +++ b/readline.h @@ -906,6 +906,7 @@ extern int rl_persistent_signal_handlers; #define RL_STATE_REDISPLAYING 0x1000000 /* updating terminal display */ #define RL_STATE_DONE 0x2000000 /* done; accepted line */ +#define RL_STATE_EOF 0x4000000 /* have received eof */ #define RL_SETSTATE(x) (rl_readline_state |= (x)) #define RL_UNSETSTATE(x) (rl_readline_state &= ~(x))
