1 /* TUI layout window management.
3 Copyright (C) 1998-2024 Free Software Foundation, Inc.
5 Contributed by Hewlett-Packard Company.
7 This file is part of GDB.
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.
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.
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/>. */
23 #include "arch-utils.h"
28 #include "cli/cli-cmds.h"
29 #include "cli/cli-decode.h"
30 #include "cli/cli-utils.h"
32 #include <unordered_set>
35 #include "tui/tui-command.h"
36 #include "tui/tui-data.h"
37 #include "tui/tui-wingeneral.h"
38 #include "tui/tui-status.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"
49 static std::vector
<std::unique_ptr
<tui_layout_split
>> layouts
;
51 /* The layout that is currently applied. */
52 static std::unique_ptr
<tui_layout_base
> applied_layout
;
54 /* The "skeleton" version of the layout that is currently applied. */
55 static tui_layout_split
*applied_skeleton
;
57 /* The two special "regs" layouts. Note that these aren't registered
58 as commands and so can never be deleted. */
59 static tui_layout_split
*src_regs_layout
;
60 static tui_layout_split
*asm_regs_layout
;
63 std::vector
<tui_win_info
*> tui_windows
;
65 /* See tui-layout.h. */
68 tui_apply_current_layout (bool preserve_cmd_win_size_p
)
70 for (tui_win_info
*win_info
: tui_windows
)
71 win_info
->make_visible (false);
73 applied_layout
->apply (0, 0, tui_term_width (), tui_term_height (),
74 preserve_cmd_win_size_p
);
76 /* Keep the list of internal windows up-to-date. */
77 for (int win_type
= SRC_WIN
; (win_type
< MAX_MAJOR_WINDOWS
); win_type
++)
78 if (tui_win_list
[win_type
] != nullptr
79 && !tui_win_list
[win_type
]->is_visible ())
80 tui_win_list
[win_type
] = nullptr;
82 /* This should always be made visible by a layout. */
83 gdb_assert (TUI_CMD_WIN
!= nullptr);
84 gdb_assert (TUI_CMD_WIN
->is_visible ());
86 /* Get the new list of currently visible windows. */
87 std::vector
<tui_win_info
*> new_tui_windows
;
88 applied_layout
->get_windows (&new_tui_windows
);
90 /* Now delete any window that was not re-applied. */
91 tui_win_info
*focus
= tui_win_with_focus ();
92 for (tui_win_info
*win_info
: tui_windows
)
94 if (!win_info
->is_visible ())
96 if (focus
== win_info
)
97 tui_set_win_focus_to (new_tui_windows
[0]);
102 /* Replace the global list of active windows. */
103 tui_windows
= std::move (new_tui_windows
);
106 /* See tui-layout. */
109 tui_adjust_window_height (struct tui_win_info
*win
, int new_height
)
111 applied_layout
->set_height (win
->name (), new_height
);
114 /* See tui-layout. */
117 tui_adjust_window_width (struct tui_win_info
*win
, int new_width
)
119 applied_layout
->set_width (win
->name (), new_width
);
122 /* Set the current layout to LAYOUT. */
125 tui_set_layout (tui_layout_split
*layout
)
127 std::string old_fingerprint
;
128 if (applied_layout
!= nullptr)
129 old_fingerprint
= applied_layout
->layout_fingerprint ();
131 applied_skeleton
= layout
;
132 applied_layout
= layout
->clone ();
134 std::string new_fingerprint
= applied_layout
->layout_fingerprint ();
135 bool preserve_command_window_size
136 = (TUI_CMD_WIN
!= nullptr && old_fingerprint
== new_fingerprint
);
138 tui_apply_current_layout (preserve_command_window_size
);
141 /* See tui-layout.h. */
144 tui_add_win_to_layout (enum tui_win_type type
)
146 gdb_assert (type
== SRC_WIN
|| type
== DISASSEM_WIN
);
148 /* If the window already exists, no need to add it. */
149 if (tui_win_list
[type
] != nullptr)
152 /* If the window we are trying to replace doesn't exist, we're
154 enum tui_win_type other
= type
== SRC_WIN
? DISASSEM_WIN
: SRC_WIN
;
155 if (tui_win_list
[other
] == nullptr)
158 const char *name
= type
== SRC_WIN
? SRC_NAME
: DISASSEM_NAME
;
159 applied_layout
->replace_window (tui_win_list
[other
]->name (), name
);
160 tui_apply_current_layout (true);
163 /* Find LAYOUT in the "layouts" global and return its index. */
166 find_layout (tui_layout_split
*layout
)
168 for (size_t i
= 0; i
< layouts
.size (); ++i
)
170 if (layout
== layouts
[i
].get ())
173 gdb_assert_not_reached ("layout not found!?");
176 /* Function to set the layout. */
179 tui_apply_layout (const char *args
, int from_tty
, cmd_list_element
*command
)
181 tui_layout_split
*layout
= (tui_layout_split
*) command
->context ();
183 /* Make sure the curses mode is enabled. */
185 tui_set_layout (layout
);
188 /* See tui-layout.h. */
193 size_t index
= find_layout (applied_skeleton
);
195 if (index
== layouts
.size ())
197 tui_set_layout (layouts
[index
].get ());
200 /* Implement the "layout next" command. */
203 tui_next_layout_command (const char *arg
, int from_tty
)
209 /* See tui-layout.h. */
212 tui_set_initial_layout ()
214 tui_set_layout (layouts
[0].get ());
217 /* Implement the "layout prev" command. */
220 tui_prev_layout_command (const char *arg
, int from_tty
)
223 size_t index
= find_layout (applied_skeleton
);
225 index
= layouts
.size ();
227 tui_set_layout (layouts
[index
].get ());
231 /* See tui-layout.h. */
236 /* If there's already a register window, we're done. */
237 if (TUI_DATA_WIN
!= nullptr)
240 tui_set_layout (TUI_DISASM_WIN
!= nullptr
245 /* Implement the "layout regs" command. */
248 tui_regs_layout_command (const char *arg
, int from_tty
)
254 /* See tui-layout.h. */
257 tui_remove_some_windows ()
259 tui_win_info
*focus
= tui_win_with_focus ();
261 if (strcmp (focus
->name (), CMD_NAME
) == 0)
263 /* Try leaving the source or disassembly window. If neither
264 exists, just do nothing. */
266 if (focus
== nullptr)
267 focus
= TUI_DISASM_WIN
;
268 if (focus
== nullptr)
272 applied_layout
->remove_windows (focus
->name ());
273 tui_apply_current_layout (true);
277 tui_win_info::resize (int height_
, int width_
,
278 int origin_x_
, int origin_y_
)
280 if (width
== width_
&& height
== height_
281 && x
== origin_x_
&& y
== origin_y_
282 && handle
!= nullptr)
290 if (handle
!= nullptr)
293 wresize (handle
.get (), height
, width
);
294 mvwin (handle
.get (), y
, x
);
295 wmove (handle
.get (), 0, 0);
297 handle
.reset (nullptr);
301 if (handle
== nullptr)
309 /* Helper function to create one of the built-in (non-status)
312 template<enum tui_win_type V
, class T
>
313 static tui_win_info
*
314 make_standard_window (const char *)
316 if (tui_win_list
[V
] == nullptr)
317 tui_win_list
[V
] = new T ();
318 return tui_win_list
[V
];
321 /* A map holding all the known window types, keyed by name. */
323 static window_types_map known_window_types
;
325 /* See tui-layout.h. */
327 known_window_names_range
328 all_known_window_names ()
330 auto begin
= known_window_names_iterator (known_window_types
.begin ());
331 auto end
= known_window_names_iterator (known_window_types
.end ());
332 return known_window_names_range (begin
, end
);
335 /* Helper function that returns a TUI window, given its name. */
337 static tui_win_info
*
338 tui_get_window_by_name (const std::string
&name
)
340 for (tui_win_info
*window
: tui_windows
)
341 if (name
== window
->name ())
344 auto iter
= known_window_types
.find (name
);
345 if (iter
== known_window_types
.end ())
346 error (_("Unknown window type \"%s\""), name
.c_str ());
348 tui_win_info
*result
= iter
->second (name
.c_str ());
349 if (result
== nullptr)
350 error (_("Could not create window \"%s\""), name
.c_str ());
354 /* Initialize the known window types. */
357 initialize_known_windows ()
359 known_window_types
.emplace (SRC_NAME
,
360 make_standard_window
<SRC_WIN
,
362 known_window_types
.emplace (CMD_NAME
,
363 make_standard_window
<CMD_WIN
, tui_cmd_window
>);
364 known_window_types
.emplace (DATA_NAME
,
365 make_standard_window
<DATA_WIN
,
367 known_window_types
.emplace (DISASSEM_NAME
,
368 make_standard_window
<DISASSEM_WIN
,
370 known_window_types
.emplace (STATUS_NAME
,
371 make_standard_window
<STATUS_WIN
,
375 /* See tui-layout.h. */
378 tui_register_window (const char *name
, window_factory
&&factory
)
380 std::string name_copy
= name
;
382 if (name_copy
== SRC_NAME
|| name_copy
== CMD_NAME
|| name_copy
== DATA_NAME
383 || name_copy
== DISASSEM_NAME
|| name_copy
== STATUS_NAME
)
384 error (_("Window type \"%s\" is built-in"), name
);
386 for (const char &c
: name_copy
)
389 error (_("invalid whitespace character in window name"));
391 if (!ISALNUM (c
) && strchr ("-_.", c
) == nullptr)
392 error (_("invalid character '%c' in window name"), c
);
395 if (!ISALPHA (name_copy
[0]))
396 error (_("window name must start with a letter, not '%c'"), name_copy
[0]);
398 /* We already check above for all the builtin window names. If we get
399 this far then NAME must be a user defined window. Remove any existing
400 factory and replace it with this new version. */
402 auto iter
= known_window_types
.find (name
);
403 if (iter
!= known_window_types
.end ())
404 known_window_types
.erase (iter
);
406 known_window_types
.emplace (std::move (name_copy
),
407 std::move (factory
));
410 /* See tui-layout.h. */
412 std::unique_ptr
<tui_layout_base
>
413 tui_layout_window::clone () const
415 tui_layout_window
*result
= new tui_layout_window (m_contents
.c_str ());
416 return std::unique_ptr
<tui_layout_base
> (result
);
419 /* See tui-layout.h. */
422 tui_layout_window::apply (int x_
, int y_
, int width_
, int height_
,
423 bool preserve_cmd_win_size_p
)
429 gdb_assert (m_window
!= nullptr);
430 if (width
== 0 || height
== 0)
432 /* The window was dropped, so it's going to be deleted, reset the
433 soon to be dangling pointer. */
437 m_window
->resize (height
, width
, x
, y
);
440 /* See tui-layout.h. */
443 tui_layout_window::get_sizes (bool height
, int *min_value
, int *max_value
)
445 TUI_SCOPED_DEBUG_ENTER_EXIT
;
447 if (m_window
== nullptr)
448 m_window
= tui_get_window_by_name (m_contents
);
450 tui_debug_printf ("window = %s, getting %s",
451 m_window
->name (), (height
? "height" : "width"));
455 *min_value
= m_window
->min_height ();
456 *max_value
= m_window
->max_height ();
460 *min_value
= m_window
->min_width ();
461 *max_value
= m_window
->max_width ();
464 tui_debug_printf ("min = %d, max = %d", *min_value
, *max_value
);
467 /* See tui-layout.h. */
470 tui_layout_window::first_edge_has_border_p () const
472 gdb_assert (m_window
!= nullptr);
473 return m_window
->can_box ();
476 /* See tui-layout.h. */
479 tui_layout_window::last_edge_has_border_p () const
481 gdb_assert (m_window
!= nullptr);
482 return m_window
->can_box ();
485 /* See tui-layout.h. */
488 tui_layout_window::replace_window (const char *name
, const char *new_window
)
490 if (m_contents
== name
)
492 m_contents
= new_window
;
493 if (m_window
!= nullptr)
495 m_window
->make_visible (false);
496 m_window
= tui_get_window_by_name (m_contents
);
501 /* See tui-layout.h. */
504 tui_layout_window::specification (ui_file
*output
, int depth
)
506 gdb_puts (get_name (), output
);
509 /* See tui-layout.h. */
512 tui_layout_window::layout_fingerprint () const
514 if (strcmp (get_name (), "cmd") == 0)
520 /* See tui-layout.h. */
523 tui_layout_split::add_split (std::unique_ptr
<tui_layout_split
> &&layout
,
526 split s
= {weight
, std::move (layout
)};
527 m_splits
.push_back (std::move (s
));
530 /* See tui-layout.h. */
533 tui_layout_split::add_window (const char *name
, int weight
)
535 tui_layout_window
*result
= new tui_layout_window (name
);
536 split s
= {weight
, std::unique_ptr
<tui_layout_base
> (result
)};
537 m_splits
.push_back (std::move (s
));
540 /* See tui-layout.h. */
542 std::unique_ptr
<tui_layout_base
>
543 tui_layout_split::clone () const
545 tui_layout_split
*result
= new tui_layout_split (m_vertical
);
546 for (const split
&item
: m_splits
)
548 std::unique_ptr
<tui_layout_base
> next
= item
.layout
->clone ();
549 split s
= {item
.weight
, std::move (next
)};
550 result
->m_splits
.push_back (std::move (s
));
552 return std::unique_ptr
<tui_layout_base
> (result
);
555 /* See tui-layout.h. */
558 tui_layout_split::get_sizes (bool height
, int *min_value
, int *max_value
)
560 TUI_SCOPED_DEBUG_ENTER_EXIT
;
564 bool first_time
= true;
565 for (const split
&item
: m_splits
)
567 int new_min
, new_max
;
568 item
.layout
->get_sizes (height
, &new_min
, &new_max
);
569 /* For the mismatch case, the first time through we want to set
570 the min and max to the computed values -- the "first_time"
571 check here is just a funny way of doing that. */
572 if (height
== m_vertical
|| first_time
)
574 *min_value
+= new_min
;
575 *max_value
+= new_max
;
579 *min_value
= std::max (*min_value
, new_min
);
580 *max_value
= std::min (*max_value
, new_max
);
585 tui_debug_printf ("min_value = %d, max_value = %d", *min_value
, *max_value
);
588 /* See tui-layout.h. */
591 tui_layout_split::first_edge_has_border_p () const
593 if (m_splits
.empty ())
595 return m_splits
[0].layout
->first_edge_has_border_p ();
598 /* See tui-layout.h. */
601 tui_layout_split::last_edge_has_border_p () const
603 if (m_splits
.empty ())
605 return m_splits
.back ().layout
->last_edge_has_border_p ();
608 /* See tui-layout.h. */
611 tui_layout_split::set_weights_from_sizes ()
613 for (int i
= 0; i
< m_splits
.size (); ++i
)
615 = m_vertical
? m_splits
[i
].layout
->height
: m_splits
[i
].layout
->width
;
618 /* See tui-layout.h. */
621 tui_layout_split::tui_debug_weights_to_string () const
625 for (int i
= 0; i
< m_splits
.size (); ++i
)
629 str
+= string_printf ("[%d] %d", i
, m_splits
[i
].weight
);
635 /* See tui-layout.h. */
638 tui_layout_split::tui_debug_print_size_info
639 (const std::vector
<tui_layout_split::size_info
> &info
)
641 gdb_assert (debug_tui
);
643 tui_debug_printf ("current size info data:");
644 for (int i
= 0; i
< info
.size (); ++i
)
645 tui_debug_printf (" [%d] { size = %d, min = %d, max = %d, share_box = %d }",
646 i
, info
[i
].size
, info
[i
].min_size
,
647 info
[i
].max_size
, info
[i
].share_box
);
650 /* See tui-layout.h. */
653 tui_layout_split::set_size (const char *name
, int new_size
, bool set_width_p
)
655 TUI_SCOPED_DEBUG_ENTER_EXIT
;
657 tui_debug_printf ("this = %p, name = %s, new_size = %d",
658 this, name
, new_size
);
660 /* Look through the children. If one is a layout holding the named
661 window, we're done; or if one actually is the named window,
663 int found_index
= -1;
664 for (int i
= 0; i
< m_splits
.size (); ++i
)
666 tui_adjust_result adjusted
;
668 adjusted
= m_splits
[i
].layout
->set_width (name
, new_size
);
670 adjusted
= m_splits
[i
].layout
->set_height (name
, new_size
);
671 if (adjusted
== HANDLED
)
673 if (adjusted
== FOUND
)
675 if (set_width_p
? m_vertical
: !m_vertical
)
682 if (found_index
== -1)
684 int curr_size
= (set_width_p
685 ? m_splits
[found_index
].layout
->width
686 : m_splits
[found_index
].layout
->height
);
687 if (curr_size
== new_size
)
690 tui_debug_printf ("found window %s at index %d", name
, found_index
);
692 set_weights_from_sizes ();
693 int delta
= m_splits
[found_index
].weight
- new_size
;
694 m_splits
[found_index
].weight
= new_size
;
696 tui_debug_printf ("before delta (%d) distribution, weights: %s",
697 delta
, tui_debug_weights_to_string ().c_str ());
699 /* Distribute the "delta" over all other windows, while respecting their
700 min/max sizes. We grow each window by 1 line at a time continually
701 looping over all the windows. However, skip the window that the user
702 just resized, obviously we don't want to readjust that window. */
703 bool found_window_that_can_grow_p
= true;
704 for (int i
= 0; delta
!= 0; i
= (i
+ 1) % m_splits
.size ())
706 int index
= (found_index
+ 1 + i
) % m_splits
.size ();
707 if (index
== found_index
)
709 if (!found_window_that_can_grow_p
)
711 found_window_that_can_grow_p
= false;
715 int new_min
, new_max
;
716 m_splits
[index
].layout
->get_sizes (m_vertical
, &new_min
, &new_max
);
720 /* The primary window grew, so we are trying to shrink other
722 if (m_splits
[index
].weight
> new_min
)
724 m_splits
[index
].weight
-= 1;
726 found_window_that_can_grow_p
= true;
731 /* The primary window shrank, so we are trying to grow other
733 if (m_splits
[index
].weight
< new_max
)
735 m_splits
[index
].weight
+= 1;
737 found_window_that_can_grow_p
= true;
741 tui_debug_printf ("index = %d, weight now: %d",
742 index
, m_splits
[index
].weight
);
745 tui_debug_printf ("after delta (%d) distribution, weights: %s",
746 delta
, tui_debug_weights_to_string ().c_str ());
751 warning (_("Invalid window width specified"));
753 warning (_("Invalid window height specified"));
754 /* Effectively undo any modifications made here. */
755 set_weights_from_sizes ();
759 /* Simply re-apply the updated layout. We pass false here so that
760 the cmd window can be resized. However, we should have already
761 resized everything above to be "just right", so the apply call
762 here should not end up changing the sizes at all. */
763 apply (x
, y
, width
, height
, false);
769 /* See tui-layout.h. */
772 tui_layout_split::apply (int x_
, int y_
, int width_
, int height_
,
773 bool preserve_cmd_win_size_p
)
775 TUI_SCOPED_DEBUG_ENTER_EXIT
;
782 /* In some situations we fix the size of the cmd window. However,
783 occasionally this turns out to be a mistake. This struct is used to
784 hold the original information about the cmd window, so we can restore
789 old_size_info (int index_
, int min_size_
, int max_size_
)
791 min_size (min_size_
),
795 /* The index in m_splits where the cmd window was found. */
798 /* The previous min/max size. */
803 /* This is given a value only if we fix the size of the cmd window. */
804 std::optional
<old_size_info
> old_cmd_info
;
806 std::vector
<size_info
> info (m_splits
.size ());
808 tui_debug_printf ("weights are: %s",
809 tui_debug_weights_to_string ().c_str ());
811 /* Step 1: Find the min and max size of each sub-layout.
812 Fixed-sized layouts are given their desired size, and then the
813 remaining space is distributed among the remaining windows
814 according to the weights given. */
815 int available_size
= m_vertical
? height
: width
;
817 int total_weight
= 0;
819 for (int i
= 0; i
< m_splits
.size (); ++i
)
821 bool cmd_win_already_exists
= TUI_CMD_WIN
!= nullptr;
823 /* Always call get_sizes, to ensure that the window is
824 instantiated. This is a bit gross but less gross than adding
825 special cases for this in other places. */
826 m_splits
[i
].layout
->get_sizes (m_vertical
, &info
[i
].min_size
,
829 if (preserve_cmd_win_size_p
830 && cmd_win_already_exists
831 && m_splits
[i
].layout
->get_name () != nullptr
832 && strcmp (m_splits
[i
].layout
->get_name (), "cmd") == 0)
834 /* Save the old cmd window information, in case we need to
836 old_cmd_info
.emplace (i
, info
[i
].min_size
, info
[i
].max_size
);
838 /* If this layout has never been applied, then it means the
839 user just changed the layout. In this situation, it's
840 desirable to keep the size of the command window the
841 same. Setting the min and max sizes this way ensures
842 that the resizing step, below, does the right thing with
844 info
[i
].min_size
= (m_vertical
845 ? TUI_CMD_WIN
->height
846 : TUI_CMD_WIN
->width
);
847 info
[i
].max_size
= info
[i
].min_size
;
850 if (info
[i
].min_size
> info
[i
].max_size
)
852 /* There is not enough room for this window, drop it. */
853 info
[i
].min_size
= 0;
854 info
[i
].max_size
= 0;
858 /* Two adjacent boxed windows will share a border. */
860 && m_splits
[prev
].layout
->last_edge_has_border_p ()
861 && m_splits
[i
].layout
->first_edge_has_border_p ())
862 info
[i
].share_box
= true;
864 if (info
[i
].min_size
== info
[i
].max_size
)
866 available_size
-= info
[i
].min_size
;
867 if (info
[i
].share_box
)
869 /* A shared border makes a bit more size available. */
876 total_weight
+= m_splits
[i
].weight
;
882 /* If last_index is set then we have a window that is not of a fixed
883 size. This window will have its size calculated below, which requires
884 that the total_weight not be zero (we divide by total_weight, so don't
885 want a floating-point exception). */
886 gdb_assert (last_index
== -1 || total_weight
> 0);
888 /* Step 2: Compute the size of each sub-layout. Fixed-sized items
889 are given their fixed size, while others are resized according to
892 for (int i
= 0; i
< m_splits
.size (); ++i
)
894 if (info
[i
].min_size
!= info
[i
].max_size
)
896 /* Compute the height and clamp to the allowable range. */
897 info
[i
].size
= available_size
* m_splits
[i
].weight
/ total_weight
;
898 if (info
[i
].size
> info
[i
].max_size
)
899 info
[i
].size
= info
[i
].max_size
;
900 if (info
[i
].size
< info
[i
].min_size
)
901 info
[i
].size
= info
[i
].min_size
;
902 /* Keep a total of all the size we've used so far (we gain some
903 size back if this window can share a border with a preceding
904 window). Any unused space will be distributed between all of
905 the other windows (while respecting min/max sizes) later in
907 used_size
+= info
[i
].size
;
908 if (info
[i
].share_box
)
910 /* A shared border makes a bit more size available. */
915 info
[i
].size
= info
[i
].min_size
;
920 tui_debug_printf ("after initial size calculation");
921 tui_debug_printf ("available_size = %d, used_size = %d",
922 available_size
, used_size
);
923 tui_debug_printf ("total_weight = %d, last_index = %d",
924 total_weight
, last_index
);
925 tui_debug_print_size_info (info
);
928 /* If we didn't find any sub-layouts that were of a non-fixed size, but
929 we did find the cmd window, then we can consider that a sort-of
930 non-fixed size sub-layout.
932 The cmd window might, initially, be of a fixed size (see above), but,
933 we are willing to relax this constraint if required to correctly apply
934 this layout (see below). */
935 if (last_index
== -1 && old_cmd_info
.has_value ())
936 last_index
= old_cmd_info
->index
;
938 /* Allocate any leftover size. */
939 if (available_size
!= used_size
&& last_index
!= -1)
941 /* Loop over all windows until the amount of used space is equal to
942 the amount of available space. There's an escape hatch within
943 the loop in case we can't find any sub-layouts to resize. */
944 bool found_window_that_can_grow_p
= true;
945 for (int idx
= last_index
;
946 available_size
!= used_size
;
947 idx
= (idx
+ 1) % m_splits
.size ())
949 /* Every time we get back to last_index, which is where the loop
950 started, we check to make sure that we did assign some space
951 to a window, bringing used_size closer to available_size.
953 If we didn't, but the cmd window is of a fixed size, then we
954 can make the console window non-fixed-size, and continue
955 around the loop, hopefully, this will allow the layout to be
958 If we still make it around the loop without moving used_size
959 closer to available_size, then there's nothing more we can do,
960 and we break out of the loop. */
961 if (idx
== last_index
)
963 /* If the used_size is greater than the available_size then
964 this indicates that the fixed-sized sub-layouts claimed
965 more space than is available. This layout is not going to
966 work. Our only hope at this point is to make the cmd
967 window non-fixed-size (if possible), and hope we can
968 shrink this enough to fit the rest of the sub-layouts in.
970 Alternatively, we've made it around the loop without
971 adjusting any window's size. This likely means all
972 windows have hit their min or max size. Again, our only
973 hope is to make the cmd window non-fixed-size, and hope
974 this fixes all our problems. */
975 if (old_cmd_info
.has_value ()
976 && ((available_size
< used_size
)
977 || !found_window_that_can_grow_p
))
979 info
[old_cmd_info
->index
].min_size
= old_cmd_info
->min_size
;
980 info
[old_cmd_info
->index
].max_size
= old_cmd_info
->max_size
;
982 ("restoring index %d (cmd) size limits, min = %d, max = %d",
983 old_cmd_info
->index
, old_cmd_info
->min_size
,
984 old_cmd_info
->max_size
);
985 old_cmd_info
.reset ();
987 else if (!found_window_that_can_grow_p
)
989 found_window_that_can_grow_p
= false;
992 if (available_size
> used_size
993 && info
[idx
].size
< info
[idx
].max_size
)
995 found_window_that_can_grow_p
= true;
999 else if (available_size
< used_size
1000 && info
[idx
].size
> info
[idx
].min_size
)
1002 found_window_that_can_grow_p
= true;
1003 info
[idx
].size
-= 1;
1010 tui_debug_printf ("after final size calculation");
1011 tui_debug_printf ("available_size = %d, used_size = %d",
1012 available_size
, used_size
);
1013 tui_debug_printf ("total_weight = %d, last_index = %d",
1014 total_weight
, last_index
);
1015 tui_debug_print_size_info (info
);
1019 /* Step 3: Resize. */
1021 const int maximum
= m_vertical
? height
: width
;
1022 for (int i
= 0; i
< m_splits
.size (); ++i
)
1024 /* If we fall off the bottom, just make allocations overlap.
1026 if (size_accum
+ info
[i
].size
> maximum
)
1027 size_accum
= maximum
- info
[i
].size
;
1028 else if (info
[i
].share_box
)
1031 m_splits
[i
].layout
->apply (x
, y
+ size_accum
, width
, info
[i
].size
,
1032 preserve_cmd_win_size_p
);
1034 m_splits
[i
].layout
->apply (x
+ size_accum
, y
, info
[i
].size
, height
,
1035 preserve_cmd_win_size_p
);
1036 size_accum
+= info
[i
].size
;
1040 /* See tui-layout.h. */
1043 tui_layout_split::remove_windows (const char *name
)
1045 for (int i
= 0; i
< m_splits
.size (); ++i
)
1047 const char *this_name
= m_splits
[i
].layout
->get_name ();
1048 if (this_name
== nullptr)
1049 m_splits
[i
].layout
->remove_windows (name
);
1050 else if (strcmp (this_name
, name
) == 0
1051 || strcmp (this_name
, CMD_NAME
) == 0
1052 || strcmp (this_name
, STATUS_NAME
) == 0)
1058 m_splits
.erase (m_splits
.begin () + i
);
1064 /* See tui-layout.h. */
1067 tui_layout_split::replace_window (const char *name
, const char *new_window
)
1069 for (auto &item
: m_splits
)
1070 item
.layout
->replace_window (name
, new_window
);
1073 /* See tui-layout.h. */
1076 tui_layout_split::specification (ui_file
*output
, int depth
)
1079 gdb_puts ("{", output
);
1082 gdb_puts ("-horizontal ", output
);
1085 for (auto &item
: m_splits
)
1088 gdb_puts (" ", output
);
1090 item
.layout
->specification (output
, depth
+ 1);
1091 gdb_printf (output
, " %d", item
.weight
);
1095 gdb_puts ("}", output
);
1098 /* See tui-layout.h. */
1101 tui_layout_split::layout_fingerprint () const
1103 for (auto &item
: m_splits
)
1105 std::string fp
= item
.layout
->layout_fingerprint ();
1106 if (!fp
.empty () && m_splits
.size () != 1)
1107 return std::string (m_vertical
? "V" : "H") + fp
;
1113 /* Destroy the layout associated with SELF. */
1116 destroy_layout (struct cmd_list_element
*self
, void *context
)
1118 tui_layout_split
*layout
= (tui_layout_split
*) context
;
1119 size_t index
= find_layout (layout
);
1120 layouts
.erase (layouts
.begin () + index
);
1123 /* List holding the sub-commands of "layout". */
1125 static struct cmd_list_element
*layout_list
;
1127 /* Called to implement 'tui layout'. */
1130 tui_layout_command (const char *args
, int from_tty
)
1132 help_list (layout_list
, "tui layout ", all_commands
, gdb_stdout
);
1135 /* Add a "layout" command with name NAME that switches to LAYOUT. */
1137 static struct cmd_list_element
*
1138 add_layout_command (const char *name
, tui_layout_split
*layout
)
1140 struct cmd_list_element
*cmd
;
1143 layout
->specification (&spec
, 0);
1145 gdb::unique_xmalloc_ptr
<char> doc
1146 = xstrprintf (_("Apply the \"%s\" layout.\n\
1147 This layout was created using:\n\
1148 tui new-layout %s %s"),
1149 name
, name
, spec
.c_str ());
1151 cmd
= add_cmd (name
, class_tui
, nullptr, doc
.get (), &layout_list
);
1152 cmd
->set_context (layout
);
1153 /* There is no API to set this. */
1154 cmd
->func
= tui_apply_layout
;
1155 cmd
->destroyer
= destroy_layout
;
1156 cmd
->doc_allocated
= 1;
1158 layouts
.emplace_back (layout
);
1163 /* Initialize the standard layouts. */
1166 initialize_layouts ()
1168 tui_layout_split
*layout
;
1170 layout
= new tui_layout_split ();
1171 layout
->add_window (SRC_NAME
, 2);
1172 layout
->add_window (STATUS_NAME
, 0);
1173 layout
->add_window (CMD_NAME
, 1);
1174 add_layout_command (SRC_NAME
, layout
);
1176 layout
= new tui_layout_split ();
1177 layout
->add_window (DISASSEM_NAME
, 2);
1178 layout
->add_window (STATUS_NAME
, 0);
1179 layout
->add_window (CMD_NAME
, 1);
1180 add_layout_command (DISASSEM_NAME
, layout
);
1182 layout
= new tui_layout_split ();
1183 layout
->add_window (SRC_NAME
, 1);
1184 layout
->add_window (DISASSEM_NAME
, 1);
1185 layout
->add_window (STATUS_NAME
, 0);
1186 layout
->add_window (CMD_NAME
, 1);
1187 add_layout_command ("split", layout
);
1189 layout
= new tui_layout_split ();
1190 layout
->add_window (DATA_NAME
, 1);
1191 layout
->add_window (SRC_NAME
, 1);
1192 layout
->add_window (STATUS_NAME
, 0);
1193 layout
->add_window (CMD_NAME
, 1);
1194 layouts
.emplace_back (layout
);
1195 src_regs_layout
= layout
;
1197 layout
= new tui_layout_split ();
1198 layout
->add_window (DATA_NAME
, 1);
1199 layout
->add_window (DISASSEM_NAME
, 1);
1200 layout
->add_window (STATUS_NAME
, 0);
1201 layout
->add_window (CMD_NAME
, 1);
1202 layouts
.emplace_back (layout
);
1203 asm_regs_layout
= layout
;
1208 /* A helper function that returns true if NAME is the name of an
1209 available window. */
1212 validate_window_name (const std::string
&name
)
1214 auto iter
= known_window_types
.find (name
);
1215 return iter
!= known_window_types
.end ();
1218 /* Implementation of the "tui new-layout" command. */
1221 tui_new_layout_command (const char *spec
, int from_tty
)
1223 std::string new_name
= extract_arg (&spec
);
1224 if (new_name
.empty ())
1225 error (_("No layout name specified"));
1226 if (new_name
[0] == '-')
1227 error (_("Layout name cannot start with '-'"));
1229 bool is_vertical
= true;
1230 spec
= skip_spaces (spec
);
1231 if (check_for_argument (&spec
, "-horizontal"))
1232 is_vertical
= false;
1234 std::vector
<std::unique_ptr
<tui_layout_split
>> splits
;
1235 splits
.emplace_back (new tui_layout_split (is_vertical
));
1236 std::unordered_set
<std::string
> seen_windows
;
1239 spec
= skip_spaces (spec
);
1240 if (spec
[0] == '\0')
1246 spec
= skip_spaces (spec
+ 1);
1247 if (check_for_argument (&spec
, "-horizontal"))
1248 is_vertical
= false;
1249 splits
.emplace_back (new tui_layout_split (is_vertical
));
1253 bool is_close
= false;
1259 if (splits
.size () == 1)
1260 error (_("Extra '}' in layout specification"));
1264 name
= extract_arg (&spec
);
1267 if (!validate_window_name (name
))
1268 error (_("Unknown window \"%s\""), name
.c_str ());
1269 if (seen_windows
.find (name
) != seen_windows
.end ())
1270 error (_("Window \"%s\" seen twice in layout"), name
.c_str ());
1273 ULONGEST weight
= get_ulongest (&spec
, '}');
1274 if ((int) weight
!= weight
)
1275 error (_("Weight out of range: %s"), pulongest (weight
));
1278 std::unique_ptr
<tui_layout_split
> last_split
1279 = std::move (splits
.back ());
1281 splits
.back ()->add_split (std::move (last_split
), weight
);
1285 splits
.back ()->add_window (name
.c_str (), weight
);
1286 seen_windows
.insert (name
);
1289 if (splits
.size () > 1)
1290 error (_("Missing '}' in layout specification"));
1291 if (seen_windows
.empty ())
1292 error (_("New layout does not contain any windows"));
1293 if (seen_windows
.find (CMD_NAME
) == seen_windows
.end ())
1294 error (_("New layout does not contain the \"" CMD_NAME
"\" window"));
1296 gdb::unique_xmalloc_ptr
<char> cmd_name
1297 = make_unique_xstrdup (new_name
.c_str ());
1298 std::unique_ptr
<tui_layout_split
> new_layout
= std::move (splits
.back ());
1299 struct cmd_list_element
*cmd
1300 = add_layout_command (cmd_name
.get (), new_layout
.get ());
1301 cmd
->name_allocated
= 1;
1302 cmd_name
.release ();
1303 new_layout
.release ();
1306 /* Function to initialize gdb commands, for tui window layout
1309 void _initialize_tui_layout ();
1311 _initialize_tui_layout ()
1313 struct cmd_list_element
*layout_cmd
1314 = add_prefix_cmd ("layout", class_tui
, tui_layout_command
, _("\
1315 Change the layout of windows.\n\
1316 Usage: tui layout prev | next | LAYOUT-NAME"),
1317 &layout_list
, 0, tui_get_cmd_list ());
1318 add_com_alias ("layout", layout_cmd
, class_tui
, 0);
1320 add_cmd ("next", class_tui
, tui_next_layout_command
,
1321 _("Apply the next TUI layout."),
1323 add_cmd ("prev", class_tui
, tui_prev_layout_command
,
1324 _("Apply the previous TUI layout."),
1326 add_cmd ("regs", class_tui
, tui_regs_layout_command
,
1327 _("Apply the TUI register layout."),
1330 add_cmd ("new-layout", class_tui
, tui_new_layout_command
,
1331 _("Create a new TUI layout.\n\
1332 Usage: tui new-layout [-horizontal] NAME WINDOW WEIGHT [WINDOW WEIGHT]...\n\
1333 Create a new TUI layout. The new layout will be named NAME,\n\
1334 and can be accessed using \"layout NAME\".\n\
1335 The windows will be displayed in the specified order.\n\
1336 A WINDOW can also be of the form:\n\
1337 { [-horizontal] NAME WEIGHT [NAME WEIGHT]... }\n\
1338 This form indicates a sub-frame.\n\
1339 Each WEIGHT is an integer, which holds the relative size\n\
1340 to be allocated to the window."),
1341 tui_get_cmd_list ());
1343 initialize_layouts ();
1344 initialize_known_windows ();