bug-readline
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

bracketed-paste-mode and gdb


From: Andrew Burgess
Subject: bracketed-paste-mode and gdb
Date: Tue, 15 Feb 2022 17:21:40 +0000

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))




reply via email to

[Prev in Thread] Current Thread [Next in Thread]