1 /* TUI layout window management.
3 Copyright (C) 1998-2020 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-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"
47 static void tui_layout_command (const char *, int);
48 static void extract_display_start_addr (struct gdbarch
**, CORE_ADDR
*);
51 static std::vector
<std::unique_ptr
<tui_layout_split
>> layouts
;
53 /* The layout that is currently applied. */
54 static std::unique_ptr
<tui_layout_base
> applied_layout
;
56 /* The "skeleton" version of the layout that is currently applied. */
57 static tui_layout_split
*applied_skeleton
;
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
;
64 /* See tui-layout.h. */
67 tui_apply_current_layout ()
69 applied_layout
->apply (0, 0, tui_term_width (), tui_term_height ());
75 tui_adjust_window_height (struct tui_win_info
*win
, int new_height
)
77 applied_layout
->adjust_size (win
->name (), new_height
);
80 /* Set the current layout to LAYOUT. */
83 tui_set_layout (tui_layout_split
*layout
)
85 struct gdbarch
*gdbarch
;
88 extract_display_start_addr (&gdbarch
, &addr
);
89 tui_make_all_invisible ();
90 applied_skeleton
= layout
;
91 applied_layout
= layout
->clone ();
92 tui_apply_current_layout ();
93 tui_delete_invisible_windows ();
95 if (gdbarch
== nullptr && TUI_DISASM_WIN
!= nullptr)
96 tui_get_begin_asm_address (&gdbarch
, &addr
);
97 tui_update_source_windows_with_addr (gdbarch
, addr
);
100 /* See tui-layout.h. */
103 tui_add_win_to_layout (enum tui_win_type type
)
105 gdb_assert (type
== SRC_WIN
|| type
== DISASSEM_WIN
);
107 /* If the window already exists, no need to add it. */
108 if (tui_win_list
[type
] != nullptr)
111 /* If the window we are trying to replace doesn't exist, we're
113 enum tui_win_type other
= type
== SRC_WIN
? DISASSEM_WIN
: SRC_WIN
;
114 if (tui_win_list
[other
] == nullptr)
117 const char *name
= type
== SRC_WIN
? SRC_NAME
: DISASSEM_NAME
;
118 applied_layout
->replace_window (tui_win_list
[other
]->name (), name
);
119 tui_apply_current_layout ();
120 tui_delete_invisible_windows ();
123 /* Find LAYOUT in the "layouts" global and return its index. */
126 find_layout (tui_layout_split
*layout
)
128 for (size_t i
= 0; i
< layouts
.size (); ++i
)
130 if (layout
== layouts
[i
].get ())
133 gdb_assert_not_reached (_("layout not found!?"));
136 /* Function to set the layout. */
139 tui_apply_layout (struct cmd_list_element
*command
,
140 const char *args
, int from_tty
)
142 tui_layout_split
*layout
143 = (tui_layout_split
*) get_cmd_context (command
);
145 /* Make sure the curses mode is enabled. */
147 tui_set_layout (layout
);
150 /* See tui-layout.h. */
155 size_t index
= find_layout (applied_skeleton
);
157 if (index
== layouts
.size ())
159 tui_set_layout (layouts
[index
].get ());
162 /* Implement the "layout next" command. */
165 tui_next_layout_command (const char *arg
, int from_tty
)
171 /* See tui-layout.h. */
174 tui_set_initial_layout ()
176 tui_set_layout (layouts
[0].get ());
179 /* Implement the "layout prev" command. */
182 tui_prev_layout_command (const char *arg
, int from_tty
)
185 size_t index
= find_layout (applied_skeleton
);
187 index
= layouts
.size ();
189 tui_set_layout (layouts
[index
].get ());
193 /* See tui-layout.h. */
198 /* If there's already a register window, we're done. */
199 if (TUI_DATA_WIN
!= nullptr)
202 tui_set_layout (TUI_DISASM_WIN
!= nullptr
207 /* Implement the "layout regs" command. */
210 tui_regs_layout_command (const char *arg
, int from_tty
)
216 /* See tui-layout.h. */
219 tui_remove_some_windows ()
221 tui_win_info
*focus
= tui_win_with_focus ();
223 if (strcmp (focus
->name (), "cmd") == 0)
225 /* Try leaving the source or disassembly window. If neither
226 exists, just do nothing. */
228 if (focus
== nullptr)
229 focus
= TUI_DISASM_WIN
;
230 if (focus
== nullptr)
234 applied_layout
->remove_windows (focus
->name ());
235 tui_apply_current_layout ();
239 extract_display_start_addr (struct gdbarch
**gdbarch_p
, CORE_ADDR
*addr_p
)
241 struct gdbarch
*gdbarch
= nullptr;
244 struct symtab_and_line cursal
= get_current_source_symtab_and_line ();
246 if (TUI_SRC_WIN
!= nullptr)
248 gdbarch
= TUI_SRC_WIN
->gdbarch
;
249 find_line_pc (cursal
.symtab
,
250 TUI_SRC_WIN
->start_line_or_addr
.u
.line_no
,
254 else if (TUI_DISASM_WIN
!= nullptr)
256 gdbarch
= TUI_DISASM_WIN
->gdbarch
;
257 addr
= TUI_DISASM_WIN
->start_line_or_addr
.u
.addr
;
260 *gdbarch_p
= gdbarch
;
265 tui_gen_win_info::resize (int height_
, int width_
,
266 int origin_x_
, int origin_y_
)
268 if (width
== width_
&& height
== height_
269 && x
== origin_x_
&& y
== origin_y_
270 && handle
!= nullptr)
278 if (handle
!= nullptr)
281 wresize (handle
.get (), height
, width
);
282 mvwin (handle
.get (), y
, x
);
283 wmove (handle
.get (), 0, 0);
285 handle
.reset (nullptr);
289 if (handle
== nullptr)
297 /* Helper function that returns a TUI window, given its name. */
299 static tui_gen_win_info
*
300 tui_get_window_by_name (const std::string
&name
)
304 if (tui_win_list
[SRC_WIN
] == nullptr)
305 tui_win_list
[SRC_WIN
] = new tui_source_window ();
306 return tui_win_list
[SRC_WIN
];
308 else if (name
== "cmd")
310 if (tui_win_list
[CMD_WIN
] == nullptr)
311 tui_win_list
[CMD_WIN
] = new tui_cmd_window ();
312 return tui_win_list
[CMD_WIN
];
314 else if (name
== "regs")
316 if (tui_win_list
[DATA_WIN
] == nullptr)
317 tui_win_list
[DATA_WIN
] = new tui_data_window ();
318 return tui_win_list
[DATA_WIN
];
320 else if (name
== "asm")
322 if (tui_win_list
[DISASSEM_WIN
] == nullptr)
323 tui_win_list
[DISASSEM_WIN
] = new tui_disasm_window ();
324 return tui_win_list
[DISASSEM_WIN
];
328 gdb_assert (name
== "status");
329 return tui_locator_win_info_ptr ();
333 /* See tui-layout.h. */
335 std::unique_ptr
<tui_layout_base
>
336 tui_layout_window::clone () const
338 tui_layout_window
*result
= new tui_layout_window (m_contents
.c_str ());
339 return std::unique_ptr
<tui_layout_base
> (result
);
342 /* See tui-layout.h. */
345 tui_layout_window::apply (int x_
, int y_
, int width_
, int height_
)
351 gdb_assert (m_window
!= nullptr);
352 m_window
->resize (height
, width
, x
, y
);
355 /* See tui-layout.h. */
358 tui_layout_window::get_sizes (int *min_height
, int *max_height
)
360 if (m_window
== nullptr)
361 m_window
= tui_get_window_by_name (m_contents
);
362 *min_height
= m_window
->min_height ();
363 *max_height
= m_window
->max_height ();
366 /* See tui-layout.h. */
369 tui_layout_window::top_boxed_p () const
371 gdb_assert (m_window
!= nullptr);
372 return m_window
->can_box ();
375 /* See tui-layout.h. */
378 tui_layout_window::bottom_boxed_p () const
380 gdb_assert (m_window
!= nullptr);
381 return m_window
->can_box ();
384 /* See tui-layout.h. */
387 tui_layout_window::replace_window (const char *name
, const char *new_window
)
389 if (m_contents
== name
)
391 m_contents
= new_window
;
392 if (m_window
!= nullptr)
394 m_window
->make_visible (false);
395 m_window
= tui_get_window_by_name (m_contents
);
400 /* See tui-layout.h. */
403 tui_layout_window::specification (ui_file
*output
)
405 fputs_unfiltered (get_name (), output
);
408 /* See tui-layout.h. */
411 tui_layout_split::add_split (int weight
)
413 tui_layout_split
*result
= new tui_layout_split ();
414 split s
= {weight
, std::unique_ptr
<tui_layout_base
> (result
)};
415 m_splits
.push_back (std::move (s
));
419 /* See tui-layout.h. */
422 tui_layout_split::add_window (const char *name
, int weight
)
424 tui_layout_window
*result
= new tui_layout_window (name
);
425 split s
= {weight
, std::unique_ptr
<tui_layout_base
> (result
)};
426 m_splits
.push_back (std::move (s
));
429 /* See tui-layout.h. */
431 std::unique_ptr
<tui_layout_base
>
432 tui_layout_split::clone () const
434 tui_layout_split
*result
= new tui_layout_split ();
435 for (const split
&item
: m_splits
)
437 std::unique_ptr
<tui_layout_base
> next
= item
.layout
->clone ();
438 split s
= {item
.weight
, std::move (next
)};
439 result
->m_splits
.push_back (std::move (s
));
441 return std::unique_ptr
<tui_layout_base
> (result
);
444 /* See tui-layout.h. */
447 tui_layout_split::get_sizes (int *min_height
, int *max_height
)
451 for (const split
&item
: m_splits
)
453 int new_min
, new_max
;
454 item
.layout
->get_sizes (&new_min
, &new_max
);
455 *min_height
+= new_min
;
456 *max_height
+= new_max
;
460 /* See tui-layout.h. */
463 tui_layout_split::top_boxed_p () const
465 if (m_splits
.empty ())
467 return m_splits
[0].layout
->top_boxed_p ();
470 /* See tui-layout.h. */
473 tui_layout_split::bottom_boxed_p () const
475 if (m_splits
.empty ())
477 return m_splits
.back ().layout
->top_boxed_p ();
480 /* See tui-layout.h. */
483 tui_layout_split::set_weights_from_heights ()
485 for (int i
= 0; i
< m_splits
.size (); ++i
)
486 m_splits
[i
].weight
= m_splits
[i
].layout
->height
;
489 /* See tui-layout.h. */
492 tui_layout_split::adjust_size (const char *name
, int new_height
)
494 /* Look through the children. If one is a layout holding the named
495 window, we're done; or if one actually is the named window,
497 int found_index
= -1;
498 for (int i
= 0; i
< m_splits
.size (); ++i
)
500 if (m_splits
[i
].layout
->adjust_size (name
, new_height
))
502 const char *win_name
= m_splits
[i
].layout
->get_name ();
503 if (win_name
!= nullptr && strcmp (name
, win_name
) == 0)
510 if (found_index
== -1)
512 if (m_splits
[found_index
].layout
->height
== new_height
)
515 set_weights_from_heights ();
516 int delta
= m_splits
[found_index
].weight
- new_height
;
517 m_splits
[found_index
].weight
= new_height
;
519 /* Distribute the "delta" over the next window; but if the next
520 window cannot hold it all, keep going until we either find a
521 window that does, or until we loop all the way around. */
522 for (int i
= 0; delta
!= 0 && i
< m_splits
.size () - 1; ++i
)
524 int index
= (found_index
+ 1 + i
) % m_splits
.size ();
526 int new_min
, new_max
;
527 m_splits
[index
].layout
->get_sizes (&new_min
, &new_max
);
531 /* The primary window grew, so we are trying to shrink other
533 int available
= m_splits
[index
].weight
- new_min
;
534 int shrink_by
= std::min (available
, -delta
);
535 m_splits
[index
].weight
-= shrink_by
;
540 /* The primary window shrank, so we are trying to grow other
542 int available
= new_max
- m_splits
[index
].weight
;
543 int grow_by
= std::min (available
, delta
);
544 m_splits
[index
].weight
+= grow_by
;
551 warning (_("Invalid window height specified"));
552 /* Effectively undo any modifications made here. */
553 set_weights_from_heights ();
557 /* Simply re-apply the updated layout. */
558 apply (x
, y
, width
, height
);
564 /* See tui-layout.h. */
567 tui_layout_split::apply (int x_
, int y_
, int width_
, int height_
)
579 /* True if this window will share a box border with the previous
580 window in the list. */
584 std::vector
<height_info
> info (m_splits
.size ());
586 /* Step 1: Find the min and max height of each sub-layout.
587 Fixed-sized layouts are given their desired height, and then the
588 remaining space is distributed among the remaining windows
589 according to the weights given. */
590 int available_height
= height
;
592 int total_weight
= 0;
593 for (int i
= 0; i
< m_splits
.size (); ++i
)
595 bool cmd_win_already_exists
= TUI_CMD_WIN
!= nullptr;
597 /* Always call get_sizes, to ensure that the window is
598 instantiated. This is a bit gross but less gross than adding
599 special cases for this in other places. */
600 m_splits
[i
].layout
->get_sizes (&info
[i
].min_height
, &info
[i
].max_height
);
603 && cmd_win_already_exists
604 && m_splits
[i
].layout
->get_name () != nullptr
605 && strcmp (m_splits
[i
].layout
->get_name (), "cmd") == 0)
607 /* If this layout has never been applied, then it means the
608 user just changed the layout. In this situation, it's
609 desirable to keep the size of the command window the
610 same. Setting the min and max heights this way ensures
611 that the resizing step, below, does the right thing with
613 info
[i
].min_height
= TUI_CMD_WIN
->height
;
614 info
[i
].max_height
= TUI_CMD_WIN
->height
;
617 if (info
[i
].min_height
== info
[i
].max_height
)
618 available_height
-= info
[i
].min_height
;
622 total_weight
+= m_splits
[i
].weight
;
625 /* Two adjacent boxed windows will share a border, making a bit
626 more height available. */
628 && m_splits
[i
- 1].layout
->bottom_boxed_p ()
629 && m_splits
[i
].layout
->top_boxed_p ())
630 info
[i
].share_box
= true;
633 /* Step 2: Compute the height of each sub-layout. Fixed-sized items
634 are given their fixed size, while others are resized according to
637 for (int i
= 0; i
< m_splits
.size (); ++i
)
639 /* Compute the height and clamp to the allowable range. */
640 info
[i
].height
= available_height
* m_splits
[i
].weight
/ total_weight
;
641 if (info
[i
].height
> info
[i
].max_height
)
642 info
[i
].height
= info
[i
].max_height
;
643 if (info
[i
].height
< info
[i
].min_height
)
644 info
[i
].height
= info
[i
].min_height
;
645 /* If there is any leftover height, just redistribute it to the
646 last resizeable window, by dropping it from the allocated
647 height. We could try to be fancier here perhaps, by
648 redistributing this height among all windows, not just the
650 if (info
[i
].min_height
!= info
[i
].max_height
)
652 used_height
+= info
[i
].height
;
653 if (info
[i
].share_box
)
658 /* Allocate any leftover height. */
659 if (available_height
>= used_height
&& last_index
!= -1)
660 info
[last_index
].height
+= available_height
- used_height
;
662 /* Step 3: Resize. */
663 int height_accum
= 0;
664 for (int i
= 0; i
< m_splits
.size (); ++i
)
666 /* If we fall off the bottom, just make allocations overlap.
668 if (height_accum
+ info
[i
].height
> height
)
669 height_accum
= height
- info
[i
].height
;
670 else if (info
[i
].share_box
)
672 m_splits
[i
].layout
->apply (x
, y
+ height_accum
, width
, info
[i
].height
);
673 height_accum
+= info
[i
].height
;
679 /* See tui-layout.h. */
682 tui_layout_split::remove_windows (const char *name
)
684 for (int i
= 0; i
< m_splits
.size (); ++i
)
686 const char *this_name
= m_splits
[i
].layout
->get_name ();
687 if (this_name
== nullptr)
688 m_splits
[i
].layout
->remove_windows (name
);
691 if (strcmp (this_name
, name
) == 0
692 || strcmp (this_name
, "cmd") == 0)
696 m_splits
.erase (m_splits
.begin () + i
);
702 /* See tui-layout.h. */
705 tui_layout_split::replace_window (const char *name
, const char *new_window
)
707 for (auto &item
: m_splits
)
708 item
.layout
->replace_window (name
, new_window
);
711 /* See tui-layout.h. */
714 tui_layout_split::specification (ui_file
*output
)
717 for (auto &item
: m_splits
)
720 fputs_unfiltered (" ", output
);
722 item
.layout
->specification (output
);
723 fprintf_unfiltered (output
, " %d", item
.weight
);
727 /* Destroy the layout associated with SELF. */
730 destroy_layout (struct cmd_list_element
*self
, void *context
)
732 tui_layout_split
*layout
= (tui_layout_split
*) context
;
733 size_t index
= find_layout (layout
);
734 layouts
.erase (layouts
.begin () + index
);
737 /* List holding the sub-commands of "layout". */
739 static struct cmd_list_element
*layout_list
;
741 /* Add a "layout" command with name NAME that switches to LAYOUT. */
743 static struct cmd_list_element
*
744 add_layout_command (const char *name
, tui_layout_split
*layout
)
746 struct cmd_list_element
*cmd
;
749 layout
->specification (&spec
);
751 gdb::unique_xmalloc_ptr
<char> doc
752 (xstrprintf (_("Apply the \"%s\" layout.\n\
753 This layout was created using:\n\
754 tui new-layout %s %s"),
755 name
, name
, spec
.c_str ()));
757 cmd
= add_cmd (name
, class_tui
, nullptr, doc
.get (), &layout_list
);
758 set_cmd_context (cmd
, layout
);
759 /* There is no API to set this. */
760 cmd
->func
= tui_apply_layout
;
761 cmd
->destroyer
= destroy_layout
;
762 cmd
->doc_allocated
= 1;
764 layouts
.emplace_back (layout
);
769 /* Initialize the standard layouts. */
772 initialize_layouts ()
774 tui_layout_split
*layout
;
776 layout
= new tui_layout_split ();
777 layout
->add_window ("src", 2);
778 layout
->add_window ("status", 0);
779 layout
->add_window ("cmd", 1);
780 add_layout_command ("src", layout
);
782 layout
= new tui_layout_split ();
783 layout
->add_window ("asm", 2);
784 layout
->add_window ("status", 0);
785 layout
->add_window ("cmd", 1);
786 add_layout_command ("asm", layout
);
788 layout
= new tui_layout_split ();
789 layout
->add_window ("src", 1);
790 layout
->add_window ("asm", 1);
791 layout
->add_window ("status", 0);
792 layout
->add_window ("cmd", 1);
793 add_layout_command ("split", layout
);
795 layout
= new tui_layout_split ();
796 layout
->add_window ("regs", 1);
797 layout
->add_window ("src", 1);
798 layout
->add_window ("status", 0);
799 layout
->add_window ("cmd", 1);
800 layouts
.emplace_back (layout
);
801 src_regs_layout
= layout
;
803 layout
= new tui_layout_split ();
804 layout
->add_window ("regs", 1);
805 layout
->add_window ("asm", 1);
806 layout
->add_window ("status", 0);
807 layout
->add_window ("cmd", 1);
808 layouts
.emplace_back (layout
);
809 asm_regs_layout
= layout
;
814 /* A helper function that returns true if NAME is the name of an
818 validate_window_name (const std::string
&name
)
820 return (name
== "src" || name
== "cmd"
821 || name
== "regs" || name
== "asm"
822 || name
== "status");
825 /* Implementation of the "tui new-layout" command. */
828 tui_new_layout_command (const char *spec
, int from_tty
)
830 std::string new_name
= extract_arg (&spec
);
831 if (new_name
.empty ())
832 error (_("No layout name specified"));
833 if (new_name
[0] == '-')
834 error (_("Layout name cannot start with '-'"));
836 std::unique_ptr
<tui_layout_split
> new_layout (new tui_layout_split
);
837 std::unordered_set
<std::string
> seen_windows
;
840 std::string name
= extract_arg (&spec
);
843 if (!validate_window_name (name
))
844 error (_("Unknown window \"%s\""), name
.c_str ());
845 if (seen_windows
.find (name
) != seen_windows
.end ())
846 error (_("Window \"%s\" seen twice in layout"), name
.c_str ());
847 ULONGEST weight
= get_ulongest (&spec
);
848 if ((int) weight
!= weight
)
849 error (_("Weight out of range: %s"), pulongest (weight
));
850 new_layout
->add_window (name
.c_str (), weight
);
851 seen_windows
.insert (name
);
853 if (seen_windows
.empty ())
854 error (_("New layout does not contain any windows"));
855 if (seen_windows
.find ("cmd") == seen_windows
.end ())
856 error (_("New layout does not contain the \"cmd\" window"));
858 gdb::unique_xmalloc_ptr
<char> cmd_name
859 = make_unique_xstrdup (new_name
.c_str ());
860 struct cmd_list_element
*cmd
861 = add_layout_command (cmd_name
.get (), new_layout
.get ());
862 cmd
->name_allocated
= 1;
864 new_layout
.release ();
867 /* Base command for "layout". */
870 tui_layout_command (const char *layout_name
, int from_tty
)
872 help_list (layout_list
, "layout ", all_commands
, gdb_stdout
);
875 /* Function to initialize gdb commands, for tui window layout
878 void _initialize_tui_layout ();
880 _initialize_tui_layout ()
882 add_prefix_cmd ("layout", class_tui
, tui_layout_command
, _("\
883 Change the layout of windows.\n\
884 Usage: layout prev | next | LAYOUT-NAME"),
885 &layout_list
, "layout ", 0, &cmdlist
);
887 add_cmd ("next", class_tui
, tui_next_layout_command
,
888 _("Apply the next TUI layout"),
890 add_cmd ("prev", class_tui
, tui_prev_layout_command
,
891 _("Apply the previous TUI layout"),
893 add_cmd ("regs", class_tui
, tui_regs_layout_command
,
894 _("Apply the TUI register layout"),
897 add_cmd ("new-layout", class_tui
, tui_new_layout_command
,
898 _("Create a new TUI layout.\n\
899 Usage: tui new-layout NAME WINDOW WEIGHT [WINDOW WEIGHT]...\n\
900 Create a new TUI layout. The new layout will be named NAME,\n\
901 and can be accessed using \"layout NAME\".\n\
902 The windows will be displayed in the specified order.\n\
903 Each WEIGHT is an integer, which holds the relative size\n\
904 to be allocated to the window."),
905 tui_get_cmd_list ());
907 initialize_layouts ();
This page took 0.214237 seconds and 5 git commands to generate.