Unify CLI/TUI tab-completion match list displayers

Message ID m3iofmlhg2.fsf@sspiff.org
State New, archived
Headers

Commit Message

Doug Evans Jan. 31, 2015, 10:21 p.m. UTC
  Doug Evans <xdje42@gmail.com> writes:
> On Wed, Jan 21, 2015 at 8:57 AM, Doug Evans <xdje42@gmail.com> wrote:
>> Hi.
>>
>> This patch unifies CLI/TUI tab-completion match list displayers.
>> In doing so it fixes a few bugs:
>>
>> - TUI now has pagination of match list output (yay)
>> - directories in TUI output get / appended (minor yay)
>> - fixes Ctrl-g handling in the CLI version (yay)
>>   [There are some other nits which the CLI version has, e.g., if
>>   gdb displays output after the first tab (dwarf complaints) then
>>   the prompt + text thus far isn't re-printed, presumably because
>>   readline isn't aware of the output.  I'm leaving that for another pass,
>>   but I like fixing Ctrl-g now.]
>>
>> ...
>>
>> +   This function handles the LIST_MAYBE_TRUNCATED marker that we add to the
>> +   completion list.
>> +
>> +   Note: While LIST_MAYBE_TRUNCATED contributes to MAX, it's not long enough
>> +   that we worry about it.  */
>
> Bleah.  Ignore this part of the patch.
> That's part of a followup patch.
> Fixing TUI match list pagination (that heads in a direction of
> completely cleaning up TUI match list display) is enough for me to
> push this in now.

Here's what I committed.

For reference sake, window resizing in TUI is still not handled very
well, but it worked for the tab-completion tests I tried
(where the window is resized while readline is waiting, for example,
for an answer to the "Display all N possibilities" prompt).

2015-01-31  Doug Evans  <xdje42@gmail.com>

	* cli-out.c: #include completer.h, readline/readline.h.
	(cli_mld_crlf, cli_mld_putch, cli_mld_puts): New functions.
	(cli_mld_flush, cld_mld_erase_entire_line): Ditto.
	(cli_mld_beep, cli_mld_read_key, cli_display_match_list): Ditto.
	* cli-out.h (cli_display_match_list): Declare.
	* completer.c (MB_INVALIDCH, MB_NULLWCH): New macros.
	(ELLIPSIS_LEN): Ditto.
	(gdb_get_y_or_n, gdb_display_match_list_pager): New functions.
	(gdb_path_isdir, gdb_printable_part, gdb_fnwidth): Ditto.
	(gdb_fnprint, gdb_print_filename): Ditto.
	(gdb_complete_get_screenwidth, gdb_display_match_list_1): Ditto.
	(gdb_display_match_list): Ditto.
	* completer.h (mld_crlf_ftype, mld_putch_ftype): New typedefs.
	(mld_puts_ftype, mld_flush_ftype, mld_erase_entire_line_ftype): Ditto.
	(mld_beep_ftype, mld_read_key_ftype): Ditto.
	(match_list_displayer): New struct.
	(gdb_display_match_list): Declare.
	* top.c (init_main): Set rl_completion_display_matches_hook.
	* tui/tui-io.c: #include completer.h.
	(printable_part, PUTX, print_filename, get_y_or_n): Delete.
	(tui_mld_crlf, tui_mld_putch, tui_mld_puts): New functions.
	(tui_mld_flush, tui_mld_erase_entire_line, tui_mld_beep): Ditto.
	(tui_mld_getc, tui_mld_read_key): Ditto.
	(tui_rl_display_match_list): Rewrite.
	(tui_handle_resize_during_io): New arg for_completion.  All callers
	updated.
  

Patch

diff --git a/gdb/cli-out.c b/gdb/cli-out.c
index 76222c6..48f2a04 100644
--- a/gdb/cli-out.c
+++ b/gdb/cli-out.c
@@ -23,11 +23,12 @@ 
 #include "defs.h"
 #include "ui-out.h"
 #include "cli-out.h"
+#include "completer.h"
 #include "vec.h"
+#include "readline/readline.h"
 
 typedef struct cli_ui_out_data cli_out_data;
 
-
 /* Prototypes for local functions */
 
 static void cli_text (struct ui_out *uiout, const char *string);
@@ -416,3 +417,84 @@  cli_out_set_stream (struct ui_out *uiout, struct ui_file *stream)
 
   return old;
 }
+
+/* CLI interface to display tab-completion matches.  */
+
+/* CLI version of displayer.crlf.  */
+
+static void
+cli_mld_crlf (const struct match_list_displayer *displayer)
+{
+  rl_crlf ();
+}
+
+/* CLI version of displayer.putch.  */
+
+static void
+cli_mld_putch (const struct match_list_displayer *displayer, int ch)
+{
+  putc (ch, rl_outstream);
+}
+
+/* CLI version of displayer.puts.  */
+
+static void
+cli_mld_puts (const struct match_list_displayer *displayer, const char *s)
+{
+  fputs (s, rl_outstream);
+}
+
+/* CLI version of displayer.flush.  */
+
+static void
+cli_mld_flush (const struct match_list_displayer *displayer)
+{
+  fflush (rl_outstream);
+}
+
+/* CLI version of displayer.erase_entire_line.  */
+
+static void
+cli_mld_erase_entire_line (const struct match_list_displayer *displayer)
+{
+  extern void _rl_erase_entire_line (void);
+
+  _rl_erase_entire_line ();
+}
+
+/* CLI version of displayer.beep.  */
+
+static void
+cli_mld_beep (const struct match_list_displayer *displayer)
+{
+  rl_ding ();
+}
+
+/* CLI version of displayer.read_key.  */
+
+static int
+cli_mld_read_key (const struct match_list_displayer *displayer)
+{
+  return rl_read_key ();
+}
+
+/* CLI version of rl_completion_display_matches_hook.
+   See gdb_display_match_list for a description of the arguments.  */
+
+void
+cli_display_match_list (char **matches, int len, int max)
+{
+  struct match_list_displayer displayer;
+
+  rl_get_screen_size (&displayer.height, &displayer.width);
+  displayer.crlf = cli_mld_crlf;
+  displayer.putch = cli_mld_putch;
+  displayer.puts = cli_mld_puts;
+  displayer.flush = cli_mld_flush;
+  displayer.erase_entire_line = cli_mld_erase_entire_line;
+  displayer.beep = cli_mld_beep;
+  displayer.read_key = cli_mld_read_key;
+
+  gdb_display_match_list (matches, len, max, &displayer);
+  rl_forced_update_display ();
+}
diff --git a/gdb/cli-out.h b/gdb/cli-out.h
index bf07069..401429a 100644
--- a/gdb/cli-out.h
+++ b/gdb/cli-out.h
@@ -48,4 +48,6 @@  extern void cli_out_data_ctor (struct cli_ui_out_data *data,
 extern struct ui_file *cli_out_set_stream (struct ui_out *uiout,
 					   struct ui_file *stream);
 
+extern void cli_display_match_list (char **matches, int len, int max);
+
 #endif
diff --git a/gdb/completer.c b/gdb/completer.c
index 2b6aa87..5937439 100644
--- a/gdb/completer.c
+++ b/gdb/completer.c
@@ -1020,3 +1020,552 @@  skip_quoted (const char *str)
 {
   return skip_quoted_chars (str, NULL, NULL);
 }
+
+/* GDB replacement for rl_display_match_list.
+   Readline doesn't provide a clean interface for TUI(curses).
+   A hack previously used was to send readline's rl_outstream through a pipe
+   and read it from the event loop.  Bleah.  IWBN if readline abstracted
+   away all the necessary bits, and this is what this code does.  It
+   replicates the parts of readline we need and then adds an abstraction
+   layer, currently implemented as struct match_list_displayer, so that both
+   CLI and TUI can use it.  We copy all this readline code to minimize
+   GDB-specific mods to readline.  Once this code performs as desired then
+   we can submit it to the readline maintainers.
+
+   N.B. A lot of the code is the way it is in order to minimize differences
+   from readline's copy.  */
+
+/* Not supported here.  */
+#undef VISIBLE_STATS
+
+#if defined (HANDLE_MULTIBYTE)
+#define MB_INVALIDCH(x) ((x) == (size_t)-1 || (x) == (size_t)-2)
+#define MB_NULLWCH(x)   ((x) == 0)
+#endif
+
+#define ELLIPSIS_LEN	3
+
+/* gdb version of readline/complete.c:get_y_or_n.
+   'y' -> returns 1, and 'n' -> returns 0.
+   Also supported: space == 'y', RUBOUT == 'n', ctrl-g == start over.
+   If FOR_PAGER is non-zero, then also supported are:
+   NEWLINE or RETURN -> returns 2, and 'q' -> returns 0.  */
+
+static int
+gdb_get_y_or_n (int for_pager, const struct match_list_displayer *displayer)
+{
+  int c;
+
+  for (;;)
+    {
+      RL_SETSTATE (RL_STATE_MOREINPUT);
+      c = displayer->read_key (displayer);
+      RL_UNSETSTATE (RL_STATE_MOREINPUT);
+
+      if (c == 'y' || c == 'Y' || c == ' ')
+	return 1;
+      if (c == 'n' || c == 'N' || c == RUBOUT)
+	return 0;
+      if (c == ABORT_CHAR || c < 0)
+	{
+	  /* Readline doesn't erase_entire_line here, but without it the
+	     --More-- prompt isn't erased and neither is the text entered
+	     thus far redisplayed.  */
+	  displayer->erase_entire_line (displayer);
+	  /* Note: The arguments to rl_abort are ignored.  */
+	  rl_abort (0, 0);
+	}
+      if (for_pager && (c == NEWLINE || c == RETURN))
+	return 2;
+      if (for_pager && (c == 'q' || c == 'Q'))
+	return 0;
+      displayer->beep (displayer);
+    }
+}
+
+/* Pager function for tab-completion.
+   This is based on readline/complete.c:_rl_internal_pager.
+   LINES is the number of lines of output displayed thus far.
+   Returns:
+   -1 -> user pressed 'n' or equivalent,
+   0 -> user pressed 'y' or equivalent,
+   N -> user pressed NEWLINE or equivalent and N is LINES - 1.  */
+
+static int
+gdb_display_match_list_pager (int lines,
+			      const struct match_list_displayer *displayer)
+{
+  int i;
+
+  displayer->puts (displayer, "--More--");
+  displayer->flush (displayer);
+  i = gdb_get_y_or_n (1, displayer);
+  displayer->erase_entire_line (displayer);
+  if (i == 0)
+    return -1;
+  else if (i == 2)
+    return (lines - 1);
+  else
+    return 0;
+}
+
+/* Return non-zero if FILENAME is a directory.
+   Based on readline/complete.c:path_isdir.  */
+
+static int
+gdb_path_isdir (const char *filename)
+{
+  struct stat finfo;
+
+  return (stat (filename, &finfo) == 0 && S_ISDIR (finfo.st_mode));
+}
+
+/* Return the portion of PATHNAME that should be output when listing
+   possible completions.  If we are hacking filename completion, we
+   are only interested in the basename, the portion following the
+   final slash.  Otherwise, we return what we were passed.  Since
+   printing empty strings is not very informative, if we're doing
+   filename completion, and the basename is the empty string, we look
+   for the previous slash and return the portion following that.  If
+   there's no previous slash, we just return what we were passed.
+
+   Based on readline/complete.c:printable_part.  */
+
+static char *
+gdb_printable_part (char *pathname)
+{
+  char *temp, *x;
+
+  if (rl_filename_completion_desired == 0)	/* don't need to do anything */
+    return (pathname);
+
+  temp = strrchr (pathname, '/');
+#if defined (__MSDOS__)
+  if (temp == 0 && ISALPHA ((unsigned char)pathname[0]) && pathname[1] == ':')
+    temp = pathname + 1;
+#endif
+
+  if (temp == 0 || *temp == '\0')
+    return (pathname);
+  /* If the basename is NULL, we might have a pathname like '/usr/src/'.
+     Look for a previous slash and, if one is found, return the portion
+     following that slash.  If there's no previous slash, just return the
+     pathname we were passed. */
+  else if (temp[1] == '\0')
+    {
+      for (x = temp - 1; x > pathname; x--)
+        if (*x == '/')
+          break;
+      return ((*x == '/') ? x + 1 : pathname);
+    }
+  else
+    return ++temp;
+}
+
+/* Compute width of STRING when displayed on screen by print_filename.
+   Based on readline/complete.c:fnwidth.  */
+
+static int
+gdb_fnwidth (const char *string)
+{
+  int width, pos;
+#if defined (HANDLE_MULTIBYTE)
+  mbstate_t ps;
+  int left, w;
+  size_t clen;
+  wchar_t wc;
+
+  left = strlen (string) + 1;
+  memset (&ps, 0, sizeof (mbstate_t));
+#endif
+
+  width = pos = 0;
+  while (string[pos])
+    {
+      if (CTRL_CHAR (string[pos]) || string[pos] == RUBOUT)
+	{
+	  width += 2;
+	  pos++;
+	}
+      else
+	{
+#if defined (HANDLE_MULTIBYTE)
+	  clen = mbrtowc (&wc, string + pos, left - pos, &ps);
+	  if (MB_INVALIDCH (clen))
+	    {
+	      width++;
+	      pos++;
+	      memset (&ps, 0, sizeof (mbstate_t));
+	    }
+	  else if (MB_NULLWCH (clen))
+	    break;
+	  else
+	    {
+	      pos += clen;
+	      w = wcwidth (wc);
+	      width += (w >= 0) ? w : 1;
+	    }
+#else
+	  width++;
+	  pos++;
+#endif
+	}
+    }
+
+  return width;
+}
+
+/* Print TO_PRINT, one matching completion.
+   PREFIX_BYTES is number of common prefix bytes.
+   Based on readline/complete.c:fnprint.  */
+
+static int
+gdb_fnprint (const char *to_print, int prefix_bytes,
+	     const struct match_list_displayer *displayer)
+{
+  int printed_len, w;
+  const char *s;
+#if defined (HANDLE_MULTIBYTE)
+  mbstate_t ps;
+  const char *end;
+  size_t tlen;
+  int width;
+  wchar_t wc;
+
+  end = to_print + strlen (to_print) + 1;
+  memset (&ps, 0, sizeof (mbstate_t));
+#endif
+
+  printed_len = 0;
+
+  /* Don't print only the ellipsis if the common prefix is one of the
+     possible completions */
+  if (to_print[prefix_bytes] == '\0')
+    prefix_bytes = 0;
+
+  if (prefix_bytes)
+    {
+      char ellipsis;
+
+      ellipsis = (to_print[prefix_bytes] == '.') ? '_' : '.';
+      for (w = 0; w < ELLIPSIS_LEN; w++)
+	displayer->putch (displayer, ellipsis);
+      printed_len = ELLIPSIS_LEN;
+    }
+
+  s = to_print + prefix_bytes;
+  while (*s)
+    {
+      if (CTRL_CHAR (*s))
+        {
+          displayer->putch (displayer, '^');
+          displayer->putch (displayer, UNCTRL (*s));
+          printed_len += 2;
+          s++;
+#if defined (HANDLE_MULTIBYTE)
+	  memset (&ps, 0, sizeof (mbstate_t));
+#endif
+        }
+      else if (*s == RUBOUT)
+	{
+	  displayer->putch (displayer, '^');
+	  displayer->putch (displayer, '?');
+	  printed_len += 2;
+	  s++;
+#if defined (HANDLE_MULTIBYTE)
+	  memset (&ps, 0, sizeof (mbstate_t));
+#endif
+	}
+      else
+	{
+#if defined (HANDLE_MULTIBYTE)
+	  tlen = mbrtowc (&wc, s, end - s, &ps);
+	  if (MB_INVALIDCH (tlen))
+	    {
+	      tlen = 1;
+	      width = 1;
+	      memset (&ps, 0, sizeof (mbstate_t));
+	    }
+	  else if (MB_NULLWCH (tlen))
+	    break;
+	  else
+	    {
+	      w = wcwidth (wc);
+	      width = (w >= 0) ? w : 1;
+	    }
+	  for (w = 0; w < tlen; ++w)
+	    displayer->putch (displayer, s[w]);
+	  s += tlen;
+	  printed_len += width;
+#else
+	  displayer->putch (displayer, *s);
+	  s++;
+	  printed_len++;
+#endif
+	}
+    }
+
+  return printed_len;
+}
+
+/* Output TO_PRINT to rl_outstream.  If VISIBLE_STATS is defined and we
+   are using it, check for and output a single character for `special'
+   filenames.  Return the number of characters we output.
+   Based on readline/complete.c:print_filename.  */
+
+static int
+gdb_print_filename (char *to_print, char *full_pathname, int prefix_bytes,
+		    const struct match_list_displayer *displayer)
+{
+  int printed_len, extension_char, slen, tlen;
+  char *s, c, *new_full_pathname, *dn;
+  extern int _rl_complete_mark_directories;
+
+  extension_char = 0;
+  printed_len = gdb_fnprint (to_print, prefix_bytes, displayer);
+
+#if defined (VISIBLE_STATS)
+ if (rl_filename_completion_desired && (rl_visible_stats || _rl_complete_mark_directories))
+#else
+ if (rl_filename_completion_desired && _rl_complete_mark_directories)
+#endif
+    {
+      /* If to_print != full_pathname, to_print is the basename of the
+	 path passed.  In this case, we try to expand the directory
+	 name before checking for the stat character. */
+      if (to_print != full_pathname)
+	{
+	  /* Terminate the directory name. */
+	  c = to_print[-1];
+	  to_print[-1] = '\0';
+
+	  /* If setting the last slash in full_pathname to a NUL results in
+	     full_pathname being the empty string, we are trying to complete
+	     files in the root directory.  If we pass a null string to the
+	     bash directory completion hook, for example, it will expand it
+	     to the current directory.  We just want the `/'. */
+	  if (full_pathname == 0 || *full_pathname == 0)
+	    dn = "/";
+	  else if (full_pathname[0] != '/')
+	    dn = full_pathname;
+	  else if (full_pathname[1] == 0)
+	    dn = "//";		/* restore trailing slash to `//' */
+	  else if (full_pathname[1] == '/' && full_pathname[2] == 0)
+	    dn = "/";		/* don't turn /// into // */
+	  else
+	    dn = full_pathname;
+	  s = tilde_expand (dn);
+	  if (rl_directory_completion_hook)
+	    (*rl_directory_completion_hook) (&s);
+
+	  slen = strlen (s);
+	  tlen = strlen (to_print);
+	  new_full_pathname = (char *)xmalloc (slen + tlen + 2);
+	  strcpy (new_full_pathname, s);
+	  if (s[slen - 1] == '/')
+	    slen--;
+	  else
+	    new_full_pathname[slen] = '/';
+	  new_full_pathname[slen] = '/';
+	  strcpy (new_full_pathname + slen + 1, to_print);
+
+#if defined (VISIBLE_STATS)
+	  if (rl_visible_stats)
+	    extension_char = stat_char (new_full_pathname);
+	  else
+#endif
+	  if (gdb_path_isdir (new_full_pathname))
+	    extension_char = '/';
+
+	  xfree (new_full_pathname);
+	  to_print[-1] = c;
+	}
+      else
+	{
+	  s = tilde_expand (full_pathname);
+#if defined (VISIBLE_STATS)
+	  if (rl_visible_stats)
+	    extension_char = stat_char (s);
+	  else
+#endif
+	    if (gdb_path_isdir (s))
+	      extension_char = '/';
+	}
+
+      xfree (s);
+      if (extension_char)
+	{
+	  displayer->putch (displayer, extension_char);
+	  printed_len++;
+	}
+    }
+
+  return printed_len;
+}
+
+/* GDB version of readline/complete.c:complete_get_screenwidth.  */
+
+static int
+gdb_complete_get_screenwidth (const struct match_list_displayer *displayer)
+{
+  /* Readline has other stuff here which it's not clear we need.  */
+  return displayer->width;
+}
+
+/* GDB version of readline/complete.c:rl_display_match_list.
+   See gdb_display_match_list for a description of MATCHES, LEN, MAX.  */
+
+static void
+gdb_display_match_list_1 (char **matches, int len, int max,
+			  const struct match_list_displayer *displayer)
+{
+  int count, limit, printed_len, lines, cols;
+  int i, j, k, l, common_length, sind;
+  char *temp, *t;
+  int page_completions = displayer->height != INT_MAX && pagination_enabled;
+  extern int _rl_completion_prefix_display_length;
+  extern int _rl_qsort_string_compare (const void *, const void *);
+  extern int _rl_print_completions_horizontally;
+  typedef int QSFUNC (const void *, const void *);
+
+  /* Find the length of the prefix common to all items: length as displayed
+     characters (common_length) and as a byte index into the matches (sind) */
+  common_length = sind = 0;
+  if (_rl_completion_prefix_display_length > 0)
+    {
+      t = gdb_printable_part (matches[0]);
+      temp = strrchr (t, '/');
+      common_length = temp ? gdb_fnwidth (temp) : gdb_fnwidth (t);
+      sind = temp ? strlen (temp) : strlen (t);
+
+      if (common_length > _rl_completion_prefix_display_length && common_length > ELLIPSIS_LEN)
+	max -= common_length - ELLIPSIS_LEN;
+      else
+	common_length = sind = 0;
+    }
+
+  /* How many items of MAX length can we fit in the screen window? */
+  cols = gdb_complete_get_screenwidth (displayer);
+  max += 2;
+  limit = cols / max;
+  if (limit != 1 && (limit * max == cols))
+    limit--;
+
+  /* If cols == 0, limit will end up -1 */
+  if (cols < displayer->width && limit < 0)
+    limit = 1;
+
+  /* Avoid a possible floating exception.  If max > cols,
+     limit will be 0 and a divide-by-zero fault will result. */
+  if (limit == 0)
+    limit = 1;
+
+  /* How many iterations of the printing loop? */
+  count = (len + (limit - 1)) / limit;
+
+  /* Watch out for special case.  If LEN is less than LIMIT, then
+     just do the inner printing loop.
+	   0 < len <= limit  implies  count = 1. */
+
+  /* Sort the items if they are not already sorted. */
+  if (rl_ignore_completion_duplicates == 0 && rl_sort_completion_matches)
+    qsort (matches + 1, len, sizeof (char *), (QSFUNC *)_rl_qsort_string_compare);
+
+  displayer->crlf (displayer);
+
+  lines = 0;
+  if (_rl_print_completions_horizontally == 0)
+    {
+      /* Print the sorted items, up-and-down alphabetically, like ls. */
+      for (i = 1; i <= count; i++)
+	{
+	  for (j = 0, l = i; j < limit; j++)
+	    {
+	      if (l > len || matches[l] == 0)
+		break;
+	      else
+		{
+		  temp = gdb_printable_part (matches[l]);
+		  printed_len = gdb_print_filename (temp, matches[l], sind,
+						    displayer);
+
+		  if (j + 1 < limit)
+		    for (k = 0; k < max - printed_len; k++)
+		      displayer->putch (displayer, ' ');
+		}
+	      l += count;
+	    }
+	  displayer->crlf (displayer);
+	  lines++;
+	  if (page_completions && lines >= (displayer->height - 1) && i < count)
+	    {
+	      lines = gdb_display_match_list_pager (lines, displayer);
+	      if (lines < 0)
+		return;
+	    }
+	}
+    }
+  else
+    {
+      /* Print the sorted items, across alphabetically, like ls -x. */
+      for (i = 1; matches[i]; i++)
+	{
+	  temp = gdb_printable_part (matches[i]);
+	  printed_len = gdb_print_filename (temp, matches[i], sind, displayer);
+	  /* Have we reached the end of this line? */
+	  if (matches[i+1])
+	    {
+	      if (i && (limit > 1) && (i % limit) == 0)
+		{
+		  displayer->crlf (displayer);
+		  lines++;
+		  if (page_completions && lines >= displayer->height - 1)
+		    {
+		      lines = gdb_display_match_list_pager (lines, displayer);
+		      if (lines < 0)
+			return;
+		    }
+		}
+	      else
+		for (k = 0; k < max - printed_len; k++)
+		  displayer->putch (displayer, ' ');
+	    }
+	}
+      displayer->crlf (displayer);
+    }
+}
+
+/* Utility for displaying completion list matches, used by both CLI and TUI.
+
+   MATCHES is the list of strings, in argv format, LEN is the number of
+   strings in MATCHES, and MAX is the length of the longest string in
+   MATCHES.  */
+
+void
+gdb_display_match_list (char **matches, int len, int max,
+			const struct match_list_displayer *displayer)
+{
+  if (rl_completion_query_items > 0 && len >= rl_completion_query_items)
+    {
+      char msg[100];
+
+      /* We can't use *query here because they wait for <RET> which is
+	 wrong here.  This follows the readline version as closely as possible
+	 for compatibility's sake.  See readline/complete.c.  */
+
+      displayer->crlf (displayer);
+
+      xsnprintf (msg, sizeof (msg),
+		 "Display all %d possibilities? (y or n)", len);
+      displayer->puts (displayer, msg);
+      displayer->flush (displayer);
+
+      if (gdb_get_y_or_n (0, displayer) == 0)
+	{
+	  displayer->crlf (displayer);
+	  return;
+	}
+    }
+
+  gdb_display_match_list_1 (matches, len, max, displayer);
+}
diff --git a/gdb/completer.h b/gdb/completer.h
index 8f925fe..dbb1cfb 100644
--- a/gdb/completer.h
+++ b/gdb/completer.h
@@ -20,6 +20,52 @@ 
 #include "gdb_vecs.h"
 #include "command.h"
 
+/* Types of functions in struct match_list_displayer.  */
+
+struct match_list_displayer;
+
+typedef void mld_crlf_ftype (const struct match_list_displayer *);
+typedef void mld_putch_ftype (const struct match_list_displayer *, int);
+typedef void mld_puts_ftype (const struct match_list_displayer *,
+			     const char *);
+typedef void mld_flush_ftype (const struct match_list_displayer *);
+typedef void mld_erase_entire_line_ftype (const struct match_list_displayer *);
+typedef void mld_beep_ftype (const struct match_list_displayer *);
+typedef int mld_read_key_ftype (const struct match_list_displayer *);
+
+/* Interface between CLI/TUI and gdb_match_list_displayer.  */
+
+struct match_list_displayer
+{
+  /* The screen dimensions to work with when displaying matches.  */
+  int height, width;
+
+  /* Print cr,lf.  */
+  mld_crlf_ftype *crlf;
+
+  /* Not "putc" to avoid issues where it is a stdio macro.  Sigh.  */
+  mld_putch_ftype *putch;
+
+  /* Print a string.  */
+  mld_puts_ftype *puts;
+
+  /* Flush all accumulated output.  */
+  mld_flush_ftype *flush;
+
+  /* Erase the currently line on the terminal (but don't discard any text the
+     user has entered, readline may shortly re-print it).  */
+  mld_erase_entire_line_ftype *erase_entire_line;
+
+  /* Ring the bell.  */
+  mld_beep_ftype *beep;
+
+  /* Read one key.  */
+  mld_read_key_ftype *read_key;
+};
+
+extern void gdb_display_match_list (char **matches, int len, int max,
+				    const struct match_list_displayer *);
+
 extern VEC (char_ptr) *complete_line (const char *text,
 				      const char *line_buffer,
 				      int point);
diff --git a/gdb/top.c b/gdb/top.c
index a1462a0..000b14e 100644
--- a/gdb/top.c
+++ b/gdb/top.c
@@ -1828,6 +1828,7 @@  init_main (void)
   rl_completion_entry_function = readline_line_completion_function;
   rl_completer_word_break_characters = default_word_break_characters ();
   rl_completer_quote_characters = get_gdb_completer_quote_characters ();
+  rl_completion_display_matches_hook = cli_display_match_list;
   rl_readline_name = "gdb";
   rl_terminal_name = getenv ("TERM");
 
diff --git a/gdb/tui/tui-io.c b/gdb/tui/tui-io.c
index 19e9485..831705c 100644
--- a/gdb/tui/tui-io.c
+++ b/gdb/tui/tui-io.c
@@ -37,7 +37,7 @@ 
 #include <fcntl.h>
 #include <signal.h>
 #include "filestuff.h"
-
+#include "completer.h"
 #include "gdb_curses.h"
 
 /* This redefines CTRL if it is not already defined, so it must come
@@ -146,7 +146,7 @@  static int tui_readline_pipe[2];
    This may be the main gdb prompt or a secondary prompt.  */
 static char *tui_rl_saved_prompt;
 
-static unsigned int tui_handle_resize_during_io (unsigned int);
+static int tui_handle_resize_during_io (int, int);
 
 static void
 tui_putc (char c)
@@ -346,182 +346,106 @@  tui_readline_output (int error, gdb_client_data data)
 }
 #endif
 
-/* Return the portion of PATHNAME that should be output when listing
-   possible completions.  If we are hacking filename completion, we
-   are only interested in the basename, the portion following the
-   final slash.  Otherwise, we return what we were passed.
+/* TUI version of displayer.crlf.  */
 
-   Comes from readline/complete.c.  */
-static const char *
-printable_part (const char *pathname)
+static void
+tui_mld_crlf (const struct match_list_displayer *displayer)
 {
-  return rl_filename_completion_desired ? lbasename (pathname) : pathname;
+  tui_putc ('\n');
 }
 
-/* Output TO_PRINT to rl_outstream.  If VISIBLE_STATS is defined and
-   we are using it, check for and output a single character for
-   `special' filenames.  Return the number of characters we
-   output.  */
-
-#define PUTX(c) \
-    do { \
-      if (CTRL_CHAR (c)) \
-        { \
-          tui_puts ("^"); \
-          tui_putc (UNCTRL (c)); \
-          printed_len += 2; \
-        } \
-      else if (c == RUBOUT) \
-	{ \
-	  tui_puts ("^?"); \
-	  printed_len += 2; \
-	} \
-      else \
-	{ \
-	  tui_putc (c); \
-	  printed_len++; \
-	} \
-    } while (0)
+/* TUI version of displayer.putch.  */
 
-static int
-print_filename (const char *to_print, const char *full_pathname)
+static void
+tui_mld_putch (const struct match_list_displayer *displayer, int ch)
 {
-  int printed_len = 0;
-  const char *s;
-
-  for (s = to_print; *s; s++)
-    {
-      PUTX (*s);
-    }
-  return printed_len;
+  tui_putc (ch);
 }
 
-/* The user must press "y" or "n".  Non-zero return means "y" pressed.
-   Comes from readline/complete.c.  */
-static int
-get_y_or_n (void)
+/* TUI version of displayer.puts.  */
+
+static void
+tui_mld_puts (const struct match_list_displayer *displayer, const char *s)
 {
-  extern int _rl_abort_internal ();
-  int c;
+  tui_puts (s);
+}
 
-  for (;;)
-    {
-      c = rl_read_key ();
-      if (c == 'y' || c == 'Y' || c == ' ')
-	return (1);
-      if (c == 'n' || c == 'N' || c == RUBOUT)
-	return (0);
-      if (c == ABORT_CHAR)
-	_rl_abort_internal ();
-      beep ();
-    }
+/* TUI version of displayer.flush.  */
+
+static void
+tui_mld_flush (const struct match_list_displayer *displayer)
+{
+  wrefresh (TUI_CMD_WIN->generic.handle);
 }
 
-/* A convenience function for displaying a list of strings in
-   columnar format on readline's output stream.  MATCHES is the list
-   of strings, in argv format, LEN is the number of strings in MATCHES,
-   and MAX is the length of the longest string in MATCHES.
+/* TUI version of displayer.erase_entire_line.  */
 
-   Comes from readline/complete.c and modified to write in
-   the TUI command window using tui_putc/tui_puts.  */
 static void
-tui_rl_display_match_list (char **matches, int len, int max)
+tui_mld_erase_entire_line (const struct match_list_displayer *displayer)
 {
-  typedef int QSFUNC (const void *, const void *);
-  extern int _rl_qsort_string_compare (const void *, 
-				       const void *);
-  extern int _rl_print_completions_horizontally;
-  
-  int count, limit, printed_len;
-  int i, j, k, l;
-  const char *temp;
+  WINDOW *w = TUI_CMD_WIN->generic.handle;
 
-  /* Screen dimension correspond to the TUI command window.  */
-  int screenwidth = TUI_CMD_WIN->generic.width;
+  wmove (w, TUI_CMD_WIN->detail.command_info.cur_line, 0);
+  wclrtoeol (w);
+  wmove (w, TUI_CMD_WIN->detail.command_info.cur_line, 0);
+}
 
-  /* If there are many items, then ask the user if she really wants to
-     see them all.  */
-  if (len >= rl_completion_query_items)
-    {
-      char msg[256];
+/* TUI version of displayer.beep.  */
 
-      xsnprintf (msg, sizeof (msg),
-		 "\nDisplay all %d possibilities? (y or n)", len);
-      tui_puts (msg);
-      if (get_y_or_n () == 0)
-	{
-	  tui_puts ("\n");
-	  return;
-	}
-    }
+static void
+tui_mld_beep (const struct match_list_displayer *displayer)
+{
+  beep ();
+}
+
+/* Helper function for tui_mld_read_key.
+   This temporarily replaces tui_getc for use during tab-completion
+   match list display.  */
+
+static int
+tui_mld_getc (FILE *fp)
+{
+  WINDOW *w = TUI_CMD_WIN->generic.handle;
+  int c = wgetch (w);
 
-  /* How many items of MAX length can we fit in the screen window?  */
-  max += 2;
-  limit = screenwidth / max;
-  if (limit != 1 && (limit * max == screenwidth))
-    limit--;
+  c = tui_handle_resize_during_io (c, 1);
 
-  /* Avoid a possible floating exception.  If max > screenwidth, limit
-     will be 0 and a divide-by-zero fault will result.  */
-  if (limit == 0)
-    limit = 1;
+  return c;
+}
 
-  /* How many iterations of the printing loop?  */
-  count = (len + (limit - 1)) / limit;
+/* TUI version of displayer.read_key.  */
 
-  /* Watch out for special case.  If LEN is less than LIMIT, then
-     just do the inner printing loop.
-	   0 < len <= limit  implies  count = 1.  */
+static int
+tui_mld_read_key (const struct match_list_displayer *displayer)
+{
+  rl_getc_func_t *prev = rl_getc_function;
+  int c;
 
-  /* Sort the items if they are not already sorted.  */
-  if (rl_ignore_completion_duplicates == 0)
-    qsort (matches + 1, len, sizeof (char *),
-           (QSFUNC *)_rl_qsort_string_compare);
+  /* We can't use tui_getc as we need NEWLINE to not get emitted.  */
+  rl_getc_function = tui_mld_getc;
+  c = rl_read_key ();
+  rl_getc_function = prev;
+  return c;
+}
 
-  tui_putc ('\n');
+/* TUI version of rl_completion_display_matches_hook.
+   See gdb_display_match_list for a description of the arguments.  */
 
-  if (_rl_print_completions_horizontally == 0)
-    {
-      /* Print the sorted items, up-and-down alphabetically, like ls.  */
-      for (i = 1; i <= count; i++)
-	{
-	  for (j = 0, l = i; j < limit; j++)
-	    {
-	      if (l > len || matches[l] == 0)
-		break;
-	      else
-		{
-		  temp = printable_part (matches[l]);
-		  printed_len = print_filename (temp, matches[l]);
-
-		  if (j + 1 < limit)
-		    for (k = 0; k < max - printed_len; k++)
-		      tui_putc (' ');
-		}
-	      l += count;
-	    }
-	  tui_putc ('\n');
-	}
-    }
-  else
-    {
-      /* Print the sorted items, across alphabetically, like ls -x.  */
-      for (i = 1; matches[i]; i++)
-	{
-	  temp = printable_part (matches[i]);
-	  printed_len = print_filename (temp, matches[i]);
-	  /* Have we reached the end of this line?  */
-	  if (matches[i+1])
-	    {
-	      if (i && (limit > 1) && (i % limit) == 0)
-		tui_putc ('\n');
-	      else
-		for (k = 0; k < max - printed_len; k++)
-		  tui_putc (' ');
-	    }
-	}
-      tui_putc ('\n');
-    }
+static void
+tui_rl_display_match_list (char **matches, int len, int max)
+{
+  struct match_list_displayer displayer;
+
+  rl_get_screen_size (&displayer.height, &displayer.width);
+  displayer.crlf = tui_mld_crlf;
+  displayer.putch = tui_mld_putch;
+  displayer.puts = tui_mld_puts;
+  displayer.flush = tui_mld_flush;
+  displayer.erase_entire_line = tui_mld_erase_entire_line;
+  displayer.beep = tui_mld_beep;
+  displayer.read_key = tui_mld_read_key;
+
+  gdb_display_match_list (matches, len, max, &displayer);
 }
 
 /* Setup the IO for curses or non-curses mode.
@@ -679,7 +603,7 @@  tui_getc (FILE *fp)
 #endif
 
   ch = wgetch (w);
-  ch = tui_handle_resize_during_io (ch);
+  ch = tui_handle_resize_during_io (ch, 0);
 
   /* The \n must be echoed because it will not be printed by
      readline.  */
@@ -803,17 +727,21 @@  tui_expand_tabs (const char *string, int col)
 
 /* Cleanup when a resize has occured.
    Returns the character that must be processed.  */
-static unsigned int
-tui_handle_resize_during_io (unsigned int original_ch)
+
+static int
+tui_handle_resize_during_io (int original_ch, int for_completion)
 {
   if (tui_win_resized ())
     {
       tui_resize_all ();
       tui_refresh_all_win ();
-      dont_repeat ();
       tui_set_win_resized_to (FALSE);
-      return '\n';
+      if (!for_completion)
+	{
+	  dont_repeat ();
+	  return '\n';
+	}
     }
-  else
-    return original_ch;
+
+  return original_ch;
 }