From patchwork Wed Jan 21 16:57:56 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Doug Evans X-Patchwork-Id: 4755 Received: (qmail 5266 invoked by alias); 21 Jan 2015 16:59:04 -0000 Mailing-List: contact gdb-patches-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: gdb-patches-owner@sourceware.org Delivered-To: mailing list gdb-patches@sourceware.org Received: (qmail 5232 invoked by uid 89); 21 Jan 2015 16:59:00 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Found: No X-Spam-SWARE-Status: No, score=-2.0 required=5.0 tests=AWL, BAYES_00, FREEMAIL_ENVFROM_END_DIGIT, FREEMAIL_FROM, RCVD_IN_DNSWL_LOW, SPF_PASS autolearn=ham version=3.3.2 X-HELO: mail-pd0-f172.google.com Received: from mail-pd0-f172.google.com (HELO mail-pd0-f172.google.com) (209.85.192.172) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with (AES128-SHA encrypted) ESMTPS; Wed, 21 Jan 2015 16:58:47 +0000 Received: by mail-pd0-f172.google.com with SMTP id v10so29420828pde.3 for ; Wed, 21 Jan 2015 08:58:45 -0800 (PST) X-Received: by 10.70.89.129 with SMTP id bo1mr15914048pdb.23.1421859525688; Wed, 21 Jan 2015 08:58:45 -0800 (PST) Received: from seba.sebabeach.org.gmail.com (173-13-178-53-sfba.hfc.comcastbusiness.net. [173.13.178.53]) by mx.google.com with ESMTPSA id fz15sm3548530pdb.71.2015.01.21.08.58.43 for (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Wed, 21 Jan 2015 08:58:44 -0800 (PST) From: Doug Evans To: gdb-patches@sourceware.org Subject: [PATCH] Unify CLI/TUI tab-completion match list displayers Date: Wed, 21 Jan 2015 08:57:56 -0800 Message-ID: MIME-Version: 1.0 X-IsSubscribed: yes 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.] Certainly it would be nice if readline provided an interface so that we didn't have to dig into some internals and re-implement some of its functions. My plan is to get something working, and then once we have that then we have a complete story to present to readline, instead of trying to get changes into readline piecemeal. I've exercised every combination I can think of on my amd64-linux box. 2015-01-21 Doug Evans * 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. 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..88c8e16 100644 --- a/gdb/completer.c +++ b/gdb/completer.c @@ -1020,3 +1020,557 @@ 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. + + 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. */ + +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 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..af4d737 100644 --- a/gdb/completer.h +++ b/gdb/completer.h @@ -20,6 +20,51 @@ #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; + + /* The rubout/backspace operation. */ + 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 7e8a3bc..bac76c3 100644 --- a/gdb/tui/tui-io.c +++ b/gdb/tui/tui-io.c @@ -37,7 +37,7 @@ #include #include #include "filestuff.h" - +#include "completer.h" #include "gdb_curses.h" /* This redefines CTRL if it is not already defined, so it must come @@ -323,182 +323,103 @@ 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 (); +} - /* 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--; +/* Helper function for tui_mld_read_key. + This temporarily replaces tui_getc for use during tab-completion + match list display. */ - /* 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; +static int +tui_mld_getc (FILE *fp) +{ + WINDOW *w = TUI_CMD_WIN->generic.handle; - /* How many iterations of the printing loop? */ - count = (len + (limit - 1)) / limit; + return wgetch (w); +} - /* Watch out for special case. If LEN is less than LIMIT, then - just do the inner printing loop. - 0 < len <= limit implies count = 1. */ +/* TUI version of displayer.read_key. */ - /* 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); +static int +tui_mld_read_key (const struct match_list_displayer *displayer) +{ + rl_getc_func_t *prev = rl_getc_function; + int c; - tui_putc ('\n'); + /* 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; +} - 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'); - } +/* TUI version of rl_completion_display_matches_hook. + See gdb_display_match_list for a description of the arguments. */ + +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.