X-Git-Url: https://git.libre-soc.org/?a=blobdiff_plain;f=gdb%2Ftui%2Ftui-io.c;h=8cac1c40f1346d1ede7faeea2d82d99ec1917a39;hb=21152986f1418bc03368455fb247bf8b5ea0b2d6;hp=233e7a666710a2e8ee9c37fb1129358220683e2e;hpb=32d0add0a654c1204ab71dc8a55d9374538c4b33;p=binutils-gdb.git diff --git a/gdb/tui/tui-io.c b/gdb/tui/tui-io.c index 233e7a66671..8cac1c40f13 100644 --- a/gdb/tui/tui-io.c +++ b/gdb/tui/tui-io.c @@ -1,6 +1,6 @@ /* TUI support I/O functions. - Copyright (C) 1998-2015 Free Software Foundation, Inc. + Copyright (C) 1998-2022 Free Software Foundation, Inc. Contributed by Hewlett-Packard Company. @@ -21,7 +21,7 @@ #include "defs.h" #include "target.h" -#include "event-loop.h" +#include "gdbsupport/event-loop.h" #include "event-top.h" #include "command.h" #include "top.h" @@ -32,46 +32,34 @@ #include "tui/tui-win.h" #include "tui/tui-wingeneral.h" #include "tui/tui-file.h" +#include "tui/tui-out.h" #include "ui-out.h" #include "cli-out.h" #include #include -#include "filestuff.h" - +#ifdef __MINGW32__ +#include +#endif +#include "gdbsupport/filestuff.h" +#include "completer.h" #include "gdb_curses.h" +#include /* This redefines CTRL if it is not already defined, so it must come after terminal state releated include files like and "gdb_curses.h". */ #include "readline/readline.h" -int -key_is_start_sequence (int ch) -{ - return (ch == 27); -} +#ifdef __MINGW32__ +static SHORT ncurses_norm_attr; +#endif -int -key_is_end_sequence (int ch) -{ - return (ch == 126); -} +static int tui_getc (FILE *fp); -int -key_is_backspace (int ch) -{ - return (ch == 8); -} - -int -key_is_command_char (int ch) +static int +key_is_start_sequence (int ch) { - return ((ch == KEY_NPAGE) || (ch == KEY_PPAGE) - || (ch == KEY_LEFT) || (ch == KEY_RIGHT) - || (ch == KEY_UP) || (ch == KEY_DOWN) - || (ch == KEY_SF) || (ch == KEY_SR) - || (ch == (int)'\f') - || key_is_start_sequence (ch)); + return (ch == 27); } /* Use definition from readline 4.3. */ @@ -102,7 +90,7 @@ key_is_command_char (int ch) be garbled. This is implemented with a pipe that TUI reads and readline writes to. A gdb input handler is created so that reading the pipe is handled automatically. This will probably not work on - non-Unix platforms. The best fix is to make readline clean enougth + non-Unix platforms. The best fix is to make readline clean enough so that is never write on stdout. Note SCz/2002-09-01: we now use more readline hooks and it seems @@ -125,13 +113,14 @@ struct ui_out *tui_out; /* GDB output files in non-curses mode. */ static struct ui_file *tui_old_stdout; static struct ui_file *tui_old_stderr; -struct ui_out *tui_old_uiout; +cli_ui_out *tui_old_uiout; /* Readline previous hooks. */ static rl_getc_func_t *tui_old_rl_getc_function; static rl_voidfunc_t *tui_old_rl_redisplay_function; static rl_vintfunc_t *tui_old_rl_prep_terminal; static rl_voidfunc_t *tui_old_rl_deprep_terminal; +static rl_compdisp_func_t *tui_old_rl_display_matches_hook; static int tui_old_rl_echoing_p; /* Readline output stream. @@ -142,55 +131,416 @@ static FILE *tui_old_rl_outstream; static int tui_readline_pipe[2]; #endif -/* The last gdb prompt that was registered in readline. - This may be the main gdb prompt or a secondary prompt. */ -static char *tui_rl_saved_prompt; +/* Print a character in the curses command window. The output is + buffered. It is up to the caller to refresh the screen if + necessary. */ + +static void +do_tui_putc (WINDOW *w, char c) +{ + /* Expand TABs, since ncurses on MS-Windows doesn't. */ + if (c == '\t') + { + int col; + + col = getcurx (w); + do + { + waddch (w, ' '); + col++; + } + while ((col % 8) != 0); + } + else + waddch (w, c); +} -static unsigned int tui_handle_resize_during_io (unsigned int); +/* Update the cached value of the command window's start line based on + the window's current Y coordinate. */ + +static void +update_cmdwin_start_line () +{ + TUI_CMD_WIN->start_line = getcury (TUI_CMD_WIN->handle.get ()); +} + +/* Print a character in the curses command window. The output is + buffered. It is up to the caller to refresh the screen if + necessary. */ static void tui_putc (char c) { - char buf[2]; + do_tui_putc (TUI_CMD_WIN->handle.get (), c); + update_cmdwin_start_line (); +} + +/* This maps colors to their corresponding color index. */ + +static std::map color_map; + +/* This holds a pair of colors and is used to track the mapping + between a color pair index and the actual colors. */ + +struct color_pair +{ + int fg; + int bg; + + bool operator< (const color_pair &o) const + { + return fg < o.fg || (fg == o.fg && bg < o.bg); + } +}; + +/* This maps pairs of colors to their corresponding color pair + index. */ + +static std::map color_pair_map; + +/* This is indexed by ANSI color offset from the base color, and holds + the corresponding curses color constant. */ + +static const int curses_colors[] = { + COLOR_BLACK, + COLOR_RED, + COLOR_GREEN, + COLOR_YELLOW, + COLOR_BLUE, + COLOR_MAGENTA, + COLOR_CYAN, + COLOR_WHITE +}; + +/* Given a color, find its index. */ + +static bool +get_color (const ui_file_style::color &color, int *result) +{ + if (color.is_none ()) + *result = -1; + else if (color.is_basic ()) + *result = curses_colors[color.get_value ()]; + else + { + auto it = color_map.find (color); + if (it == color_map.end ()) + { + /* The first 8 colors are standard. */ + int next = color_map.size () + 8; + if (next >= COLORS) + return false; + uint8_t rgb[3]; + color.get_rgb (rgb); + /* We store RGB as 0..255, but curses wants 0..1000. */ + if (init_color (next, rgb[0] * 1000 / 255, rgb[1] * 1000 / 255, + rgb[2] * 1000 / 255) == ERR) + return false; + color_map[color] = next; + *result = next; + } + else + *result = it->second; + } + return true; +} + +/* The most recently emitted color pair. */ + +static int last_color_pair = -1; + +/* The most recently applied style. */ + +static ui_file_style last_style; + +/* If true, we're highlighting the current source line in reverse + video mode. */ +static bool reverse_mode_p = false; + +/* The background/foreground colors before we entered reverse + mode. */ +static ui_file_style::color reverse_save_bg (ui_file_style::NONE); +static ui_file_style::color reverse_save_fg (ui_file_style::NONE); + +/* Given two colors, return their color pair index; making a new one + if necessary. */ + +static int +get_color_pair (int fg, int bg) +{ + color_pair c = { fg, bg }; + auto it = color_pair_map.find (c); + if (it == color_pair_map.end ()) + { + /* Color pair 0 is our default color, so new colors start at + 1. */ + int next = color_pair_map.size () + 1; + /* Curses has a limited number of available color pairs. Fall + back to the default if we've used too many. */ + if (next >= COLOR_PAIRS) + return 0; + init_pair (next, fg, bg); + color_pair_map[c] = next; + return next; + } + return it->second; +} + +/* Apply STYLE to W. */ + +void +tui_apply_style (WINDOW *w, ui_file_style style) +{ + /* Reset. */ + wattron (w, A_NORMAL); + wattroff (w, A_BOLD); + wattroff (w, A_DIM); + wattroff (w, A_REVERSE); + if (last_color_pair != -1) + wattroff (w, COLOR_PAIR (last_color_pair)); + wattron (w, COLOR_PAIR (0)); + + const ui_file_style::color &fg = style.get_foreground (); + const ui_file_style::color &bg = style.get_background (); + if (!fg.is_none () || !bg.is_none ()) + { + int fgi, bgi; + if (get_color (fg, &fgi) && get_color (bg, &bgi)) + { +#ifdef __MINGW32__ + /* MS-Windows port of ncurses doesn't support implicit + default foreground and background colors, so we must + specify them explicitly when needed, using the colors we + saw at startup. */ + if (fgi == -1) + fgi = ncurses_norm_attr & 15; + if (bgi == -1) + bgi = (ncurses_norm_attr >> 4) & 15; +#endif + int pair = get_color_pair (fgi, bgi); + if (last_color_pair != -1) + wattroff (w, COLOR_PAIR (last_color_pair)); + wattron (w, COLOR_PAIR (pair)); + last_color_pair = pair; + } + } + + switch (style.get_intensity ()) + { + case ui_file_style::NORMAL: + break; + + case ui_file_style::BOLD: + wattron (w, A_BOLD); + break; + + case ui_file_style::DIM: + wattron (w, A_DIM); + break; + + default: + gdb_assert_not_reached ("invalid intensity"); + } + + if (style.is_reverse ()) + wattron (w, A_REVERSE); + + last_style = style; +} + +/* Apply an ANSI escape sequence from BUF to W. BUF must start with + the ESC character. If BUF does not start with an ANSI escape, + return 0. Otherwise, apply the sequence if it is recognized, or + simply ignore it if not. In this case, the number of bytes read + from BUF is returned. */ + +static size_t +apply_ansi_escape (WINDOW *w, const char *buf) +{ + ui_file_style style = last_style; + size_t n_read; + + if (!style.parse (buf, &n_read)) + return n_read; + + if (reverse_mode_p) + { + /* We want to reverse _only_ the default foreground/background + colors. If the foreground color is not the default (because + the text was styled), we want to leave it as is. If e.g., + the terminal is fg=BLACK, and bg=WHITE, and the style wants + to print text in RED, we want to reverse the background color + (print in BLACK), but still print the text in RED. To do + that, we enable the A_REVERSE attribute, and re-reverse the + parsed-style's fb/bg colors. + + Notes on the approach: + + - there's no portable way to know which colors the default + fb/bg colors map to. + + - this approach does the right thing even if you change the + terminal colors while GDB is running -- the reversed + colors automatically adapt. + */ + if (!style.is_default ()) + { + ui_file_style::color bg = style.get_background (); + ui_file_style::color fg = style.get_foreground (); + style.set_fg (bg); + style.set_bg (fg); + } + + /* Enable A_REVERSE. */ + style.set_reverse (true); + } + + tui_apply_style (w, style); + return n_read; +} + +/* See tui.io.h. */ + +void +tui_set_reverse_mode (WINDOW *w, bool reverse) +{ + ui_file_style style = last_style; + + reverse_mode_p = reverse; + style.set_reverse (reverse); + + if (reverse) + { + reverse_save_bg = style.get_background (); + reverse_save_fg = style.get_foreground (); + } + else + { + style.set_bg (reverse_save_bg); + style.set_fg (reverse_save_fg); + } - buf[0] = c; - buf[1] = 0; - tui_puts (buf); + tui_apply_style (w, style); } -/* Print the string in the curses command window. */ +/* Print LENGTH characters from the buffer pointed to by BUF to the + curses command window. The output is buffered. It is up to the + caller to refresh the screen if necessary. */ + void -tui_puts (const char *string) +tui_write (const char *buf, size_t length) +{ + /* We need this to be \0-terminated for the regexp matching. */ + std::string copy (buf, length); + tui_puts (copy.c_str ()); +} + +/* Print a string in the curses command window. The output is + buffered. It is up to the caller to refresh the screen if + necessary. */ + +void +tui_puts (const char *string, WINDOW *w) +{ + if (w == nullptr) + w = TUI_CMD_WIN->handle.get (); + + while (true) + { + const char *next = strpbrk (string, "\n\1\2\033\t"); + + /* Print the plain text prefix. */ + size_t n_chars = next == nullptr ? strlen (string) : next - string; + if (n_chars > 0) + waddnstr (w, string, n_chars); + + /* We finished. */ + if (next == nullptr) + break; + + char c = *next; + switch (c) + { + case '\1': + case '\2': + /* Ignore these, they are readline escape-marking + sequences. */ + ++next; + break; + + case '\n': + case '\t': + do_tui_putc (w, c); + ++next; + break; + + case '\033': + { + size_t bytes_read = apply_ansi_escape (w, next); + if (bytes_read > 0) + next += bytes_read; + else + { + /* Just drop the escape. */ + ++next; + } + } + break; + + default: + gdb_assert_not_reached ("missing case in tui_puts"); + } + + string = next; + } + + if (TUI_CMD_WIN != nullptr && w == TUI_CMD_WIN->handle.get ()) + update_cmdwin_start_line (); +} + +static void +tui_puts_internal (WINDOW *w, const char *string, int *height) { - static int tui_skip_line = -1; char c; - WINDOW *w; + int prev_col = 0; + bool saw_nl = false; - w = TUI_CMD_WIN->generic.handle; while ((c = *string++) != 0) { - /* Catch annotation and discard them. We need two \032 and - discard until a \n is seen. */ - if (c == '\032') - { - tui_skip_line++; - } - else if (tui_skip_line != 1) - { - tui_skip_line = -1; - waddch (w, c); - } - else if (c == '\n') - tui_skip_line = -1; - } - getyx (w, TUI_CMD_WIN->detail.command_info.cur_line, - TUI_CMD_WIN->detail.command_info.curch); - TUI_CMD_WIN->detail.command_info.start_line - = TUI_CMD_WIN->detail.command_info.cur_line; + if (c == '\n') + saw_nl = true; - /* We could defer the following. */ - wrefresh (w); - fflush (stdout); + if (c == '\1' || c == '\2') + { + /* Ignore these, they are readline escape-marking + sequences. */ + } + else + { + if (c == '\033') + { + size_t bytes_read = apply_ansi_escape (w, string - 1); + if (bytes_read > 0) + { + string = string + bytes_read - 1; + continue; + } + } + do_tui_putc (w, c); + + if (height != nullptr) + { + int col = getcurx (w); + if (col <= prev_col) + ++*height; + prev_col = col; + } + } + } + if (TUI_CMD_WIN != nullptr && w == TUI_CMD_WIN->handle.get ()) + update_cmdwin_start_line (); + if (saw_nl) + wrefresh (w); } /* Readline callback. @@ -201,12 +551,12 @@ tui_redisplay_readline (void) { int prev_col; int height; - int col, line; + int col; int c_pos; int c_line; int in; WINDOW *w; - char *prompt; + const char *prompt; int start_line; /* Detect when we temporarily left SingleKey and now the readline @@ -215,68 +565,69 @@ tui_redisplay_readline (void) The command could call prompt_for_continue and we must not restore SingleKey so that the prompt and normal keymap are used. */ if (tui_current_key_mode == TUI_ONE_COMMAND_MODE && rl_end == 0 - && immediate_quit == 0) + && !gdb_in_secondary_prompt_p (current_ui)) tui_set_key_mode (TUI_SINGLE_KEY_MODE); if (tui_current_key_mode == TUI_SINGLE_KEY_MODE) prompt = ""; else - prompt = tui_rl_saved_prompt; + prompt = rl_display_prompt; c_pos = -1; c_line = -1; - w = TUI_CMD_WIN->generic.handle; - start_line = TUI_CMD_WIN->detail.command_info.start_line; + w = TUI_CMD_WIN->handle.get (); + start_line = TUI_CMD_WIN->start_line; wmove (w, start_line, 0); prev_col = 0; height = 1; - for (in = 0; prompt && prompt[in]; in++) - { - waddch (w, prompt[in]); - getyx (w, line, col); - if (col < prev_col) - height++; - prev_col = col; - } - for (in = 0; in < rl_end; in++) + if (prompt != nullptr) + tui_puts_internal (w, prompt, &height); + + prev_col = getcurx (w); + for (in = 0; in <= rl_end; in++) { unsigned char c; - c = (unsigned char) rl_line_buffer[in]; if (in == rl_point) { - getyx (w, c_line, c_pos); + getyx (w, c_line, c_pos); } + if (in == rl_end) + break; + + c = (unsigned char) rl_line_buffer[in]; if (CTRL_CHAR (c) || c == RUBOUT) { - waddch (w, '^'); - waddch (w, CTRL_CHAR (c) ? UNCTRL (c) : '?'); + waddch (w, '^'); + waddch (w, CTRL_CHAR (c) ? UNCTRL (c) : '?'); + } + else if (c == '\t') + { + /* Expand TABs, since ncurses on MS-Windows doesn't. */ + col = getcurx (w); + do + { + waddch (w, ' '); + col++; + } while ((col % 8) != 0); } else { - waddch (w, c); + waddch (w, c); } if (c == '\n') - { - getyx (w, TUI_CMD_WIN->detail.command_info.start_line, - TUI_CMD_WIN->detail.command_info.curch); - } - getyx (w, line, col); + TUI_CMD_WIN->start_line = getcury (w); + col = getcurx (w); if (col < prev_col) - height++; + height++; prev_col = col; } wclrtobot (w); - getyx (w, TUI_CMD_WIN->detail.command_info.start_line, - TUI_CMD_WIN->detail.command_info.curch); + TUI_CMD_WIN->start_line = getcury (w); if (c_line >= 0) - { - wmove (w, c_line, c_pos); - TUI_CMD_WIN->detail.command_info.cur_line = c_line; - TUI_CMD_WIN->detail.command_info.curch = c_pos; - } - TUI_CMD_WIN->detail.command_info.start_line -= height - 1; + wmove (w, c_line, c_pos); + TUI_CMD_WIN->start_line -= height - 1; wrefresh (w); fflush(stdout); @@ -288,11 +639,9 @@ tui_redisplay_readline (void) static void tui_prep_terminal (int notused1) { - /* Save the prompt registered in readline to correctly display it. - (we can't use gdb_prompt() due to secondary prompts and can't use - rl_prompt because it points to an alloca buffer). */ - xfree (tui_rl_saved_prompt); - tui_rl_saved_prompt = rl_prompt != NULL ? xstrdup (rl_prompt) : NULL; +#ifdef NCURSES_MOUSE_VERSION + mousemask (ALL_MOUSE_EVENTS, NULL); +#endif } /* Readline callback to restore the terminal. It is called once each @@ -300,6 +649,9 @@ tui_prep_terminal (int notused1) static void tui_deprep_terminal (void) { +#ifdef NCURSES_MOUSE_VERSION + mousemask (0, NULL); +#endif } #ifdef TUI_USE_PIPE_FOR_READLINE @@ -320,182 +672,120 @@ 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. - - Comes from readline/complete.c. */ -static const char * -printable_part (const char *pathname) -{ - return rl_filename_completion_desired ? lbasename (pathname) : pathname; -} - -/* 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.crlf. */ -static int -print_filename (const char *to_print, const char *full_pathname) +static void +tui_mld_crlf (const struct match_list_displayer *displayer) { - int printed_len = 0; - const char *s; + tui_putc ('\n'); +} - for (s = to_print; *s; s++) - { - PUTX (*s); - } - return printed_len; +/* TUI version of displayer.putch. */ + +static void +tui_mld_putch (const struct match_list_displayer *displayer, int ch) +{ + 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->handle.get ()); } -/* 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->handle.get (); + int cur_y = getcury (w); - /* Screen dimension correspond to the TUI command window. */ - int screenwidth = TUI_CMD_WIN->generic.width; + wmove (w, cur_y, 0); + wclrtoeol (w); + wmove (w, cur_y, 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--; +/* A wrapper for wgetch that enters nonl mode. We We normally want + curses' "nl" mode, but when reading from the user, we'd like to + differentiate between C-j and C-m, because some users bind these + keys differently in their .inputrc. So, put curses into nonl mode + just when reading from the user. See PR tui/20819. */ - /* 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 +gdb_wgetch (WINDOW *win) +{ + nonl (); + int r = wgetch (win); + nl (); + return r; +} - /* How many iterations of the printing loop? */ - count = (len + (limit - 1)) / limit; +/* Helper function for tui_mld_read_key. + This temporarily replaces tui_getc for use during tab-completion + match list display. */ - /* 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_getc (FILE *fp) +{ + WINDOW *w = TUI_CMD_WIN->handle.get (); + int c = gdb_wgetch (w); - /* 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); + return c; +} - tui_putc ('\n'); +/* TUI version of displayer.read_key. */ - 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]); +static int +tui_mld_read_key (const struct match_list_displayer *displayer) +{ + rl_getc_func_t *prev = rl_getc_function; + int c; - 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'); - } + /* 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 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. @@ -513,11 +803,16 @@ tui_setup_io (int mode) if (mode) { + /* Ensure that readline has been initialized before saving any + of its variables. */ + tui_ensure_readline_initialized (); + /* Redirect readline to TUI. */ tui_old_rl_redisplay_function = rl_redisplay_function; tui_old_rl_deprep_terminal = rl_deprep_term_function; tui_old_rl_prep_terminal = rl_prep_term_function; tui_old_rl_getc_function = rl_getc_function; + tui_old_rl_display_matches_hook = rl_completion_display_matches_hook; tui_old_rl_outstream = rl_outstream; tui_old_rl_echoing_p = _rl_echoing_p; rl_redisplay_function = tui_redisplay_readline; @@ -533,7 +828,8 @@ tui_setup_io (int mode) /* Keep track of previous gdb output. */ tui_old_stdout = gdb_stdout; tui_old_stderr = gdb_stderr; - tui_old_uiout = current_uiout; + tui_old_uiout = dynamic_cast (current_uiout); + gdb_assert (tui_old_uiout != nullptr); /* Reconfigure gdb output. */ gdb_stdout = tui_stdout; @@ -561,13 +857,19 @@ tui_setup_io (int mode) rl_deprep_term_function = tui_old_rl_deprep_terminal; rl_prep_term_function = tui_old_rl_prep_terminal; rl_getc_function = tui_old_rl_getc_function; + rl_completion_display_matches_hook = tui_old_rl_display_matches_hook; rl_outstream = tui_old_rl_outstream; - rl_completion_display_matches_hook = 0; _rl_echoing_p = tui_old_rl_echoing_p; rl_already_prompted = 0; /* Save tty for SIGCONT. */ savetty (); + + /* Clean up color information. */ + last_color_pair = -1; + last_style = ui_file_style (); + color_map.clear (); + color_pair_map.clear (); } } @@ -579,17 +881,11 @@ tui_cont_sig (int sig) if (tui_active) { /* Restore the terminal setting because another process (shell) - might have changed it. */ + might have changed it. */ resetty (); /* Force a refresh of the screen. */ tui_refresh_all_win (); - - /* Update cursor position on the screen. */ - wmove (TUI_CMD_WIN->generic.handle, - TUI_CMD_WIN->detail.command_info.start_line, - TUI_CMD_WIN->detail.command_info.curch); - wrefresh (TUI_CMD_WIN->generic.handle); } signal (sig, tui_cont_sig); } @@ -604,8 +900,8 @@ tui_initialize_io (void) #endif /* Create tui output streams. */ - tui_stdout = tui_fileopen (stdout); - tui_stderr = tui_fileopen (stderr); + tui_stdout = new tui_file (stdout); + tui_stderr = new tui_file (stderr); tui_out = tui_out_new (tui_stdout); /* Create the default UI. */ @@ -622,7 +918,7 @@ tui_initialize_io (void) if (tui_rl_outstream == 0) error (_("Cannot redirect readline output")); - setvbuf (tui_rl_outstream, (char*) NULL, _IOLBF, 0); + setvbuf (tui_rl_outstream, NULL, _IOLBF, 0); #ifdef O_NONBLOCK (void) fcntl (tui_readline_pipe[0], F_SETFL, O_NONBLOCK); @@ -631,73 +927,298 @@ tui_initialize_io (void) (void) fcntl (tui_readline_pipe[0], F_SETFL, O_NDELAY); #endif #endif - add_file_handler (tui_readline_pipe[0], tui_readline_output, 0); + add_file_handler (tui_readline_pipe[0], tui_readline_output, 0, "tui"); #else tui_rl_outstream = stdout; #endif + +#ifdef __MINGW32__ + /* MS-Windows port of ncurses doesn't support default foreground and + background colors, so we must record the default colors at startup. */ + HANDLE hstdout = (HANDLE)_get_osfhandle (fileno (stdout)); + DWORD cmode; + CONSOLE_SCREEN_BUFFER_INFO csbi; + + if (hstdout != INVALID_HANDLE_VALUE + && GetConsoleMode (hstdout, &cmode) != 0 + && GetConsoleScreenBufferInfo (hstdout, &csbi)) + ncurses_norm_attr = csbi.wAttributes; +#endif } -/* Get a character from the command window. This is called from the - readline package. */ -int -tui_getc (FILE *fp) +/* Dispatch the correct tui function based upon the mouse event. */ + +#ifdef NCURSES_MOUSE_VERSION + +static void +tui_dispatch_mouse_event () +{ + MEVENT mev; + if (getmouse (&mev) != OK) + return; + + for (tui_win_info *wi : all_tui_windows ()) + if (mev.x > wi->x && mev.x < wi->x + wi->width - 1 + && mev.y > wi->y && mev.y < wi->y + wi->height - 1) + { + if ((mev.bstate & BUTTON1_CLICKED) != 0 + || (mev.bstate & BUTTON2_CLICKED) != 0 + || (mev.bstate & BUTTON3_CLICKED) != 0) + { + int button = (mev.bstate & BUTTON1_CLICKED) != 0 ? 1 + : ((mev.bstate & BUTTON2_CLICKED) != 0 ? 2 + : 3); + wi->click (mev.x - wi->x - 1, mev.y - wi->y - 1, button); + } +#ifdef BUTTON5_PRESSED + else if ((mev.bstate & BUTTON4_PRESSED) != 0) + wi->backward_scroll (3); + else if ((mev.bstate & BUTTON5_PRESSED) != 0) + wi->forward_scroll (3); +#endif + break; + } +} + +#endif + +/* Dispatch the correct tui function based upon the control + character. */ +static unsigned int +tui_dispatch_ctrl_char (unsigned int ch) +{ + struct tui_win_info *win_info = tui_win_with_focus (); + + /* If no window has the focus, or if the focus window can't scroll, + just pass the character through. */ + if (win_info == NULL || !win_info->can_scroll ()) + return ch; + + switch (ch) + { + case KEY_NPAGE: + win_info->forward_scroll (0); + break; + case KEY_PPAGE: + win_info->backward_scroll (0); + break; + case KEY_DOWN: + case KEY_SF: + win_info->forward_scroll (1); + break; + case KEY_UP: + case KEY_SR: + win_info->backward_scroll (1); + break; + case KEY_RIGHT: + win_info->left_scroll (1); + break; + case KEY_LEFT: + win_info->right_scroll (1); + break; + default: + /* We didn't recognize the character as a control character, so pass it + through. */ + return ch; + } + + /* We intercepted the control character, so return 0 (which readline + will interpret as a no-op). */ + return 0; +} + +/* See tui-io.h. */ + +void +tui_inject_newline_into_command_window () +{ + gdb_assert (tui_active); + + WINDOW *w = TUI_CMD_WIN->handle.get (); + + /* When hitting return with an empty input, gdb executes the last + command. If we emit a newline, this fills up the command window + with empty lines with gdb prompt at beginning. Instead of that, + stay on the same line but provide a visual effect to show the + user we recognized the command. */ + if (rl_end == 0 && !gdb_in_secondary_prompt_p (current_ui)) + { + wmove (w, getcury (w), 0); + + /* Clear the line. This will blink the gdb prompt since + it will be redrawn at the same line. */ + wclrtoeol (w); + wrefresh (w); + napms (20); + } + else + { + /* Move cursor to the end of the command line before emitting the + newline. We need to do so because when ncurses outputs a newline + it truncates any text that appears past the end of the cursor. */ + int px, py; + getyx (w, py, px); + px += rl_end - rl_point; + py += px / TUI_CMD_WIN->width; + px %= TUI_CMD_WIN->width; + wmove (w, py, px); + tui_putc ('\n'); + } +} + +/* If we're passing an escape sequence to readline, this points to a + string holding the remaining characters of the sequence to pass. + We advance the pointer one character at a time until '\0' is + reached. */ +static const char *cur_seq = nullptr; + +/* Set CUR_SEQ to point at the current sequence to pass to readline, + setup to call the input handler again so we complete the sequence + shortly, and return the first character to start the sequence. */ + +static int +start_sequence (const char *seq) +{ + call_stdin_event_handler_again_p = 1; + cur_seq = seq + 1; + return seq[0]; +} + +/* Main worker for tui_getc. Get a character from the command window. + This is called from the readline package, but wrapped in a + try/catch by tui_getc. */ + +static int +tui_getc_1 (FILE *fp) { int ch; WINDOW *w; - w = TUI_CMD_WIN->generic.handle; + w = TUI_CMD_WIN->handle.get (); #ifdef TUI_USE_PIPE_FOR_READLINE /* Flush readline output. */ tui_readline_output (0, 0); #endif - ch = wgetch (w); - ch = tui_handle_resize_during_io (ch); - - /* The \n must be echoed because it will not be printed by - readline. */ - if (ch == '\n') + /* We enable keypad mode so that curses's wgetch processes mouse + escape sequences. In keypad mode, wgetch also processes the + escape sequences for keys such as up/down etc. and returns KEY_UP + / KEY_DOWN etc. When we have the focus on the command window + though, we want to pass the raw up/down etc. escape codes to + readline so readline understands them. */ + if (cur_seq != nullptr) { - /* When hitting return with an empty input, gdb executes the last - command. If we emit a newline, this fills up the command window - with empty lines with gdb prompt at beginning. Instead of that, - stay on the same line but provide a visual effect to show the - user we recognized the command. */ - if (rl_end == 0) - { - wmove (w, TUI_CMD_WIN->detail.command_info.cur_line, 0); - - /* Clear the line. This will blink the gdb prompt since - it will be redrawn at the same line. */ - wclrtoeol (w); - wrefresh (w); - napms (20); - } + ch = *cur_seq++; + + /* If we've reached the end of the string, we're done with the + sequence. Otherwise, setup to get back here again for + another character. */ + if (*cur_seq == '\0') + cur_seq = nullptr; else - { - wmove (w, TUI_CMD_WIN->detail.command_info.cur_line, - TUI_CMD_WIN->detail.command_info.curch); - waddch (w, ch); - } + call_stdin_event_handler_again_p = 1; + return ch; } - - if (key_is_command_char (ch)) - { /* Handle prev/next/up/down here. */ - ch = tui_dispatch_ctrl_char (ch); + else + ch = gdb_wgetch (w); + + /* Handle prev/next/up/down here. */ + ch = tui_dispatch_ctrl_char (ch); + +#ifdef NCURSES_MOUSE_VERSION + if (ch == KEY_MOUSE) + { + tui_dispatch_mouse_event (); + return 0; } - - if (ch == '\n' || ch == '\r' || ch == '\f') - TUI_CMD_WIN->detail.command_info.curch = 0; +#endif + + /* Translate curses keys back to escape sequences so that readline + can understand them. We do this irrespective of which window has + the focus. If e.g., we're focused on a non-command window, then + the up/down keys will already have been filtered by + tui_dispatch_ctrl_char. Keys that haven't been intercepted will + be passed down to readline. */ + if (current_ui->command_editing) + { + /* For the standard arrow keys + home/end, hardcode sequences + readline understands. See bind_arrow_keys_internal in + readline/readline.c. */ + switch (ch) + { + case KEY_UP: + return start_sequence ("\033[A"); + case KEY_DOWN: + return start_sequence ("\033[B"); + case KEY_RIGHT: + return start_sequence ("\033[C"); + case KEY_LEFT: + return start_sequence ("\033[D"); + case KEY_HOME: + return start_sequence ("\033[H"); + case KEY_END: + return start_sequence ("\033[F"); + + /* del and ins are unfortunately not hardcoded in readline for + all systems. */ + + case KEY_DC: /* del */ +#ifdef __MINGW32__ + return start_sequence ("\340S"); +#else + return start_sequence ("\033[3~"); +#endif + + case KEY_IC: /* ins */ +#if defined __MINGW32__ + return start_sequence ("\340R"); +#else + return start_sequence ("\033[2~"); +#endif + } + + /* Keycodes above KEY_MAX are not garanteed to be stable. + Compare keyname instead. */ + if (ch >= KEY_MAX) + { + auto name = gdb::string_view (keyname (ch)); + + /* The following sequences are hardcoded in readline as + well. */ + + /* ctrl-arrow keys */ + if (name == "kLFT5") /* ctrl-left */ + return start_sequence ("\033[1;5D"); + else if (name == "kRIT5") /* ctrl-right */ + return start_sequence ("\033[1;5C"); + else if (name == "kDC5") /* ctrl-del */ + return start_sequence ("\033[3;5~"); + + /* alt-arrow keys */ + else if (name == "kLFT3") /* alt-left */ + return start_sequence ("\033[1;3D"); + else if (name == "kRIT3") /* alt-right */ + return start_sequence ("\033[1;3C"); + } + } + + /* Handle the CTRL-L refresh for each window. */ + if (ch == '\f') + { + tui_refresh_all_win (); + return ch; + } + if (ch == KEY_BACKSPACE) return '\b'; - if (async_command_editing_p && key_is_start_sequence (ch)) + if (current_ui->command_editing && key_is_start_sequence (ch)) { int ch_pending; nodelay (w, TRUE); - ch_pending = wgetch (w); + ch_pending = gdb_wgetch (w); nodelay (w, FALSE); /* If we have pending input following a start sequence, call the stdin @@ -718,23 +1239,35 @@ tui_getc (FILE *fp) } } + if (ch > 0xff) + { + /* Readline doesn't understand non-8-bit curses keys, filter + them out. */ + return 0; + } + return ch; } +/* Get a character from the command window. This is called from the + readline package. */ -/* 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_getc (FILE *fp) { - if (tui_win_resized ()) + try { - tui_resize_all (); - tui_refresh_all_win (); - dont_repeat (); - tui_set_win_resized_to (FALSE); - return '\n'; + return tui_getc_1 (fp); + } + catch (const gdb_exception &ex) + { + /* Just in case, don't ever let an exception escape to readline. + This shouldn't ever happen, but if it does, print the + exception instead of just crashing GDB. */ + exception_print (gdb_stderr, ex); + + /* If we threw an exception, it's because we recognized the + character. */ + return 0; } - else - return original_ch; }