mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2024-11-27 03:51:15 +08:00
855dc4f2ac
This TUI code calls fflush on stdout, but I don't believe this is useful in gdb.
1286 lines
33 KiB
C
1286 lines
33 KiB
C
/* TUI support I/O functions.
|
|
|
|
Copyright (C) 1998-2024 Free Software Foundation, Inc.
|
|
|
|
Contributed by Hewlett-Packard Company.
|
|
|
|
This file is part of GDB.
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
|
|
|
#include "exceptions.h"
|
|
#include "target.h"
|
|
#include "gdbsupport/event-loop.h"
|
|
#include "event-top.h"
|
|
#include "top.h"
|
|
#include "ui.h"
|
|
#include "tui/tui.h"
|
|
#include "tui/tui-data.h"
|
|
#include "tui/tui-io.h"
|
|
#include "tui/tui-command.h"
|
|
#include "tui/tui-win.h"
|
|
#include "tui/tui-wingeneral.h"
|
|
#include "tui/tui-file.h"
|
|
#include "ui-out.h"
|
|
#include "cli-out.h"
|
|
#include <fcntl.h>
|
|
#include <signal.h>
|
|
#ifdef __MINGW32__
|
|
#include <windows.h>
|
|
#endif
|
|
#include "gdbsupport/filestuff.h"
|
|
#include "completer.h"
|
|
#include "gdb_curses.h"
|
|
#include <map>
|
|
#include "pager.h"
|
|
#include "gdbsupport/gdb-checked-static-cast.h"
|
|
|
|
/* 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"
|
|
|
|
#ifdef __MINGW32__
|
|
static SHORT ncurses_norm_attr;
|
|
#endif
|
|
|
|
static int tui_getc (FILE *fp);
|
|
|
|
static int
|
|
key_is_start_sequence (int ch)
|
|
{
|
|
return (ch == 27);
|
|
}
|
|
|
|
/* Use definition from readline 4.3. */
|
|
#undef CTRL_CHAR
|
|
#define CTRL_CHAR(c) \
|
|
((c) < control_character_threshold && (((c) & 0x80) == 0))
|
|
|
|
/* This file controls the IO interactions between gdb and curses.
|
|
When the TUI is enabled, gdb has two modes a curses and a standard
|
|
mode.
|
|
|
|
In curses mode, the gdb outputs are made in a curses command
|
|
window. For this, the gdb_stdout and gdb_stderr are redirected to
|
|
the specific ui_file implemented by TUI. The output is handled by
|
|
tui_puts(). The input is also controlled by curses with
|
|
tui_getc(). The readline library uses this function to get its
|
|
input. Several readline hooks are installed to redirect readline
|
|
output to the TUI (see also the note below).
|
|
|
|
In normal mode, the gdb outputs are restored to their origin, that
|
|
is as if TUI is not used. Readline also uses its original getc()
|
|
function with stdin.
|
|
|
|
Note SCz/2001-07-21: the current readline is not clean in its
|
|
management of the output. Even if we install a redisplay handler,
|
|
it sometimes writes on a stdout file. It is important to redirect
|
|
every output produced by readline, otherwise the curses window will
|
|
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 enough
|
|
so that is never write on stdout.
|
|
|
|
Note SCz/2002-09-01: we now use more readline hooks and it seems
|
|
that with them we don't need the pipe anymore (verified by creating
|
|
the pipe and closing its end so that write causes a SIGPIPE). The
|
|
old pipe code is still there and can be conditionally removed by
|
|
#undef TUI_USE_PIPE_FOR_READLINE. */
|
|
|
|
/* For gdb 5.3, prefer to continue the pipe hack as a backup wheel. */
|
|
#ifdef HAVE_PIPE
|
|
#define TUI_USE_PIPE_FOR_READLINE
|
|
#endif
|
|
/* #undef TUI_USE_PIPE_FOR_READLINE */
|
|
|
|
/* TUI output files. */
|
|
static struct ui_file *tui_stdout;
|
|
static struct ui_file *tui_stderr;
|
|
static struct ui_file *tui_stdlog;
|
|
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;
|
|
static struct ui_file *tui_old_stdlog;
|
|
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.
|
|
Should be removed when readline is clean. */
|
|
static FILE *tui_rl_outstream;
|
|
static FILE *tui_old_rl_outstream;
|
|
#ifdef TUI_USE_PIPE_FOR_READLINE
|
|
static int tui_readline_pipe[2];
|
|
#endif
|
|
|
|
/* 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);
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
do_tui_putc (tui_cmd_win ()->handle.get (), 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. */
|
|
|
|
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)
|
|
{
|
|
if (!style_tui_current_position)
|
|
return n_read;
|
|
|
|
/* 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;
|
|
|
|
if (reverse)
|
|
{
|
|
reverse_save_bg = style.get_background ();
|
|
reverse_save_fg = style.get_foreground ();
|
|
|
|
if (!style_tui_current_position)
|
|
{
|
|
/* Switch to default style (reversed) while highlighting the
|
|
current position. */
|
|
style = {};
|
|
}
|
|
}
|
|
else
|
|
{
|
|
style.set_bg (reverse_save_bg);
|
|
style.set_fg (reverse_save_fg);
|
|
}
|
|
|
|
style.set_reverse (reverse);
|
|
|
|
tui_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 ());
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
char c;
|
|
int prev_col = 0;
|
|
bool saw_nl = false;
|
|
|
|
while ((c = *string++) != 0)
|
|
{
|
|
if (c == '\1' || c == '\2')
|
|
{
|
|
/* Ignore these, they are readline escape-marking
|
|
sequences. */
|
|
continue;
|
|
}
|
|
|
|
if (c == '\033')
|
|
{
|
|
size_t bytes_read = apply_ansi_escape (w, string - 1);
|
|
if (bytes_read > 0)
|
|
{
|
|
string = string + bytes_read - 1;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (c == '\n')
|
|
saw_nl = true;
|
|
|
|
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.
|
|
Redisplay the command line with its prompt after readline has
|
|
changed the edited text. */
|
|
void
|
|
tui_redisplay_readline (void)
|
|
{
|
|
const char *prompt;
|
|
|
|
/* Detect when we temporarily left SingleKey and now the readline
|
|
edit buffer is empty, automatically restore the SingleKey
|
|
mode. The restore must only be done if the command has finished.
|
|
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
|
|
&& !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 = rl_display_prompt;
|
|
|
|
int c_pos = -1;
|
|
int c_line = -1;
|
|
WINDOW *w = tui_cmd_win ()->handle.get ();
|
|
int start_line = tui_cmd_win ()->start_line;
|
|
wmove (w, start_line, 0);
|
|
int height = 1;
|
|
if (prompt != nullptr)
|
|
tui_puts_internal (w, prompt, &height);
|
|
|
|
int prev_col = getcurx (w);
|
|
for (int in = 0; in <= rl_end; in++)
|
|
{
|
|
unsigned char c;
|
|
|
|
if (in == rl_point)
|
|
{
|
|
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) : '?');
|
|
}
|
|
else if (c == '\t')
|
|
{
|
|
/* Expand TABs, since ncurses on MS-Windows doesn't. */
|
|
int col = getcurx (w);
|
|
do
|
|
{
|
|
waddch (w, ' ');
|
|
col++;
|
|
} while ((col % 8) != 0);
|
|
}
|
|
else
|
|
{
|
|
waddch (w, c);
|
|
}
|
|
if (c == '\n')
|
|
tui_cmd_win ()->start_line = getcury (w);
|
|
int col = getcurx (w);
|
|
if (col < prev_col)
|
|
height++;
|
|
prev_col = col;
|
|
}
|
|
wclrtobot (w);
|
|
tui_cmd_win ()->start_line = getcury (w);
|
|
if (c_line >= 0)
|
|
wmove (w, c_line, c_pos);
|
|
tui_cmd_win ()->start_line -= height - 1;
|
|
|
|
wrefresh (w);
|
|
}
|
|
|
|
/* Readline callback to prepare the terminal. It is called once each
|
|
time we enter readline. Terminal is already setup in curses
|
|
mode. */
|
|
static void
|
|
tui_prep_terminal (int notused1)
|
|
{
|
|
#ifdef NCURSES_MOUSE_VERSION
|
|
if (tui_enable_mouse)
|
|
mousemask (ALL_MOUSE_EVENTS, NULL);
|
|
#endif
|
|
}
|
|
|
|
/* Readline callback to restore the terminal. It is called once each
|
|
time we leave readline. There is nothing to do in curses mode. */
|
|
static void
|
|
tui_deprep_terminal (void)
|
|
{
|
|
#ifdef NCURSES_MOUSE_VERSION
|
|
mousemask (0, NULL);
|
|
#endif
|
|
}
|
|
|
|
#ifdef TUI_USE_PIPE_FOR_READLINE
|
|
/* Read readline output pipe and feed the command window with it.
|
|
Should be removed when readline is clean. */
|
|
static void
|
|
tui_readline_output (int error, gdb_client_data data)
|
|
{
|
|
int size;
|
|
char buf[256];
|
|
|
|
size = read (tui_readline_pipe[0], buf, sizeof (buf) - 1);
|
|
if (size > 0 && tui_active)
|
|
{
|
|
buf[size] = 0;
|
|
tui_puts (buf);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* TUI version of displayer.crlf. */
|
|
|
|
static void
|
|
tui_mld_crlf (const struct match_list_displayer *displayer)
|
|
{
|
|
tui_putc ('\n');
|
|
}
|
|
|
|
/* TUI version of displayer.putch. */
|
|
|
|
static void
|
|
tui_mld_putch (const struct match_list_displayer *displayer, int ch)
|
|
{
|
|
tui_putc (ch);
|
|
}
|
|
|
|
/* TUI version of displayer.puts. */
|
|
|
|
static void
|
|
tui_mld_puts (const struct match_list_displayer *displayer, const char *s)
|
|
{
|
|
tui_puts (s);
|
|
}
|
|
|
|
/* TUI version of displayer.flush. */
|
|
|
|
static void
|
|
tui_mld_flush (const struct match_list_displayer *displayer)
|
|
{
|
|
wrefresh (tui_cmd_win ()->handle.get ());
|
|
}
|
|
|
|
/* TUI version of displayer.erase_entire_line. */
|
|
|
|
static void
|
|
tui_mld_erase_entire_line (const struct match_list_displayer *displayer)
|
|
{
|
|
WINDOW *w = tui_cmd_win ()->handle.get ();
|
|
int cur_y = getcury (w);
|
|
|
|
wmove (w, cur_y, 0);
|
|
wclrtoeol (w);
|
|
wmove (w, cur_y, 0);
|
|
}
|
|
|
|
/* TUI version of displayer.beep. */
|
|
|
|
static void
|
|
tui_mld_beep (const struct match_list_displayer *displayer)
|
|
{
|
|
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 ();
|
|
return r;
|
|
}
|
|
|
|
/* 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 ()->handle.get ();
|
|
int c = gdb_wgetch (w);
|
|
|
|
return c;
|
|
}
|
|
|
|
/* TUI version of displayer.read_key. */
|
|
|
|
static int
|
|
tui_mld_read_key (const struct match_list_displayer *displayer)
|
|
{
|
|
/* We can't use tui_getc as we need NEWLINE to not get emitted. */
|
|
scoped_restore restore_getc_function
|
|
= make_scoped_restore (&rl_getc_function, tui_mld_getc);
|
|
return rl_read_key ();
|
|
}
|
|
|
|
/* 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.
|
|
- In non-curses mode, readline and gdb use the standard input and
|
|
standard output/error directly.
|
|
- In curses mode, the standard output/error is controlled by TUI
|
|
with the tui_stdout and tui_stderr. The output is redirected in
|
|
the curses command window. Several readline callbacks are installed
|
|
so that readline asks for its input to the curses command window
|
|
with wgetch(). */
|
|
void
|
|
tui_setup_io (int mode)
|
|
{
|
|
extern int _rl_echoing_p;
|
|
|
|
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;
|
|
rl_deprep_term_function = tui_deprep_terminal;
|
|
rl_prep_term_function = tui_prep_terminal;
|
|
rl_getc_function = tui_getc;
|
|
_rl_echoing_p = 0;
|
|
rl_outstream = tui_rl_outstream;
|
|
rl_prompt = 0;
|
|
rl_completion_display_matches_hook = tui_rl_display_match_list;
|
|
rl_already_prompted = 0;
|
|
|
|
/* Keep track of previous gdb output. */
|
|
tui_old_stdout = gdb_stdout;
|
|
tui_old_stderr = gdb_stderr;
|
|
tui_old_stdlog = gdb_stdlog;
|
|
tui_old_uiout = gdb::checked_static_cast<cli_ui_out *> (current_uiout);
|
|
|
|
/* Reconfigure gdb output. */
|
|
gdb_stdout = tui_stdout;
|
|
gdb_stderr = tui_stderr;
|
|
gdb_stdlog = tui_stdlog;
|
|
gdb_stdtarg = gdb_stderr;
|
|
current_uiout = tui_out;
|
|
|
|
/* Save tty for SIGCONT. */
|
|
savetty ();
|
|
}
|
|
else
|
|
{
|
|
/* Restore gdb output. */
|
|
gdb_stdout = tui_old_stdout;
|
|
gdb_stderr = tui_old_stderr;
|
|
gdb_stdlog = tui_old_stdlog;
|
|
gdb_stdtarg = gdb_stderr;
|
|
current_uiout = tui_old_uiout;
|
|
|
|
/* Restore readline. */
|
|
rl_redisplay_function = tui_old_rl_redisplay_function;
|
|
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_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 ();
|
|
}
|
|
}
|
|
|
|
#ifdef SIGCONT
|
|
/* Catch SIGCONT to restore the terminal and refresh the screen. */
|
|
static void
|
|
tui_cont_sig (int sig)
|
|
{
|
|
if (tui_active)
|
|
{
|
|
/* Restore the terminal setting because another process (shell)
|
|
might have changed it. */
|
|
resetty ();
|
|
|
|
/* Force a refresh of the screen. */
|
|
tui_refresh_all_win ();
|
|
}
|
|
signal (sig, tui_cont_sig);
|
|
}
|
|
#endif
|
|
|
|
/* Initialize the IO for gdb in curses mode. */
|
|
void
|
|
tui_initialize_io (void)
|
|
{
|
|
#ifdef SIGCONT
|
|
signal (SIGCONT, tui_cont_sig);
|
|
#endif
|
|
|
|
/* Create tui output streams. */
|
|
tui_stdout = new pager_file (new tui_file (stdout, true));
|
|
tui_stderr = new tui_file (stderr, false);
|
|
tui_stdlog = new timestamped_file (tui_stderr);
|
|
tui_out = new cli_ui_out (tui_stdout, 0);
|
|
|
|
/* Create the default UI. */
|
|
tui_old_uiout = new cli_ui_out (gdb_stdout);
|
|
|
|
#ifdef TUI_USE_PIPE_FOR_READLINE
|
|
/* Temporary solution for readline writing to stdout: redirect
|
|
readline output in a pipe, read that pipe and output the content
|
|
in the curses command window. */
|
|
if (gdb_pipe_cloexec (tui_readline_pipe) != 0)
|
|
error (_("Cannot create pipe for readline"));
|
|
|
|
tui_rl_outstream = fdopen (tui_readline_pipe[1], "w");
|
|
if (tui_rl_outstream == 0)
|
|
error (_("Cannot redirect readline output"));
|
|
|
|
setvbuf (tui_rl_outstream, NULL, _IOLBF, 0);
|
|
|
|
#ifdef O_NONBLOCK
|
|
(void) fcntl (tui_readline_pipe[0], F_SETFL, O_NONBLOCK);
|
|
#else
|
|
#ifdef O_NDELAY
|
|
(void) fcntl (tui_readline_pipe[0], F_SETFL, O_NDELAY);
|
|
#endif
|
|
#endif
|
|
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
|
|
}
|
|
|
|
/* 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 ()->handle.get ();
|
|
|
|
#ifdef TUI_USE_PIPE_FOR_READLINE
|
|
/* Flush readline output. */
|
|
tui_readline_output (0, 0);
|
|
#endif
|
|
|
|
/* 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)
|
|
{
|
|
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
|
|
call_stdin_event_handler_again_p = 1;
|
|
return 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;
|
|
}
|
|
#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 guaranteed to be stable.
|
|
Compare keyname instead. */
|
|
if (ch >= KEY_MAX)
|
|
{
|
|
std::string_view name;
|
|
const char *name_str = keyname (ch);
|
|
if (name_str != nullptr)
|
|
name = std::string_view (name_str);
|
|
|
|
/* 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 (current_ui->command_editing && key_is_start_sequence (ch))
|
|
{
|
|
int ch_pending;
|
|
|
|
nodelay (w, TRUE);
|
|
ch_pending = gdb_wgetch (w);
|
|
nodelay (w, FALSE);
|
|
|
|
/* If we have pending input following a start sequence, call the stdin
|
|
event handler again because ncurses may have already read and stored
|
|
the input into its internal buffer, meaning that we won't get an stdin
|
|
event for it. If we don't compensate for this missed stdin event, key
|
|
sequences as Alt_F (^[f) will not behave promptly.
|
|
|
|
(We only compensates for the missed 2nd byte of a key sequence because
|
|
2-byte sequences are by far the most commonly used. ncurses may have
|
|
buffered a larger, 3+-byte key sequence though it remains to be seen
|
|
whether it is useful to compensate for all the bytes of such
|
|
sequences.) */
|
|
if (ch_pending != ERR)
|
|
{
|
|
ungetch (ch_pending);
|
|
call_stdin_event_handler_again_p = 1;
|
|
}
|
|
}
|
|
|
|
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. */
|
|
|
|
static int
|
|
tui_getc (FILE *fp)
|
|
{
|
|
try
|
|
{
|
|
return tui_getc_1 (fp);
|
|
}
|
|
catch (const gdb_exception_forced_quit &ex)
|
|
{
|
|
/* As noted below, it's not safe to let an exception escape
|
|
to newline, so, for this case, reset the quit flag for
|
|
later QUIT checking. */
|
|
set_force_quit_flag ();
|
|
return 0;
|
|
}
|
|
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;
|
|
}
|
|
}
|