/* TUI support I/O functions.
- Copyright (C) 1998-2019 Free Software Foundation, Inc.
+ Copyright (C) 1998-2022 Free Software Foundation, Inc.
Contributed by Hewlett-Packard Company.
#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"
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)
{
- 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')
+ /* Expand TABs, since ncurses on MS-Windows doesn't. */
+ if (c == '\t')
{
- 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;
+ int col;
- col = getcurx (w);
- do
- {
- waddch (w, ' ');
- col++;
- }
- while ((col % 8) != 0);
+ col = getcurx (w);
+ do
+ {
+ waddch (w, ' ');
+ col++;
}
- else
- waddch (w, c);
+ while ((col % 8) != 0);
}
- else if (c == '\n')
- tui_skip_line = -1;
+ else
+ waddch (w, c);
}
/* Update the cached value of the command window's start line based on
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)
{
wrefresh (w);
}
-/* 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 ();
- tui_puts_internal (w, string, nullptr);
-}
-
/* Readline callback.
Redisplay the command line with its prompt after readline has
changed the edited text. */
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;
if (in == rl_point)
{
- getyx (w, c_line, c_pos);
+ getyx (w, c_line, c_pos);
}
if (in == rl_end)
- break;
+ 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')
{
}
else
{
- waddch (w, c);
+ waddch (w, c);
}
if (c == '\n')
TUI_CMD_WIN->start_line = getcury (w);
col = getcurx (w);
if (col < prev_col)
- height++;
+ height++;
prev_col = col;
}
wclrtobot (w);
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
static void
tui_deprep_terminal (void)
{
+#ifdef NCURSES_MOUSE_VERSION
+ mousemask (0, NULL);
+#endif
}
#ifdef TUI_USE_PIPE_FOR_READLINE
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;
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. */
(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
#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
{
struct tui_win_info *win_info = tui_win_with_focus ();
- /* Handle the CTRL-L refresh for each window. */
- if (ch == '\f')
- tui_refresh_all_win ();
-
/* 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 ())
case KEY_LEFT:
win_info->right_scroll (1);
break;
- case '\f':
- break;
default:
/* We didn't recognize the character as a control character, so pass it
- through. */
+ through. */
return ch;
}
return 0;
}
-/* Get a character from the command window. This is called from the
- readline package. */
+/* 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
-tui_getc (FILE *fp)
+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;
tui_readline_output (0, 0);
#endif
- ch = gdb_wgetch (w);
-
- /* The \n must be echoed because it will not be printed by
- readline. */
- if (ch == '\n' || ch == '\r')
+ /* 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 && !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);
- }
+ 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
- {
- /* 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');
- }
+ 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 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 (ch > 0xff)
+ {
+ /* Readline doesn't understand non-8-bit curses keys, filter
+ them out. */
+ return 0;
+ }
+
return ch;
}
-/* See tui-io.h. */
+/* Get a character from the command window. This is called from the
+ readline package. */
-gdb::unique_xmalloc_ptr<char>
-tui_expand_tabs (const char *string)
+static int
+tui_getc (FILE *fp)
{
- int n_adjust, ncol;
- const char *s;
- char *ret, *q;
-
- /* 1. How many additional characters do we need? */
- for (ncol = 0, n_adjust = 0, s = string; s; )
+ try
{
- s = strpbrk (s, "\t");
- if (s)
- {
- ncol += (s - string) + n_adjust;
- /* Adjustment for the next tab stop, minus one for the TAB
- we replace with spaces. */
- n_adjust += 8 - (ncol % 8) - 1;
- s++;
- }
+ return tui_getc_1 (fp);
}
-
- /* Allocate the copy. */
- ret = q = (char *) xmalloc (strlen (string) + n_adjust + 1);
-
- /* 2. Copy the original string while replacing TABs with spaces. */
- for (ncol = 0, s = string; s; )
+ catch (const gdb_exception &ex)
{
- const char *s1 = strpbrk (s, "\t");
- if (s1)
- {
- if (s1 > s)
- {
- strncpy (q, s, s1 - s);
- q += s1 - s;
- ncol += s1 - s;
- }
- do {
- *q++ = ' ';
- ncol++;
- } while ((ncol % 8) != 0);
- s1++;
- }
- else
- strcpy (q, s);
- s = s1;
+ /* 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;
}
-
- return gdb::unique_xmalloc_ptr<char> (ret);
}