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