1 /* TUI layout window management.
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 "arch-utils.h"
23 #include "command.h"
24 #include "symtab.h"
25 #include "frame.h"
26 #include "source.h"
27 #include "cli/cli-cmds.h"
28 #include "cli/cli-decode.h"
29 #include "cli/cli-utils.h"
30 #include <ctype.h>
31 #include <unordered_set>
32 
33 #include "tui/tui.h"
34 #include "tui/tui-command.h"
35 #include "tui/tui-data.h"
36 #include "tui/tui-wingeneral.h"
37 #include "tui/tui-status.h"
38 #include "tui/tui-regs.h"
39 #include "tui/tui-win.h"
40 #include "tui/tui-winsource.h"
41 #include "tui/tui-disasm.h"
42 #include "tui/tui-layout.h"
43 #include "tui/tui-source.h"
44 #include "gdb_curses.h"
45 #include "gdbsupport/gdb-safe-ctype.h"
46 
47 /* The layouts.  */
48 static std::vector<std::unique_ptr<tui_layout_split>> layouts;
49 
50 /* The layout that is currently applied.  */
51 static std::unique_ptr<tui_layout_base> applied_layout;
52 
53 /* The "skeleton" version of the layout that is currently applied.  */
54 static tui_layout_split *applied_skeleton;
55 
56 /* The two special "regs" layouts.  Note that these aren't registered
57    as commands and so can never be deleted.  */
58 static tui_layout_split *src_regs_layout;
59 static tui_layout_split *asm_regs_layout;
60 
61 /* See tui-data.h.  */
62 std::vector<tui_win_info *> tui_windows;
63 
64 /* See tui-layout.h.  */
65 
66 void
tui_apply_current_layout(bool preserve_cmd_win_size_p)67 tui_apply_current_layout (bool preserve_cmd_win_size_p)
68 {
69   for (tui_win_info *win_info : tui_windows)
70     win_info->make_visible (false);
71 
72   applied_layout->apply (0, 0, tui_term_width (), tui_term_height (),
73                                preserve_cmd_win_size_p);
74 
75   /* Keep the list of internal windows up-to-date.  */
76   for (int win_type = SRC_WIN; (win_type < MAX_MAJOR_WINDOWS); win_type++)
77     if (tui_win_list[win_type] != nullptr
78           && !tui_win_list[win_type]->is_visible ())
79       tui_win_list[win_type] = nullptr;
80 
81   /* This should always be made visible by a layout.  */
82   gdb_assert (TUI_CMD_WIN != nullptr);
83   gdb_assert (TUI_CMD_WIN->is_visible ());
84 
85   /* Get the new list of currently visible windows.  */
86   std::vector<tui_win_info *> new_tui_windows;
87   applied_layout->get_windows (&new_tui_windows);
88 
89   /* Now delete any window that was not re-applied.  */
90   tui_win_info *focus = tui_win_with_focus ();
91   for (tui_win_info *win_info : tui_windows)
92     {
93       if (!win_info->is_visible ())
94           {
95             if (focus == win_info)
96               tui_set_win_focus_to (new_tui_windows[0]);
97             delete win_info;
98           }
99     }
100 
101   /* Replace the global list of active windows.  */
102   tui_windows = std::move (new_tui_windows);
103 }
104 
105 /* See tui-layout.  */
106 
107 void
tui_adjust_window_height(struct tui_win_info * win,int new_height)108 tui_adjust_window_height (struct tui_win_info *win, int new_height)
109 {
110   applied_layout->set_height (win->name (), new_height);
111 }
112 
113 /* See tui-layout.  */
114 
115 void
tui_adjust_window_width(struct tui_win_info * win,int new_width)116 tui_adjust_window_width (struct tui_win_info *win, int new_width)
117 {
118   applied_layout->set_width (win->name (), new_width);
119 }
120 
121 /* Set the current layout to LAYOUT.  */
122 
123 static void
tui_set_layout(tui_layout_split * layout)124 tui_set_layout (tui_layout_split *layout)
125 {
126   std::string old_fingerprint;
127   if (applied_layout != nullptr)
128     old_fingerprint = applied_layout->layout_fingerprint ();
129 
130   applied_skeleton = layout;
131   applied_layout = layout->clone ();
132 
133   std::string new_fingerprint = applied_layout->layout_fingerprint ();
134   bool preserve_command_window_size
135     = (TUI_CMD_WIN != nullptr && old_fingerprint == new_fingerprint);
136 
137   tui_apply_current_layout (preserve_command_window_size);
138 }
139 
140 /* See tui-layout.h.  */
141 
142 void
tui_add_win_to_layout(enum tui_win_type type)143 tui_add_win_to_layout (enum tui_win_type type)
144 {
145   gdb_assert (type == SRC_WIN || type == DISASSEM_WIN);
146 
147   /* If the window already exists, no need to add it.  */
148   if (tui_win_list[type] != nullptr)
149     return;
150 
151   /* If the window we are trying to replace doesn't exist, we're
152      done.  */
153   enum tui_win_type other = type == SRC_WIN ? DISASSEM_WIN : SRC_WIN;
154   if (tui_win_list[other] == nullptr)
155     return;
156 
157   const char *name = type == SRC_WIN ? SRC_NAME : DISASSEM_NAME;
158   applied_layout->replace_window (tui_win_list[other]->name (), name);
159   tui_apply_current_layout (true);
160 }
161 
162 /* Find LAYOUT in the "layouts" global and return its index.  */
163 
164 static size_t
find_layout(tui_layout_split * layout)165 find_layout (tui_layout_split *layout)
166 {
167   for (size_t i = 0; i < layouts.size (); ++i)
168     {
169       if (layout == layouts[i].get ())
170           return i;
171     }
172   gdb_assert_not_reached ("layout not found!?");
173 }
174 
175 /* Function to set the layout. */
176 
177 static void
tui_apply_layout(const char * args,int from_tty,cmd_list_element * command)178 tui_apply_layout (const char *args, int from_tty, cmd_list_element *command)
179 {
180   tui_layout_split *layout = (tui_layout_split *) command->context ();
181 
182   /* Make sure the curses mode is enabled.  */
183   tui_enable ();
184   tui_set_layout (layout);
185 }
186 
187 /* See tui-layout.h.  */
188 
189 void
tui_next_layout()190 tui_next_layout ()
191 {
192   size_t index = find_layout (applied_skeleton);
193   ++index;
194   if (index == layouts.size ())
195     index = 0;
196   tui_set_layout (layouts[index].get ());
197 }
198 
199 /* Implement the "layout next" command.  */
200 
201 static void
tui_next_layout_command(const char * arg,int from_tty)202 tui_next_layout_command (const char *arg, int from_tty)
203 {
204   tui_enable ();
205   tui_next_layout ();
206 }
207 
208 /* See tui-layout.h.  */
209 
210 void
tui_set_initial_layout()211 tui_set_initial_layout ()
212 {
213   tui_set_layout (layouts[0].get ());
214 }
215 
216 /* Implement the "layout prev" command.  */
217 
218 static void
tui_prev_layout_command(const char * arg,int from_tty)219 tui_prev_layout_command (const char *arg, int from_tty)
220 {
221   tui_enable ();
222   size_t index = find_layout (applied_skeleton);
223   if (index == 0)
224     index = layouts.size ();
225   --index;
226   tui_set_layout (layouts[index].get ());
227 }
228 
229 
230 /* See tui-layout.h.  */
231 
232 void
tui_regs_layout()233 tui_regs_layout ()
234 {
235   /* If there's already a register window, we're done.  */
236   if (TUI_DATA_WIN != nullptr)
237     return;
238 
239   tui_set_layout (TUI_DISASM_WIN != nullptr
240                       ? asm_regs_layout
241                       : src_regs_layout);
242 }
243 
244 /* Implement the "layout regs" command.  */
245 
246 static void
tui_regs_layout_command(const char * arg,int from_tty)247 tui_regs_layout_command (const char *arg, int from_tty)
248 {
249   tui_enable ();
250   tui_regs_layout ();
251 }
252 
253 /* See tui-layout.h.  */
254 
255 void
tui_remove_some_windows()256 tui_remove_some_windows ()
257 {
258   tui_win_info *focus = tui_win_with_focus ();
259 
260   if (strcmp (focus->name (), CMD_NAME) == 0)
261     {
262       /* Try leaving the source or disassembly window.  If neither
263            exists, just do nothing.  */
264       focus = TUI_SRC_WIN;
265       if (focus == nullptr)
266           focus = TUI_DISASM_WIN;
267       if (focus == nullptr)
268           return;
269     }
270 
271   applied_layout->remove_windows (focus->name ());
272   tui_apply_current_layout (true);
273 }
274 
275 void
resize(int height_,int width_,int origin_x_,int origin_y_)276 tui_win_info::resize (int height_, int width_,
277                           int origin_x_, int origin_y_)
278 {
279   if (width == width_ && height == height_
280       && x == origin_x_ && y == origin_y_
281       && handle != nullptr)
282     return;
283 
284   width = width_;
285   height = height_;
286   x = origin_x_;
287   y = origin_y_;
288 
289   if (handle != nullptr)
290     {
291 #ifdef HAVE_WRESIZE
292       wresize (handle.get (), height, width);
293       mvwin (handle.get (), y, x);
294       wmove (handle.get (), 0, 0);
295 #else
296       handle.reset (nullptr);
297 #endif
298     }
299 
300   if (handle == nullptr)
301     make_window ();
302 
303   rerender ();
304 }
305 
306 
307 
308 /* Helper function to create one of the built-in (non-status)
309    windows.  */
310 
311 template<enum tui_win_type V, class T>
312 static tui_win_info *
make_standard_window(const char *)313 make_standard_window (const char *)
314 {
315   if (tui_win_list[V] == nullptr)
316     tui_win_list[V] = new T ();
317   return tui_win_list[V];
318 }
319 
320 /* A map holding all the known window types, keyed by name.  */
321 
322 static window_types_map known_window_types;
323 
324 /* See tui-layout.h.  */
325 
326 known_window_names_range
all_known_window_names()327 all_known_window_names ()
328 {
329   auto begin = known_window_names_iterator (known_window_types.begin ());
330   auto end = known_window_names_iterator (known_window_types.end ());
331   return known_window_names_range (begin, end);
332 }
333 
334 /* Helper function that returns a TUI window, given its name.  */
335 
336 static tui_win_info *
tui_get_window_by_name(const std::string & name)337 tui_get_window_by_name (const std::string &name)
338 {
339   for (tui_win_info *window : tui_windows)
340     if (name == window->name ())
341       return window;
342 
343   auto iter = known_window_types.find (name);
344   if (iter == known_window_types.end ())
345     error (_("Unknown window type \"%s\""), name.c_str ());
346 
347   tui_win_info *result = iter->second (name.c_str ());
348   if (result == nullptr)
349     error (_("Could not create window \"%s\""), name.c_str ());
350   return result;
351 }
352 
353 /* Initialize the known window types.  */
354 
355 static void
initialize_known_windows()356 initialize_known_windows ()
357 {
358   known_window_types.emplace (SRC_NAME,
359                                      make_standard_window<SRC_WIN,
360                                                                 tui_source_window>);
361   known_window_types.emplace (CMD_NAME,
362                                      make_standard_window<CMD_WIN, tui_cmd_window>);
363   known_window_types.emplace (DATA_NAME,
364                                      make_standard_window<DATA_WIN,
365                                                                 tui_data_window>);
366   known_window_types.emplace (DISASSEM_NAME,
367                                      make_standard_window<DISASSEM_WIN,
368                                                                 tui_disasm_window>);
369   known_window_types.emplace (STATUS_NAME,
370                                      make_standard_window<STATUS_WIN,
371                                                                 tui_status_window>);
372 }
373 
374 /* See tui-layout.h.  */
375 
376 void
tui_register_window(const char * name,window_factory && factory)377 tui_register_window (const char *name, window_factory &&factory)
378 {
379   std::string name_copy = name;
380 
381   if (name_copy == SRC_NAME || name_copy == CMD_NAME || name_copy == DATA_NAME
382       || name_copy == DISASSEM_NAME || name_copy == STATUS_NAME)
383     error (_("Window type \"%s\" is built-in"), name);
384 
385   for (const char &c : name_copy)
386     {
387       if (ISSPACE (c))
388           error (_("invalid whitespace character in window name"));
389 
390       if (!ISALNUM (c) && strchr ("-_.", c) == nullptr)
391           error (_("invalid character '%c' in window name"), c);
392     }
393 
394   if (!ISALPHA (name_copy[0]))
395     error (_("window name must start with a letter, not '%c'"), name_copy[0]);
396 
397   /* We already check above for all the builtin window names.  If we get
398      this far then NAME must be a user defined window.  Remove any existing
399      factory and replace it with this new version.  */
400 
401   auto iter = known_window_types.find (name);
402   if (iter != known_window_types.end ())
403     known_window_types.erase (iter);
404 
405   known_window_types.emplace (std::move (name_copy),
406                                      std::move (factory));
407 }
408 
409 /* See tui-layout.h.  */
410 
411 std::unique_ptr<tui_layout_base>
clone()412 tui_layout_window::clone () const
413 {
414   tui_layout_window *result = new tui_layout_window (m_contents.c_str ());
415   return std::unique_ptr<tui_layout_base> (result);
416 }
417 
418 /* See tui-layout.h.  */
419 
420 void
apply(int x_,int y_,int width_,int height_,bool preserve_cmd_win_size_p)421 tui_layout_window::apply (int x_, int y_, int width_, int height_,
422                                 bool preserve_cmd_win_size_p)
423 {
424   x = x_;
425   y = y_;
426   width = width_;
427   height = height_;
428   gdb_assert (m_window != nullptr);
429   if (width == 0 || height == 0)
430     {
431       /* The window was dropped, so it's going to be deleted, reset the
432            soon to be dangling pointer.  */
433       m_window = nullptr;
434       return;
435     }
436   m_window->resize (height, width, x, y);
437 }
438 
439 /* See tui-layout.h.  */
440 
441 void
get_sizes(bool height,int * min_value,int * max_value)442 tui_layout_window::get_sizes (bool height, int *min_value, int *max_value)
443 {
444   TUI_SCOPED_DEBUG_ENTER_EXIT;
445 
446   if (m_window == nullptr)
447     m_window = tui_get_window_by_name (m_contents);
448 
449   tui_debug_printf ("window = %s, getting %s",
450                         m_window->name (), (height ? "height" : "width"));
451 
452   if (height)
453     {
454       *min_value = m_window->min_height ();
455       *max_value = m_window->max_height ();
456     }
457   else
458     {
459       *min_value = m_window->min_width ();
460       *max_value = m_window->max_width ();
461     }
462 
463   tui_debug_printf ("min = %d, max = %d", *min_value, *max_value);
464 }
465 
466 /* See tui-layout.h.  */
467 
468 bool
first_edge_has_border_p()469 tui_layout_window::first_edge_has_border_p () const
470 {
471   gdb_assert (m_window != nullptr);
472   return m_window->can_box ();
473 }
474 
475 /* See tui-layout.h.  */
476 
477 bool
last_edge_has_border_p()478 tui_layout_window::last_edge_has_border_p () const
479 {
480   gdb_assert (m_window != nullptr);
481   return m_window->can_box ();
482 }
483 
484 /* See tui-layout.h.  */
485 
486 void
replace_window(const char * name,const char * new_window)487 tui_layout_window::replace_window (const char *name, const char *new_window)
488 {
489   if (m_contents == name)
490     {
491       m_contents = new_window;
492       if (m_window != nullptr)
493           {
494             m_window->make_visible (false);
495             m_window = tui_get_window_by_name (m_contents);
496           }
497     }
498 }
499 
500 /* See tui-layout.h.  */
501 
502 void
specification(ui_file * output,int depth)503 tui_layout_window::specification (ui_file *output, int depth)
504 {
505   gdb_puts (get_name (), output);
506 }
507 
508 /* See tui-layout.h.  */
509 
510 std::string
layout_fingerprint()511 tui_layout_window::layout_fingerprint () const
512 {
513   if (strcmp (get_name (), "cmd") == 0)
514     return "C";
515   else
516     return "";
517 }
518 
519 /* See tui-layout.h.  */
520 
521 void
add_split(std::unique_ptr<tui_layout_split> && layout,int weight)522 tui_layout_split::add_split (std::unique_ptr<tui_layout_split> &&layout,
523                                    int weight)
524 {
525   split s = {weight, std::move (layout)};
526   m_splits.push_back (std::move (s));
527 }
528 
529 /* See tui-layout.h.  */
530 
531 void
add_window(const char * name,int weight)532 tui_layout_split::add_window (const char *name, int weight)
533 {
534   tui_layout_window *result = new tui_layout_window (name);
535   split s = {weight, std::unique_ptr<tui_layout_base> (result)};
536   m_splits.push_back (std::move (s));
537 }
538 
539 /* See tui-layout.h.  */
540 
541 std::unique_ptr<tui_layout_base>
clone()542 tui_layout_split::clone () const
543 {
544   tui_layout_split *result = new tui_layout_split (m_vertical);
545   for (const split &item : m_splits)
546     {
547       std::unique_ptr<tui_layout_base> next = item.layout->clone ();
548       split s = {item.weight, std::move (next)};
549       result->m_splits.push_back (std::move (s));
550     }
551   return std::unique_ptr<tui_layout_base> (result);
552 }
553 
554 /* See tui-layout.h.  */
555 
556 void
get_sizes(bool height,int * min_value,int * max_value)557 tui_layout_split::get_sizes (bool height, int *min_value, int *max_value)
558 {
559   TUI_SCOPED_DEBUG_ENTER_EXIT;
560 
561   *min_value = 0;
562   *max_value = 0;
563   bool first_time = true;
564   for (const split &item : m_splits)
565     {
566       int new_min, new_max;
567       item.layout->get_sizes (height, &new_min, &new_max);
568       /* For the mismatch case, the first time through we want to set
569            the min and max to the computed values -- the "first_time"
570            check here is just a funny way of doing that.  */
571       if (height == m_vertical || first_time)
572           {
573             *min_value += new_min;
574             *max_value += new_max;
575           }
576       else
577           {
578             *min_value = std::max (*min_value, new_min);
579             *max_value = std::min (*max_value, new_max);
580           }
581       first_time = false;
582     }
583 
584   tui_debug_printf ("min_value = %d, max_value = %d", *min_value, *max_value);
585 }
586 
587 /* See tui-layout.h.  */
588 
589 bool
first_edge_has_border_p()590 tui_layout_split::first_edge_has_border_p () const
591 {
592   if (m_splits.empty ())
593     return false;
594   return m_splits[0].layout->first_edge_has_border_p ();
595 }
596 
597 /* See tui-layout.h.  */
598 
599 bool
last_edge_has_border_p()600 tui_layout_split::last_edge_has_border_p () const
601 {
602   if (m_splits.empty ())
603     return false;
604   return m_splits.back ().layout->last_edge_has_border_p ();
605 }
606 
607 /* See tui-layout.h.  */
608 
609 void
set_weights_from_sizes()610 tui_layout_split::set_weights_from_sizes ()
611 {
612   for (int i = 0; i < m_splits.size (); ++i)
613     m_splits[i].weight
614       = m_vertical ? m_splits[i].layout->height : m_splits[i].layout->width;
615 }
616 
617 /* See tui-layout.h.  */
618 
619 std::string
tui_debug_weights_to_string()620 tui_layout_split::tui_debug_weights_to_string () const
621 {
622   std::string str;
623 
624   for (int i = 0; i < m_splits.size (); ++i)
625     {
626       if (i > 0)
627        str += ", ";
628       str += string_printf ("[%d] %d", i, m_splits[i].weight);
629     }
630 
631   return str;
632 }
633 
634 /* See tui-layout.h.  */
635 
636 void
tui_debug_print_size_info(const std::vector<tui_layout_split::size_info> & info)637 tui_layout_split::tui_debug_print_size_info
638   (const std::vector<tui_layout_split::size_info> &info)
639 {
640   gdb_assert (debug_tui);
641 
642   tui_debug_printf ("current size info data:");
643   for (int i = 0; i < info.size (); ++i)
644     tui_debug_printf ("  [%d] { size = %d, min = %d, max = %d, share_box = %d }",
645                           i, info[i].size, info[i].min_size,
646                           info[i].max_size, info[i].share_box);
647 }
648 
649 /* See tui-layout.h.  */
650 
651 tui_adjust_result
set_size(const char * name,int new_size,bool set_width_p)652 tui_layout_split::set_size (const char *name, int new_size, bool set_width_p)
653 {
654   TUI_SCOPED_DEBUG_ENTER_EXIT;
655 
656   tui_debug_printf ("this = %p, name = %s, new_size = %d",
657                         this, name, new_size);
658 
659   /* Look through the children.  If one is a layout holding the named
660      window, we're done; or if one actually is the named window,
661      update it.  */
662   int found_index = -1;
663   for (int i = 0; i < m_splits.size (); ++i)
664     {
665       tui_adjust_result adjusted;
666       if (set_width_p)
667           adjusted = m_splits[i].layout->set_width (name, new_size);
668       else
669           adjusted = m_splits[i].layout->set_height (name, new_size);
670       if (adjusted == HANDLED)
671           return HANDLED;
672       if (adjusted == FOUND)
673           {
674             if (set_width_p ? m_vertical : !m_vertical)
675               return FOUND;
676             found_index = i;
677             break;
678           }
679     }
680 
681   if (found_index == -1)
682     return NOT_FOUND;
683   int curr_size = (set_width_p
684                        ? m_splits[found_index].layout->width
685                        : m_splits[found_index].layout->height);
686   if (curr_size == new_size)
687     return HANDLED;
688 
689   tui_debug_printf ("found window %s at index %d", name, found_index);
690 
691   set_weights_from_sizes ();
692   int delta = m_splits[found_index].weight - new_size;
693   m_splits[found_index].weight = new_size;
694 
695   tui_debug_printf ("before delta (%d) distribution, weights: %s",
696                         delta, tui_debug_weights_to_string ().c_str ());
697 
698   /* Distribute the "delta" over all other windows, while respecting their
699      min/max sizes.  We grow each window by 1 line at a time continually
700      looping over all the windows.  However, skip the window that the user
701      just resized, obviously we don't want to readjust that window.  */
702   bool found_window_that_can_grow_p = true;
703   for (int i = 0; delta != 0; i = (i + 1) % m_splits.size ())
704     {
705       int index = (found_index + 1 + i) % m_splits.size ();
706       if (index == found_index)
707           {
708             if (!found_window_that_can_grow_p)
709               break;
710             found_window_that_can_grow_p = false;
711             continue;
712           }
713 
714       int new_min, new_max;
715       m_splits[index].layout->get_sizes (m_vertical, &new_min, &new_max);
716 
717       if (delta < 0)
718           {
719             /* The primary window grew, so we are trying to shrink other
720                windows.  */
721             if (m_splits[index].weight > new_min)
722               {
723                 m_splits[index].weight -= 1;
724                 delta += 1;
725                 found_window_that_can_grow_p = true;
726               }
727           }
728       else
729           {
730             /* The primary window shrank, so we are trying to grow other
731                windows.  */
732             if (m_splits[index].weight < new_max)
733               {
734                 m_splits[index].weight += 1;
735                 delta -= 1;
736                 found_window_that_can_grow_p = true;
737               }
738           }
739 
740       tui_debug_printf ("index = %d, weight now: %d",
741                               index, m_splits[index].weight);
742     }
743 
744   tui_debug_printf ("after delta (%d) distribution, weights: %s",
745                         delta, tui_debug_weights_to_string ().c_str ());
746 
747   if (delta != 0)
748     {
749       if (set_width_p)
750           warning (_("Invalid window width specified"));
751       else
752           warning (_("Invalid window height specified"));
753       /* Effectively undo any modifications made here.  */
754       set_weights_from_sizes ();
755     }
756   else
757     {
758       /* Simply re-apply the updated layout.  We pass false here so that
759            the cmd window can be resized.  However, we should have already
760            resized everything above to be "just right", so the apply call
761            here should not end up changing the sizes at all.  */
762       apply (x, y, width, height, false);
763     }
764 
765   return HANDLED;
766 }
767 
768 /* See tui-layout.h.  */
769 
770 void
apply(int x_,int y_,int width_,int height_,bool preserve_cmd_win_size_p)771 tui_layout_split::apply (int x_, int y_, int width_, int height_,
772                                bool preserve_cmd_win_size_p)
773 {
774   TUI_SCOPED_DEBUG_ENTER_EXIT;
775 
776   x = x_;
777   y = y_;
778   width = width_;
779   height = height_;
780 
781   /* In some situations we fix the size of the cmd window.  However,
782      occasionally this turns out to be a mistake.  This struct is used to
783      hold the original information about the cmd window, so we can restore
784      it if needed.  */
785   struct old_size_info
786   {
787     /* Constructor.  */
788     old_size_info (int index_, int min_size_, int max_size_)
789       : index (index_),
790           min_size (min_size_),
791           max_size (max_size_)
792     { /* Nothing.  */ }
793 
794     /* The index in m_splits where the cmd window was found.  */
795     int index;
796 
797     /* The previous min/max size.  */
798     int min_size;
799     int max_size;
800   };
801 
802   /* This is given a value only if we fix the size of the cmd window.  */
803   std::optional<old_size_info> old_cmd_info;
804 
805   std::vector<size_info> info (m_splits.size ());
806 
807   tui_debug_printf ("weights are: %s",
808                         tui_debug_weights_to_string ().c_str ());
809 
810   /* Step 1: Find the min and max size of each sub-layout.
811      Fixed-sized layouts are given their desired size, and then the
812      remaining space is distributed among the remaining windows
813      according to the weights given.  */
814   int available_size = m_vertical ? height : width;
815   int last_index = -1;
816   int total_weight = 0;
817   int prev = -1;
818   for (int i = 0; i < m_splits.size (); ++i)
819     {
820       bool cmd_win_already_exists = TUI_CMD_WIN != nullptr;
821 
822       /* Always call get_sizes, to ensure that the window is
823            instantiated.  This is a bit gross but less gross than adding
824            special cases for this in other places.  */
825       m_splits[i].layout->get_sizes (m_vertical, &info[i].min_size,
826                                              &info[i].max_size);
827 
828       if (preserve_cmd_win_size_p
829             && cmd_win_already_exists
830             && m_splits[i].layout->get_name () != nullptr
831             && strcmp (m_splits[i].layout->get_name (), "cmd") == 0)
832           {
833             /* Save the old cmd window information, in case we need to
834                restore it later.  */
835             old_cmd_info.emplace (i, info[i].min_size, info[i].max_size);
836 
837             /* If this layout has never been applied, then it means the
838                user just changed the layout.  In this situation, it's
839                desirable to keep the size of the command window the
840                same.  Setting the min and max sizes this way ensures
841                that the resizing step, below, does the right thing with
842                this window.  */
843             info[i].min_size = (m_vertical
844                                     ? TUI_CMD_WIN->height
845                                     : TUI_CMD_WIN->width);
846             info[i].max_size = info[i].min_size;
847           }
848 
849       if (info[i].min_size > info[i].max_size)
850           {
851             /* There is not enough room for this window, drop it.  */
852             info[i].min_size = 0;
853             info[i].max_size = 0;
854             continue;
855           }
856 
857       /* Two adjacent boxed windows will share a border.  */
858       if (prev != -1
859             && m_splits[prev].layout->last_edge_has_border_p ()
860             && m_splits[i].layout->first_edge_has_border_p ())
861           info[i].share_box = true;
862 
863       if (info[i].min_size == info[i].max_size)
864           {
865             available_size -= info[i].min_size;
866             if (info[i].share_box)
867               {
868                 /* A shared border makes a bit more size available.  */
869                 ++available_size;
870               }
871           }
872       else
873           {
874             last_index = i;
875             total_weight += m_splits[i].weight;
876           }
877 
878       prev = i;
879     }
880 
881   /* If last_index is set then we have a window that is not of a fixed
882      size.  This window will have its size calculated below, which requires
883      that the total_weight not be zero (we divide by total_weight, so don't
884      want a floating-point exception).  */
885   gdb_assert (last_index == -1 || total_weight > 0);
886 
887   /* Step 2: Compute the size of each sub-layout.  Fixed-sized items
888      are given their fixed size, while others are resized according to
889      their weight.  */
890   int used_size = 0;
891   for (int i = 0; i < m_splits.size (); ++i)
892     {
893       if (info[i].min_size != info[i].max_size)
894           {
895             /* Compute the height and clamp to the allowable range.  */
896             info[i].size = available_size * m_splits[i].weight / total_weight;
897             if (info[i].size > info[i].max_size)
898               info[i].size = info[i].max_size;
899             if (info[i].size < info[i].min_size)
900               info[i].size = info[i].min_size;
901             /* Keep a total of all the size we've used so far (we gain some
902                size back if this window can share a border with a preceding
903                window).  Any unused space will be distributed between all of
904                the other windows (while respecting min/max sizes) later in
905                this function.  */
906             used_size += info[i].size;
907             if (info[i].share_box)
908               {
909                 /* A shared border makes a bit more size available.  */
910                 --used_size;
911               }
912           }
913       else
914           info[i].size = info[i].min_size;
915     }
916 
917   if (debug_tui)
918     {
919       tui_debug_printf ("after initial size calculation");
920       tui_debug_printf ("available_size = %d, used_size = %d",
921                               available_size, used_size);
922       tui_debug_printf ("total_weight = %d, last_index = %d",
923                               total_weight, last_index);
924       tui_debug_print_size_info (info);
925     }
926 
927   /* If we didn't find any sub-layouts that were of a non-fixed size, but
928      we did find the cmd window, then we can consider that a sort-of
929      non-fixed size sub-layout.
930 
931      The cmd window might, initially, be of a fixed size (see above), but,
932      we are willing to relax this constraint if required to correctly apply
933      this layout (see below).  */
934   if (last_index == -1 && old_cmd_info.has_value ())
935     last_index = old_cmd_info->index;
936 
937   /* Allocate any leftover size.  */
938   if (available_size != used_size && last_index != -1)
939     {
940       /* Loop over all windows until the amount of used space is equal to
941            the amount of available space.  There's an escape hatch within
942            the loop in case we can't find any sub-layouts to resize.  */
943       bool found_window_that_can_grow_p = true;
944       for (int idx = last_index;
945              available_size != used_size;
946              idx = (idx + 1) % m_splits.size ())
947           {
948             /* Every time we get back to last_index, which is where the loop
949                started, we check to make sure that we did assign some space
950                to a window, bringing used_size closer to available_size.
951 
952                If we didn't, but the cmd window is of a fixed size, then we
953                can make the console window non-fixed-size, and continue
954                around the loop, hopefully, this will allow the layout to be
955                applied correctly.
956 
957                If we still make it around the loop without moving used_size
958                closer to available_size, then there's nothing more we can do,
959                and we break out of the loop.  */
960             if (idx == last_index)
961               {
962                 /* If the used_size is greater than the available_size then
963                      this indicates that the fixed-sized sub-layouts claimed
964                      more space than is available.  This layout is not going to
965                      work.  Our only hope at this point is to make the cmd
966                      window non-fixed-size (if possible), and hope we can
967                      shrink this enough to fit the rest of the sub-layouts in.
968 
969                      Alternatively, we've made it around the loop without
970                      adjusting any window's size.  This likely means all
971                      windows have hit their min or max size.  Again, our only
972                      hope is to make the cmd window non-fixed-size, and hope
973                      this fixes all our problems.  */
974                 if (old_cmd_info.has_value ()
975                       && ((available_size < used_size)
976                           || !found_window_that_can_grow_p))
977                     {
978                       info[old_cmd_info->index].min_size = old_cmd_info->min_size;
979                       info[old_cmd_info->index].max_size = old_cmd_info->max_size;
980                       tui_debug_printf
981                         ("restoring index %d (cmd) size limits, min = %d, max = %d",
982                          old_cmd_info->index, old_cmd_info->min_size,
983                          old_cmd_info->max_size);
984                       old_cmd_info.reset ();
985                     }
986                 else if (!found_window_that_can_grow_p)
987                     break;
988                 found_window_that_can_grow_p = false;
989               }
990 
991             if (available_size > used_size
992                 && info[idx].size < info[idx].max_size)
993               {
994                 found_window_that_can_grow_p = true;
995                 info[idx].size += 1;
996                 used_size += 1;
997               }
998             else if (available_size < used_size
999                        && info[idx].size > info[idx].min_size)
1000               {
1001                 found_window_that_can_grow_p = true;
1002                 info[idx].size -= 1;
1003                 used_size -= 1;
1004               }
1005           }
1006 
1007       if (debug_tui)
1008           {
1009             tui_debug_printf ("after final size calculation");
1010             tui_debug_printf ("available_size = %d, used_size = %d",
1011                                   available_size, used_size);
1012             tui_debug_printf ("total_weight = %d, last_index = %d",
1013                                   total_weight, last_index);
1014             tui_debug_print_size_info (info);
1015           }
1016     }
1017 
1018   /* Step 3: Resize.  */
1019   int size_accum = 0;
1020   const int maximum = m_vertical ? height : width;
1021   for (int i = 0; i < m_splits.size (); ++i)
1022     {
1023       /* If we fall off the bottom, just make allocations overlap.
1024            GIGO.  */
1025       if (size_accum + info[i].size > maximum)
1026           size_accum = maximum - info[i].size;
1027       else if (info[i].share_box)
1028           --size_accum;
1029       if (m_vertical)
1030           m_splits[i].layout->apply (x, y + size_accum, width, info[i].size,
1031                                            preserve_cmd_win_size_p);
1032       else
1033           m_splits[i].layout->apply (x + size_accum, y, info[i].size, height,
1034                                            preserve_cmd_win_size_p);
1035       size_accum += info[i].size;
1036     }
1037 }
1038 
1039 /* See tui-layout.h.  */
1040 
1041 void
remove_windows(const char * name)1042 tui_layout_split::remove_windows (const char *name)
1043 {
1044   for (int i = 0; i < m_splits.size (); ++i)
1045     {
1046       const char *this_name = m_splits[i].layout->get_name ();
1047       if (this_name == nullptr)
1048           m_splits[i].layout->remove_windows (name);
1049       else if (strcmp (this_name, name) == 0
1050                  || strcmp (this_name, CMD_NAME) == 0
1051                  || strcmp (this_name, STATUS_NAME) == 0)
1052           {
1053             /* Keep.  */
1054           }
1055       else
1056           {
1057             m_splits.erase (m_splits.begin () + i);
1058             --i;
1059           }
1060     }
1061 }
1062 
1063 /* See tui-layout.h.  */
1064 
1065 void
replace_window(const char * name,const char * new_window)1066 tui_layout_split::replace_window (const char *name, const char *new_window)
1067 {
1068   for (auto &item : m_splits)
1069     item.layout->replace_window (name, new_window);
1070 }
1071 
1072 /* See tui-layout.h.  */
1073 
1074 void
specification(ui_file * output,int depth)1075 tui_layout_split::specification (ui_file *output, int depth)
1076 {
1077   if (depth > 0)
1078     gdb_puts ("{", output);
1079 
1080   if (!m_vertical)
1081     gdb_puts ("-horizontal ", output);
1082 
1083   bool first = true;
1084   for (auto &item : m_splits)
1085     {
1086       if (!first)
1087           gdb_puts (" ", output);
1088       first = false;
1089       item.layout->specification (output, depth + 1);
1090       gdb_printf (output, " %d", item.weight);
1091     }
1092 
1093   if (depth > 0)
1094     gdb_puts ("}", output);
1095 }
1096 
1097 /* See tui-layout.h.  */
1098 
1099 std::string
layout_fingerprint()1100 tui_layout_split::layout_fingerprint () const
1101 {
1102   for (auto &item : m_splits)
1103     {
1104       std::string fp = item.layout->layout_fingerprint ();
1105       if (!fp.empty () && m_splits.size () != 1)
1106           return std::string (m_vertical ? "V" : "H") + fp;
1107     }
1108 
1109   return "";
1110 }
1111 
1112 /* Destroy the layout associated with SELF.  */
1113 
1114 static void
destroy_layout(struct cmd_list_element * self,void * context)1115 destroy_layout (struct cmd_list_element *self, void *context)
1116 {
1117   tui_layout_split *layout = (tui_layout_split *) context;
1118   size_t index = find_layout (layout);
1119   layouts.erase (layouts.begin () + index);
1120 }
1121 
1122 /* List holding the sub-commands of "layout".  */
1123 
1124 static struct cmd_list_element *layout_list;
1125 
1126 /* Called to implement 'tui layout'.  */
1127 
1128 static void
tui_layout_command(const char * args,int from_tty)1129 tui_layout_command (const char *args, int from_tty)
1130 {
1131   help_list (layout_list, "tui layout ", all_commands, gdb_stdout);
1132 }
1133 
1134 /* Add a "layout" command with name NAME that switches to LAYOUT.  */
1135 
1136 static struct cmd_list_element *
add_layout_command(const char * name,tui_layout_split * layout)1137 add_layout_command (const char *name, tui_layout_split *layout)
1138 {
1139   struct cmd_list_element *cmd;
1140 
1141   string_file spec;
1142   layout->specification (&spec, 0);
1143 
1144   gdb::unique_xmalloc_ptr<char> doc
1145     = xstrprintf (_("Apply the \"%s\" layout.\n\
1146 This layout was created using:\n\
1147   tui new-layout %s %s"),
1148                       name, name, spec.c_str ());
1149 
1150   cmd = add_cmd (name, class_tui, nullptr, doc.get (), &layout_list);
1151   cmd->set_context (layout);
1152   /* There is no API to set this.  */
1153   cmd->func = tui_apply_layout;
1154   cmd->destroyer = destroy_layout;
1155   cmd->doc_allocated = 1;
1156   doc.release ();
1157   layouts.emplace_back (layout);
1158 
1159   return cmd;
1160 }
1161 
1162 /* Initialize the standard layouts.  */
1163 
1164 static void
initialize_layouts()1165 initialize_layouts ()
1166 {
1167   tui_layout_split *layout;
1168 
1169   layout = new tui_layout_split ();
1170   layout->add_window (SRC_NAME, 2);
1171   layout->add_window (STATUS_NAME, 0);
1172   layout->add_window (CMD_NAME, 1);
1173   add_layout_command (SRC_NAME, layout);
1174 
1175   layout = new tui_layout_split ();
1176   layout->add_window (DISASSEM_NAME, 2);
1177   layout->add_window (STATUS_NAME, 0);
1178   layout->add_window (CMD_NAME, 1);
1179   add_layout_command (DISASSEM_NAME, layout);
1180 
1181   layout = new tui_layout_split ();
1182   layout->add_window (SRC_NAME, 1);
1183   layout->add_window (DISASSEM_NAME, 1);
1184   layout->add_window (STATUS_NAME, 0);
1185   layout->add_window (CMD_NAME, 1);
1186   add_layout_command ("split", layout);
1187 
1188   layout = new tui_layout_split ();
1189   layout->add_window (DATA_NAME, 1);
1190   layout->add_window (SRC_NAME, 1);
1191   layout->add_window (STATUS_NAME, 0);
1192   layout->add_window (CMD_NAME, 1);
1193   layouts.emplace_back (layout);
1194   src_regs_layout = layout;
1195 
1196   layout = new tui_layout_split ();
1197   layout->add_window (DATA_NAME, 1);
1198   layout->add_window (DISASSEM_NAME, 1);
1199   layout->add_window (STATUS_NAME, 0);
1200   layout->add_window (CMD_NAME, 1);
1201   layouts.emplace_back (layout);
1202   asm_regs_layout = layout;
1203 }
1204 
1205 
1206 
1207 /* A helper function that returns true if NAME is the name of an
1208    available window.  */
1209 
1210 static bool
validate_window_name(const std::string & name)1211 validate_window_name (const std::string &name)
1212 {
1213   auto iter = known_window_types.find (name);
1214   return iter != known_window_types.end ();
1215 }
1216 
1217 /* Implementation of the "tui new-layout" command.  */
1218 
1219 static void
tui_new_layout_command(const char * spec,int from_tty)1220 tui_new_layout_command (const char *spec, int from_tty)
1221 {
1222   std::string new_name = extract_arg (&spec);
1223   if (new_name.empty ())
1224     error (_("No layout name specified"));
1225   if (new_name[0] == '-')
1226     error (_("Layout name cannot start with '-'"));
1227 
1228   bool is_vertical = true;
1229   spec = skip_spaces (spec);
1230   if (check_for_argument (&spec, "-horizontal"))
1231     is_vertical = false;
1232 
1233   std::vector<std::unique_ptr<tui_layout_split>> splits;
1234   splits.emplace_back (new tui_layout_split (is_vertical));
1235   std::unordered_set<std::string> seen_windows;
1236   while (true)
1237     {
1238       spec = skip_spaces (spec);
1239       if (spec[0] == '\0')
1240           break;
1241 
1242       if (spec[0] == '{')
1243           {
1244             is_vertical = true;
1245             spec = skip_spaces (spec + 1);
1246             if (check_for_argument (&spec, "-horizontal"))
1247               is_vertical = false;
1248             splits.emplace_back (new tui_layout_split (is_vertical));
1249             continue;
1250           }
1251 
1252       bool is_close = false;
1253       std::string name;
1254       if (spec[0] == '}')
1255           {
1256             is_close = true;
1257             ++spec;
1258             if (splits.size () == 1)
1259               error (_("Extra '}' in layout specification"));
1260           }
1261       else
1262           {
1263             name = extract_arg (&spec);
1264             if (name.empty ())
1265               break;
1266             if (!validate_window_name (name))
1267               error (_("Unknown window \"%s\""), name.c_str ());
1268             if (seen_windows.find (name) != seen_windows.end ())
1269               error (_("Window \"%s\" seen twice in layout"), name.c_str ());
1270           }
1271 
1272       ULONGEST weight = get_ulongest (&spec, '}');
1273       if ((int) weight != weight)
1274           error (_("Weight out of range: %s"), pulongest (weight));
1275       if (is_close)
1276           {
1277             std::unique_ptr<tui_layout_split> last_split
1278               = std::move (splits.back ());
1279             splits.pop_back ();
1280             splits.back ()->add_split (std::move (last_split), weight);
1281           }
1282       else
1283           {
1284             splits.back ()->add_window (name.c_str (), weight);
1285             seen_windows.insert (name);
1286           }
1287     }
1288   if (splits.size () > 1)
1289     error (_("Missing '}' in layout specification"));
1290   if (seen_windows.empty ())
1291     error (_("New layout does not contain any windows"));
1292   if (seen_windows.find (CMD_NAME) == seen_windows.end ())
1293     error (_("New layout does not contain the \"" CMD_NAME "\" window"));
1294 
1295   gdb::unique_xmalloc_ptr<char> cmd_name
1296     = make_unique_xstrdup (new_name.c_str ());
1297   std::unique_ptr<tui_layout_split> new_layout = std::move (splits.back ());
1298   struct cmd_list_element *cmd
1299     = add_layout_command (cmd_name.get (), new_layout.get ());
1300   cmd->name_allocated = 1;
1301   cmd_name.release ();
1302   new_layout.release ();
1303 }
1304 
1305 /* Function to initialize gdb commands, for tui window layout
1306    manipulation.  */
1307 
1308 void _initialize_tui_layout ();
1309 void
_initialize_tui_layout()1310 _initialize_tui_layout ()
1311 {
1312   struct cmd_list_element *layout_cmd
1313     = add_prefix_cmd ("layout", class_tui, tui_layout_command, _("\
1314 Change the layout of windows.\n\
1315 Usage: tui layout prev | next | LAYOUT-NAME"),
1316                           &layout_list, 0, tui_get_cmd_list ());
1317   add_com_alias ("layout", layout_cmd, class_tui, 0);
1318 
1319   add_cmd ("next", class_tui, tui_next_layout_command,
1320              _("Apply the next TUI layout."),
1321              &layout_list);
1322   add_cmd ("prev", class_tui, tui_prev_layout_command,
1323              _("Apply the previous TUI layout."),
1324              &layout_list);
1325   add_cmd ("regs", class_tui, tui_regs_layout_command,
1326              _("Apply the TUI register layout."),
1327              &layout_list);
1328 
1329   add_cmd ("new-layout", class_tui, tui_new_layout_command,
1330              _("Create a new TUI layout.\n\
1331 Usage: tui new-layout [-horizontal] NAME WINDOW WEIGHT [WINDOW WEIGHT]...\n\
1332 Create a new TUI layout.  The new layout will be named NAME,\n\
1333 and can be accessed using \"layout NAME\".\n\
1334 The windows will be displayed in the specified order.\n\
1335 A WINDOW can also be of the form:\n\
1336   { [-horizontal] NAME WEIGHT [NAME WEIGHT]... }\n\
1337 This form indicates a sub-frame.\n\
1338 Each WEIGHT is an integer, which holds the relative size\n\
1339 to be allocated to the window."),
1340              tui_get_cmd_list ());
1341 
1342   initialize_layouts ();
1343   initialize_known_windows ();
1344 }
1345