/* TUI support I/O functions.
- Copyright (C) 1998-2015 Free Software Foundation, Inc.
+ Copyright (C) 1998-2019 Free Software Foundation, Inc.
Contributed by Hewlett-Packard Company.
#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 <fcntl.h>
#include <signal.h>
-#include "filestuff.h"
+#ifdef __MINGW32__
+#include <windows.h>
+#endif
+#include "common/filestuff.h"
#include "completer.h"
#include "gdb_curses.h"
+#include <map>
/* This redefines CTRL if it is not already defined, so it must come
after terminal state releated include files like <term.h> and
"gdb_curses.h". */
#include "readline/readline.h"
-int
-key_is_start_sequence (int ch)
-{
- return (ch == 27);
-}
-
-int
-key_is_end_sequence (int ch)
-{
- return (ch == 126);
-}
+#ifdef __MINGW32__
+static SHORT ncurses_norm_attr;
+#endif
-int
-key_is_backspace (int ch)
-{
- return (ch == 8);
-}
+static int tui_getc (FILE *fp);
-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. */
/* 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.
This may be the main gdb prompt or a secondary prompt. */
static char *tui_rl_saved_prompt;
-static int tui_handle_resize_during_io (int, int);
+/* 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)
+{
+ static int tui_skip_line = -1;
+
+ /* 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;
+ /* 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);
+ }
+ else if (c == '\n')
+ tui_skip_line = -1;
+}
+
+/* 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->detail.command_info.start_line
+ = getcury (TUI_CMD_WIN->generic.handle);
+}
+
+/* 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];
+ WINDOW *w = TUI_CMD_WIN->generic.handle;
+
+ do_tui_putc (w, c);
+ update_cmdwin_start_line ();
+}
+
+/* This maps colors to their corresponding color index. */
+
+static std::map<ui_file_style::color, int> 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, int> 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. */
+
+static void
+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);
+ }
- buf[0] = c;
- buf[1] = 0;
- tui_puts (buf);
+ /* Enable A_REVERSE. */
+ style.set_reverse (true);
+ }
+
+ apply_style (w, style);
+ return n_read;
}
-/* Print the string in the curses command window. */
+/* See tui.io.h. */
+
void
-tui_puts (const char *string)
+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);
+ }
+
+ apply_style (w, style);
+}
+
+/* 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_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 ());
+}
+
+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;
- 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;
- /* Expand TABs, since ncurses on MS-Windows doesn't. */
- if (c == '\t')
+ if (c == '\1' || c == '\2')
+ {
+ /* Ignore these, they are readline escape-marking
+ sequences. */
+ }
+ else
+ {
+ if (c == '\033')
{
- int line, col;
-
- getyx (w, line, col);
- do
+ size_t bytes_read = apply_ansi_escape (w, string - 1);
+ if (bytes_read > 0)
{
- waddch (w, ' ');
- col++;
- } while ((col % 8) != 0);
+ string = string + bytes_read - 1;
+ continue;
+ }
}
- else
- waddch (w, c);
- }
- else if (c == '\n')
- tui_skip_line = -1;
+ do_tui_putc (w, c);
+
+ if (height != nullptr)
+ {
+ int col = getcurx (w);
+ if (col <= prev_col)
+ ++*height;
+ prev_col = col;
+ }
+ }
}
- 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;
+ update_cmdwin_start_line ();
+}
- /* We could defer the following. */
- wrefresh (w);
- fflush (stdout);
+/* 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->generic.handle;
+ tui_puts_internal (w, string, nullptr);
}
/* Readline callback.
{
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
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)
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;
- }
+ if (prompt != nullptr)
+ tui_puts_internal (TUI_CMD_WIN->generic.handle, prompt, &height);
+
+ prev_col = getcurx (w);
for (in = 0; in <= rl_end; in++)
{
unsigned char c;
else if (c == '\t')
{
/* Expand TABs, since ncurses on MS-Windows doesn't. */
- getyx (w, line, col);
+ col = getcurx (w);
do
{
waddch (w, ' ');
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->detail.command_info.start_line = getcury (w);
+ col = getcurx (w);
if (col < prev_col)
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->detail.command_info.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;
- }
+ wmove (w, c_line, c_pos);
TUI_CMD_WIN->detail.command_info.start_line -= height - 1;
wrefresh (w);
tui_mld_erase_entire_line (const struct match_list_displayer *displayer)
{
WINDOW *w = TUI_CMD_WIN->generic.handle;
+ int cur_y = getcury (w);
- wmove (w, TUI_CMD_WIN->detail.command_info.cur_line, 0);
+ wmove (w, cur_y, 0);
wclrtoeol (w);
- wmove (w, TUI_CMD_WIN->detail.command_info.cur_line, 0);
+ wmove (w, cur_y, 0);
}
/* TUI version of displayer.beep. */
beep ();
}
+/* 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. */
+
+static int
+gdb_wgetch (WINDOW *win)
+{
+ nonl ();
+ int r = wgetch (win);
+ nl ();
+ /* In nonl mode, if the user types Enter, it will not be echoed
+ properly. This will result in gdb output appearing immediately
+ after the command. So, if we read \r, emit a \r now, after nl
+ mode has been re-entered, so that the output looks correct. */
+ if (r == '\r')
+ puts ("\r");
+ return r;
+}
+
/* Helper function for tui_mld_read_key.
This temporarily replaces tui_getc for use during tab-completion
match list display. */
tui_mld_getc (FILE *fp)
{
WINDOW *w = TUI_CMD_WIN->generic.handle;
- int c = wgetch (w);
-
- c = tui_handle_resize_during_io (c, 1);
+ int c = gdb_wgetch (w);
return c;
}
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;
/* 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<cli_ui_out *> (current_uiout);
+ gdb_assert (tui_old_uiout != nullptr);
/* Reconfigure gdb output. */
gdb_stdout = tui_stdout;
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 ();
}
}
/* 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);
#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. */
#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
+static int
tui_getc (FILE *fp)
{
int ch;
tui_readline_output (0, 0);
#endif
- ch = wgetch (w);
- ch = tui_handle_resize_during_io (ch, 0);
+ ch = gdb_wgetch (w);
/* The \n must be echoed because it will not be printed by
readline. */
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)
+ if (rl_end == 0 && !gdb_in_secondary_prompt_p (current_ui))
{
- wmove (w, TUI_CMD_WIN->detail.command_info.cur_line, 0);
+ wmove (w, getcury (w), 0);
/* Clear the line. This will blink the gdb prompt since
it will be redrawn at the same line. */
}
else
{
- wmove (w, TUI_CMD_WIN->detail.command_info.cur_line,
- TUI_CMD_WIN->detail.command_info.curch);
- waddch (w, ch);
+ /* 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->generic.width;
+ px %= TUI_CMD_WIN->generic.width;
+ wmove (w, py, px);
+ tui_putc ('\n');
}
}
- if (key_is_command_char (ch))
- { /* Handle prev/next/up/down here. */
- ch = tui_dispatch_ctrl_char (ch);
- }
+ /* Handle prev/next/up/down here. */
+ ch = tui_dispatch_ctrl_char (ch);
- if (ch == '\n' || ch == '\r' || ch == '\f')
- TUI_CMD_WIN->detail.command_info.curch = 0;
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
char *
tui_expand_tabs (const char *string, int col)
{
- int n_adjust;
+ int n_adjust, ncol;
const char *s;
char *ret, *q;
/* 1. How many additional characters do we need? */
- for (n_adjust = 0, s = string; s; )
+ for (ncol = col, n_adjust = 0, s = string; s; )
{
s = strpbrk (s, "\t");
if (s)
{
- col += (s - string) + n_adjust;
+ ncol += (s - string) + n_adjust;
/* Adjustment for the next tab stop, minus one for the TAB
we replace with spaces. */
- n_adjust += 8 - (col % 8) - 1;
+ n_adjust += 8 - (ncol % 8) - 1;
s++;
}
}
/* Allocate the copy. */
- ret = q = xmalloc (strlen (string) + n_adjust + 1);
+ ret = q = (char *) xmalloc (strlen (string) + n_adjust + 1);
/* 2. Copy the original string while replacing TABs with spaces. */
- for (s = string; s; )
+ for (ncol = col, s = string; s; )
{
- char *s1 = strpbrk (s, "\t");
+ const char *s1 = strpbrk (s, "\t");
if (s1)
{
if (s1 > s)
{
strncpy (q, s, s1 - s);
q += s1 - s;
- col += s1 - s;
+ ncol += s1 - s;
}
do {
*q++ = ' ';
- col++;
- } while ((col % 8) != 0);
+ ncol++;
+ } while ((ncol % 8) != 0);
s1++;
}
else
return ret;
}
-
-/* Cleanup when a resize has occured.
- Returns the character that must be processed. */
-
-static int
-tui_handle_resize_during_io (int original_ch, int for_completion)
-{
- if (tui_win_resized ())
- {
- tui_resize_all ();
- tui_refresh_all_win ();
- tui_set_win_resized_to (FALSE);
- if (!for_completion)
- {
- dont_repeat ();
- return '\n';
- }
- }
-
- return original_ch;
-}