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
show_tui_debug(struct ui_file * file,int from_tty,struct cmd_list_element * c,const char * value)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
tui_rl_switch_mode(int notused1,int notused2)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
tui_rl_change_windows(int notused1,int notused2)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
tui_rl_delete_other_windows(int notused1,int notused2)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
tui_rl_other_window(int count,int key)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
tui_rl_command_key(int count,int key)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
tui_rl_command_mode(int count,int key)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
tui_rl_next_keymap(int notused1,int notused2)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
tui_rl_startup_hook(void)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
tui_set_key_mode(enum tui_key_mode mode)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
tui_ensure_readline_initialized()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 *
gdb_getenv_term(void)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
tui_enable(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
tui_disable(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
tui_enable_command(const char * args,int from_tty)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
tui_disable_command(const char * args,int from_tty)568 tui_disable_command (const char *args, int from_tty)
569 {
570   tui_disable ();
571 }
572 
573 void
tui_show_assembly(struct gdbarch * gdbarch,CORE_ADDR addr)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
tui_is_window_visible(enum tui_win_type type)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
tui_get_command_dimension(unsigned int * width,unsigned int * height)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
_initialize_tui()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