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