Home | History | Annotate | Line # | Download | only in tui
tui.c revision 1.11
      1 /* General functions for the WDB TUI.
      2 
      3    Copyright (C) 1998-2024 Free Software Foundation, Inc.
      4 
      5    Contributed by Hewlett-Packard Company.
      6 
      7    This file is part of GDB.
      8 
      9    This program is free software; you can redistribute it and/or modify
     10    it under the terms of the GNU General Public License as published by
     11    the Free Software Foundation; either version 3 of the License, or
     12    (at your option) any later version.
     13 
     14    This program is distributed in the hope that it will be useful,
     15    but WITHOUT ANY WARRANTY; without even the implied warranty of
     16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     17    GNU General Public License for more details.
     18 
     19    You should have received a copy of the GNU General Public License
     20    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
     21 
     22 #include "event-top.h"
     23 #include "cli/cli-cmds.h"
     24 #include "tui/tui.h"
     25 #include "tui/tui-hooks.h"
     26 #include "tui/tui-command.h"
     27 #include "tui/tui-data.h"
     28 #include "tui/tui-layout.h"
     29 #include "tui/tui-io.h"
     30 #include "tui/tui-regs.h"
     31 #include "tui/tui-status.h"
     32 #include "tui/tui-win.h"
     33 #include "tui/tui-wingeneral.h"
     34 #include "tui/tui-winsource.h"
     35 #include "tui/tui-source.h"
     36 #include "target.h"
     37 #include "frame.h"
     38 #include "breakpoint.h"
     39 #include "inferior.h"
     40 #include "symtab.h"
     41 #include "source.h"
     42 #include "terminal.h"
     43 #include "top.h"
     44 #include "ui.h"
     45 
     46 #include <ctype.h>
     47 #include <signal.h>
     48 #include <fcntl.h>
     49 #include <setjmp.h>
     50 
     51 #include "gdb_curses.h"
     52 #include "interps.h"
     53 
     54 /* See tui.h.  */
     55 
     56 bool debug_tui = false;
     57 
     58 /* Implement 'show debug tui'.  */
     59 
     60 static void
     61 show_tui_debug (struct ui_file *file, int from_tty,
     62 		struct cmd_list_element *c, const char *value)
     63 {
     64   gdb_printf (file, _("TUI debugging is \"%s\".\n"), value);
     65 }
     66 
     67 /* This redefines CTRL if it is not already defined, so it must come
     68    after terminal state releated include files like <term.h> and
     69    "gdb_curses.h".  */
     70 #include "readline/readline.h"
     71 
     72 /* Tells whether the TUI is active or not.  */
     73 bool tui_active = false;
     74 static bool tui_finish_init = true;
     75 
     76 enum tui_key_mode tui_current_key_mode = TUI_COMMAND_MODE;
     77 
     78 struct tui_char_command
     79 {
     80   unsigned char key;
     81   const char *cmd;
     82 };
     83 
     84 /* Key mapping to gdb commands when the TUI is using the single key
     85    mode.  */
     86 static const struct tui_char_command tui_commands[] = {
     87   { 'c', "continue" },
     88   { 'C', "reverse-continue" },
     89   { 'd', "down" },
     90   { 'f', "finish" },
     91   { 'F', "reverse-finish" },
     92   { 'n', "next" },
     93   { 'N', "reverse-next" },
     94   { 'o', "nexti" },
     95   { 'O', "reverse-nexti" },
     96   { 'r', "run" },
     97   { 's', "step" },
     98   { 'S', "reverse-step" },
     99   { 'i', "stepi" },
    100   { 'I', "reverse-stepi" },
    101   { 'u', "up" },
    102   { 'v', "info locals" },
    103   { 'w', "where" },
    104   { 0, 0 },
    105 };
    106 
    107 static Keymap tui_keymap;
    108 static Keymap tui_readline_standard_keymap;
    109 
    110 /* TUI readline command.
    111    Switch the output mode between TUI/standard gdb.  */
    112 static int
    113 tui_rl_switch_mode (int notused1, int notused2)
    114 {
    115 
    116   /* Don't let exceptions escape.  We're in the middle of a readline
    117      callback that isn't prepared for that.  */
    118   try
    119     {
    120       if (tui_active)
    121 	{
    122 	  tui_disable ();
    123 	  rl_prep_terminal (0);
    124 	}
    125       else
    126 	{
    127 	  /* If tui_enable throws, we'll re-prep below.  */
    128 	  rl_deprep_terminal ();
    129 	  tui_enable ();
    130 	}
    131     }
    132   catch (const gdb_exception_forced_quit &ex)
    133     {
    134       /* Ideally, we'd do a 'throw' here, but as noted above, we can't
    135 	 do that, so, instead, we'll set the necessary flags so that
    136 	 a later QUIT check will restart the forced quit.  */
    137       set_force_quit_flag ();
    138     }
    139   catch (const gdb_exception &ex)
    140     {
    141       exception_print (gdb_stderr, ex);
    142 
    143       if (!tui_active)
    144 	rl_prep_terminal (0);
    145     }
    146 
    147   /* Clear the readline in case switching occurred in middle of
    148      something.  */
    149   if (rl_end)
    150     rl_kill_text (0, rl_end);
    151 
    152   /* Since we left the curses mode, the terminal mode is restored to
    153      some previous state.  That state may not be suitable for readline
    154      to work correctly (it may be restored in line mode).  We force an
    155      exit of the current readline so that readline is re-entered and
    156      it will be able to setup the terminal for its needs.  By
    157      re-entering in readline, we also redisplay its prompt in the
    158      non-curses mode.  */
    159   rl_newline (1, '\n');
    160 
    161   /* Make sure the \n we are returning does not repeat the last
    162      command.  */
    163   dont_repeat ();
    164   return 0;
    165 }
    166 
    167 /* TUI readline command.
    168    Change the TUI layout to show a next layout.
    169    This function is bound to CTRL-X 2.  It is intended to provide
    170    a functionality close to the Emacs split-window command.  */
    171 static int
    172 tui_rl_change_windows (int notused1, int notused2)
    173 {
    174   if (!tui_active)
    175     tui_rl_switch_mode (0 /* notused */, 0 /* notused */);
    176 
    177   if (tui_active)
    178     tui_next_layout ();
    179 
    180   return 0;
    181 }
    182 
    183 /* TUI readline command.
    184    Delete the second TUI window to only show one.  */
    185 static int
    186 tui_rl_delete_other_windows (int notused1, int notused2)
    187 {
    188   if (!tui_active)
    189     tui_rl_switch_mode (0 /* notused */, 0 /* notused */);
    190 
    191   if (tui_active)
    192     tui_remove_some_windows ();
    193 
    194   return 0;
    195 }
    196 
    197 /* TUI readline command.
    198    Switch the active window to give the focus to a next window.  */
    199 static int
    200 tui_rl_other_window (int count, int key)
    201 {
    202   struct tui_win_info *win_info;
    203 
    204   if (!tui_active)
    205     tui_rl_switch_mode (0 /* notused */, 0 /* notused */);
    206 
    207   win_info = tui_next_win (tui_win_with_focus ());
    208   if (win_info)
    209     tui_set_win_focus_to (win_info);
    210   return 0;
    211 }
    212 
    213 /* TUI readline command.
    214    Execute the gdb command bound to the specified key.  */
    215 static int
    216 tui_rl_command_key (int count, int key)
    217 {
    218   int i;
    219 
    220   reinitialize_more_filter ();
    221   for (i = 0; tui_commands[i].cmd; i++)
    222     {
    223       if (tui_commands[i].key == key)
    224 	{
    225 	  /* Insert the command in the readline buffer.
    226 	     Avoid calling the gdb command here since it creates
    227 	     a possible recursion on readline if prompt_for_continue
    228 	     is called (See PR 9584).  The command will also appear
    229 	     in the readline history which turns out to be better.  */
    230 	  rl_insert_text (tui_commands[i].cmd);
    231 	  rl_newline (1, '\n');
    232 
    233 	  /* Switch to gdb command mode while executing the command.
    234 	     This way the gdb's continue prompt will be displayed.  */
    235 	  tui_set_key_mode (TUI_ONE_COMMAND_MODE);
    236 	  return 0;
    237 	}
    238     }
    239   return 0;
    240 }
    241 
    242 /* TUI readline command.
    243    Temporarily leave the TUI SingleKey mode to allow editing
    244    a gdb command with the normal readline.  Once the command
    245    is executed, the TUI SingleKey mode is installed back.  */
    246 static int
    247 tui_rl_command_mode (int count, int key)
    248 {
    249   tui_set_key_mode (TUI_ONE_COMMAND_MODE);
    250   return rl_insert (count, key);
    251 }
    252 
    253 /* TUI readline command.
    254    Switch between TUI SingleKey mode and gdb readline editing.  */
    255 static int
    256 tui_rl_next_keymap (int notused1, int notused2)
    257 {
    258   if (!tui_active)
    259     tui_rl_switch_mode (0 /* notused */, 0 /* notused */);
    260 
    261   if (rl_end)
    262     {
    263       rl_end = 0;
    264       rl_point = 0;
    265       rl_mark = 0;
    266     }
    267 
    268   tui_set_key_mode (tui_current_key_mode == TUI_COMMAND_MODE
    269 		    ? TUI_SINGLE_KEY_MODE : TUI_COMMAND_MODE);
    270   return 0;
    271 }
    272 
    273 /* Readline hook to redisplay ourself the gdb prompt.
    274    In the SingleKey mode, the prompt is not printed so that
    275    the command window is cleaner.  It will be displayed if
    276    we temporarily leave the SingleKey mode.  */
    277 static int
    278 tui_rl_startup_hook (void)
    279 {
    280   if (tui_current_key_mode != TUI_COMMAND_MODE
    281       && !gdb_in_secondary_prompt_p (current_ui))
    282     tui_set_key_mode (TUI_SINGLE_KEY_MODE);
    283   return 0;
    284 }
    285 
    286 /* Change the TUI key mode by installing the appropriate readline
    287    keymap.  */
    288 void
    289 tui_set_key_mode (enum tui_key_mode mode)
    290 {
    291   tui_current_key_mode = mode;
    292   rl_set_keymap (mode == TUI_SINGLE_KEY_MODE
    293 		 ? tui_keymap : tui_readline_standard_keymap);
    294   tui_show_status_content ();
    295 }
    296 
    297 /* Initialize readline and configure the keymap for the switching
    298    key shortcut.  */
    299 void
    300 tui_ensure_readline_initialized ()
    301 {
    302   static bool initialized;
    303 
    304   if (initialized)
    305     return;
    306   initialized = true;
    307 
    308   int i;
    309   Keymap tui_ctlx_keymap;
    310 
    311   rl_add_defun ("tui-switch-mode", tui_rl_switch_mode, -1);
    312   rl_add_defun ("next-keymap", tui_rl_next_keymap, -1);
    313   rl_add_defun ("tui-delete-other-windows", tui_rl_delete_other_windows, -1);
    314   rl_add_defun ("tui-change-windows", tui_rl_change_windows, -1);
    315   rl_add_defun ("tui-other-window", tui_rl_other_window, -1);
    316 
    317   tui_keymap = rl_make_bare_keymap ();
    318 
    319   /* The named keymap feature was added in Readline 8.0.  */
    320 #if RL_READLINE_VERSION >= 0x800
    321   rl_set_keymap_name ("SingleKey", tui_keymap);
    322 #endif
    323 
    324   tui_ctlx_keymap = rl_make_bare_keymap ();
    325   tui_readline_standard_keymap = rl_get_keymap ();
    326 
    327   for (i = 0; tui_commands[i].cmd; i++)
    328     rl_bind_key_in_map (tui_commands[i].key, tui_rl_command_key, tui_keymap);
    329 
    330   rl_generic_bind (ISKMAP, "\\C-x", (char*) tui_ctlx_keymap, tui_keymap);
    331 
    332   /* Bind all other keys to tui_rl_command_mode so that we switch
    333      temporarily from SingleKey mode and can enter a gdb command.  */
    334   for (i = ' '; i < 0x7f; i++)
    335     {
    336       int j;
    337 
    338       for (j = 0; tui_commands[j].cmd; j++)
    339 	if (tui_commands[j].key == i)
    340 	  break;
    341 
    342       if (tui_commands[j].cmd)
    343 	continue;
    344 
    345       rl_bind_key_in_map (i, tui_rl_command_mode, tui_keymap);
    346     }
    347 
    348   rl_bind_key_in_map ('a', tui_rl_switch_mode, emacs_ctlx_keymap);
    349   rl_bind_key_in_map ('a', tui_rl_switch_mode, tui_ctlx_keymap);
    350   rl_bind_key_in_map ('A', tui_rl_switch_mode, emacs_ctlx_keymap);
    351   rl_bind_key_in_map ('A', tui_rl_switch_mode, tui_ctlx_keymap);
    352   rl_bind_key_in_map (CTRL ('A'), tui_rl_switch_mode, emacs_ctlx_keymap);
    353   rl_bind_key_in_map (CTRL ('A'), tui_rl_switch_mode, tui_ctlx_keymap);
    354   rl_bind_key_in_map ('1', tui_rl_delete_other_windows, emacs_ctlx_keymap);
    355   rl_bind_key_in_map ('1', tui_rl_delete_other_windows, tui_ctlx_keymap);
    356   rl_bind_key_in_map ('2', tui_rl_change_windows, emacs_ctlx_keymap);
    357   rl_bind_key_in_map ('2', tui_rl_change_windows, tui_ctlx_keymap);
    358   rl_bind_key_in_map ('o', tui_rl_other_window, emacs_ctlx_keymap);
    359   rl_bind_key_in_map ('o', tui_rl_other_window, tui_ctlx_keymap);
    360   rl_bind_key_in_map ('q', tui_rl_next_keymap, tui_keymap);
    361   rl_bind_key_in_map ('s', tui_rl_next_keymap, emacs_ctlx_keymap);
    362   rl_bind_key_in_map ('s', tui_rl_next_keymap, tui_ctlx_keymap);
    363 
    364   /* Initialize readline after the above.  */
    365   rl_initialize ();
    366 }
    367 
    368 /* Return the TERM variable from the environment, or "<unset>"
    369    if not set.  */
    370 
    371 static const char *
    372 gdb_getenv_term (void)
    373 {
    374   const char *term;
    375 
    376   term = getenv ("TERM");
    377   if (term != NULL)
    378     return term;
    379   return "<unset>";
    380 }
    381 
    382 /* Enter in the tui mode (curses).
    383    When in normal mode, it installs the tui hooks in gdb, redirects
    384    the gdb output, configures the readline to work in tui mode.
    385    When in curses mode, it does nothing.  */
    386 void
    387 tui_enable (void)
    388 {
    389   TUI_SCOPED_DEBUG_ENTER_EXIT;
    390 
    391   if (tui_active)
    392     return;
    393 
    394   /* To avoid to initialize curses when gdb starts, there is a deferred
    395      curses initialization.  This initialization is made only once
    396      and the first time the curses mode is entered.  */
    397   if (tui_finish_init)
    398     {
    399       WINDOW *w;
    400       SCREEN *s;
    401 #ifndef __MINGW32__
    402        const char *cap;
    403 #endif
    404       const char *interp;
    405 
    406       /* If the top level interpreter is not the console/tui (e.g.,
    407 	 MI), enabling curses will certainly lose.  */
    408       interp = top_level_interpreter ()->name ();
    409       if (strcmp (interp, INTERP_TUI) != 0)
    410 	error (_("Cannot enable the TUI when the interpreter is '%s'"), interp);
    411 
    412       /* Don't try to setup curses (and print funny control
    413 	 characters) if we're not outputting to a terminal.  */
    414       if (!gdb_stderr->isatty ())
    415 	error (_("Cannot enable the TUI when output is not a terminal"));
    416 
    417       s = newterm (NULL, stdout, stdin);
    418 #ifdef __MINGW32__
    419       /* The MinGW port of ncurses requires $TERM to be unset in order
    420 	 to activate the Windows console driver.  */
    421       if (s == NULL)
    422 	s = newterm ((char *) "unknown", stdout, stdin);
    423 #endif
    424       if (s == NULL)
    425 	{
    426 	  error (_("Cannot enable the TUI: error opening terminal [TERM=%s]"),
    427 		 gdb_getenv_term ());
    428 	}
    429       w = stdscr;
    430       if (has_colors ())
    431 	{
    432 #ifdef HAVE_USE_DEFAULT_COLORS
    433 	  /* Ncurses extension to help with resetting to the default
    434 	     color.  */
    435 	  use_default_colors ();
    436 #endif
    437 	  start_color ();
    438 	}
    439 
    440       /* Check required terminal capabilities.  The MinGW port of
    441 	 ncurses does have them, but doesn't expose them through "cup".  */
    442 #ifndef __MINGW32__
    443       cap = tigetstr ((char *) "cup");
    444       if (cap == NULL || cap == (char *) -1 || *cap == '\0')
    445 	{
    446 	  endwin ();
    447 	  delscreen (s);
    448 	  error (_("Cannot enable the TUI: "
    449 		   "terminal doesn't support cursor addressing [TERM=%s]"),
    450 		 gdb_getenv_term ());
    451 	}
    452 #endif
    453 
    454       /* We must mark the tui sub-system active before trying to setup the
    455 	 current layout as tui windows defined by an extension language
    456 	 rely on this flag being true in order to know that the window
    457 	 they are creating is currently valid.  */
    458       tui_active = true;
    459 
    460       cbreak ();
    461       noecho ();
    462       /* timeout (1); */
    463       nodelay(w, FALSE);
    464       nl();
    465       keypad (w, TRUE);
    466       tui_set_term_height_to (LINES);
    467       tui_set_term_width_to (COLS);
    468       def_prog_mode ();
    469 
    470       tui_show_frame_info (deprecated_safe_get_selected_frame ());
    471       tui_set_initial_layout ();
    472       tui_set_win_focus_to (TUI_SRC_WIN);
    473       keypad (TUI_CMD_WIN->handle.get (), TRUE);
    474       wrefresh (TUI_CMD_WIN->handle.get ());
    475       tui_finish_init = false;
    476     }
    477   else
    478     {
    479       /* Save the current gdb setting of the terminal.
    480 	 Curses will restore this state when endwin() is called.  */
    481       def_shell_mode ();
    482       clearok (stdscr, TRUE);
    483 
    484       tui_active = true;
    485     }
    486 
    487   gdb_assert (tui_active);
    488 
    489   if (tui_update_variables ())
    490     tui_rehighlight_all ();
    491 
    492   tui_setup_io (1);
    493 
    494   /* Resize windows before anything might display/refresh a
    495      window.  */
    496   if (tui_win_resized ())
    497     {
    498       tui_set_win_resized_to (false);
    499       tui_resize_all ();
    500     }
    501 
    502   /* Install the TUI specific hooks.  This must be done after the call to
    503      tui_display_main so that we don't detect the symtab changed event it
    504      can cause.  */
    505   tui_install_hooks ();
    506   rl_startup_hook = tui_rl_startup_hook;
    507 
    508   /* Restore TUI keymap.  */
    509   tui_set_key_mode (tui_current_key_mode);
    510 
    511   /* Refresh the screen.  */
    512   tui_refresh_all_win ();
    513 
    514   /* Update gdb's knowledge of its terminal.  */
    515   gdb_save_tty_state ();
    516   tui_update_gdb_sizes ();
    517 }
    518 
    519 /* Leave the tui mode.
    520    Remove the tui hooks and configure the gdb output and readline
    521    back to their original state.  The curses mode is left so that
    522    the terminal setting is restored to the point when we entered.  */
    523 void
    524 tui_disable (void)
    525 {
    526   TUI_SCOPED_DEBUG_ENTER_EXIT;
    527 
    528   if (!tui_active)
    529     return;
    530 
    531   /* Restore initial readline keymap.  */
    532   rl_set_keymap (tui_readline_standard_keymap);
    533 
    534   /* Remove TUI hooks.  */
    535   tui_remove_hooks ();
    536   rl_startup_hook = 0;
    537   rl_already_prompted = 0;
    538 
    539 #ifdef NCURSES_MOUSE_VERSION
    540   mousemask (0, NULL);
    541 #endif
    542 
    543   /* Leave curses and restore previous gdb terminal setting.  */
    544   endwin ();
    545 
    546   /* gdb terminal has changed, update gdb internal copy of it
    547      so that terminal management with the inferior works.  */
    548   tui_setup_io (0);
    549 
    550   /* Update gdb's knowledge of its terminal.  */
    551   gdb_save_tty_state ();
    552 
    553   tui_active = false;
    554   tui_update_gdb_sizes ();
    555 }
    556 
    557 /* Command wrapper for enabling tui mode.  */
    558 
    559 static void
    560 tui_enable_command (const char *args, int from_tty)
    561 {
    562   tui_enable ();
    563 }
    564 
    565 /* Command wrapper for leaving tui mode.  */
    566 
    567 static void
    568 tui_disable_command (const char *args, int from_tty)
    569 {
    570   tui_disable ();
    571 }
    572 
    573 void
    574 tui_show_assembly (struct gdbarch *gdbarch, CORE_ADDR addr)
    575 {
    576   tui_suppress_output suppress;
    577   tui_add_win_to_layout (DISASSEM_WIN);
    578   tui_update_source_windows_with_addr (gdbarch, addr);
    579 }
    580 
    581 bool
    582 tui_is_window_visible (enum tui_win_type type)
    583 {
    584   if (!tui_active)
    585     return false;
    586 
    587   if (tui_win_list[type] == nullptr)
    588     return false;
    589 
    590   return tui_win_list[type]->is_visible ();
    591 }
    592 
    593 bool
    594 tui_get_command_dimension (unsigned int *width,
    595 			   unsigned int *height)
    596 {
    597   if (!tui_active || (TUI_CMD_WIN == NULL))
    598     return false;
    599 
    600   *width = TUI_CMD_WIN->width;
    601   *height = TUI_CMD_WIN->height;
    602   return true;
    603 }
    604 
    605 void _initialize_tui ();
    606 void
    607 _initialize_tui ()
    608 {
    609   struct cmd_list_element **tuicmd;
    610 
    611   tuicmd = tui_get_cmd_list ();
    612 
    613   add_cmd ("enable", class_tui, tui_enable_command,
    614 	   _("Enable TUI display mode.\n\
    615 Usage: tui enable"),
    616 	   tuicmd);
    617   add_cmd ("disable", class_tui, tui_disable_command,
    618 	   _("Disable TUI display mode.\n\
    619 Usage: tui disable"),
    620 	   tuicmd);
    621 
    622   /* Debug this tui internals.  */
    623   add_setshow_boolean_cmd ("tui", class_maintenance, &debug_tui,  _("\
    624 Set tui debugging."), _("\
    625 Show tui debugging."), _("\
    626 When true, tui specific internal debugging is enabled."),
    627 			   NULL,
    628 			   show_tui_debug,
    629 			   &setdebuglist, &showdebuglist);
    630 }
    631