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"
31 #include "tui/tui-command.h"
32 #include "tui/tui-data.h"
33 #include "tui/tui-wingeneral.h"
34 #include "tui/tui-stack.h"
35 #include "tui/tui-regs.h"
36 #include "tui/tui-win.h"
37 #include "tui/tui-winsource.h"
38 #include "tui/tui-disasm.h"
39 #include "tui/tui-layout.h"
40 #include "tui/tui-source.h"
41 #include "gdb_curses.h"
43 static void show_layout (enum tui_layout_type
);
44 static enum tui_layout_type
next_layout (void);
45 static enum tui_layout_type
prev_layout (void);
46 static void tui_layout_command (const char *, int);
47 static void extract_display_start_addr (struct gdbarch
**, CORE_ADDR
*);
50 /* The pre-defined layouts. */
51 static tui_layout_split
*standard_layouts
[UNDEFINED_LAYOUT
];
53 /* The layout that is currently applied. */
54 static std::unique_ptr
<tui_layout_base
> applied_layout
;
56 static enum tui_layout_type current_layout
= UNDEFINED_LAYOUT
;
58 /* Accessor for the current layout. */
60 tui_current_layout (void)
62 return current_layout
;
65 /* See tui-layout.h. */
68 tui_apply_current_layout ()
70 applied_layout
->apply (0, 0, tui_term_width (), tui_term_height ());
76 tui_adjust_window_height (struct tui_win_info
*win
, int new_height
)
78 applied_layout
->adjust_size (win
->name (), new_height
);
81 /* Show the screen layout defined. */
83 show_layout (enum tui_layout_type layout
)
85 enum tui_layout_type cur_layout
= tui_current_layout ();
87 if (layout
!= cur_layout
)
89 tui_make_all_invisible ();
90 applied_layout
= standard_layouts
[layout
]->clone ();
91 tui_apply_current_layout ();
92 current_layout
= layout
;
93 tui_delete_invisible_windows ();
98 /* Function to set the layout to SRC_COMMAND, DISASSEM_COMMAND,
99 SRC_DISASSEM_COMMAND, SRC_DATA_COMMAND, or DISASSEM_DATA_COMMAND. */
101 tui_set_layout (enum tui_layout_type layout_type
)
103 gdb_assert (layout_type
!= UNDEFINED_LAYOUT
);
105 enum tui_layout_type cur_layout
= tui_current_layout ();
106 struct gdbarch
*gdbarch
;
108 struct tui_win_info
*win_with_focus
= tui_win_with_focus ();
110 extract_display_start_addr (&gdbarch
, &addr
);
112 enum tui_layout_type new_layout
= layout_type
;
114 if (new_layout
!= cur_layout
)
116 tui_suppress_output suppress
;
118 show_layout (new_layout
);
120 /* Now determine where focus should be. */
121 if (win_with_focus
!= TUI_CMD_WIN
)
126 tui_set_win_focus_to (TUI_SRC_WIN
);
128 case DISASSEM_COMMAND
:
129 /* The previous layout was not showing code.
130 This can happen if there is no source
133 1. if the source file is in another dir OR
134 2. if target was compiled without -g
135 We still want to show the assembly though! */
137 tui_get_begin_asm_address (&gdbarch
, &addr
);
138 tui_set_win_focus_to (TUI_DISASM_WIN
);
140 case SRC_DISASSEM_COMMAND
:
141 /* The previous layout was not showing code.
142 This can happen if there is no source
145 1. if the source file is in another dir OR
146 2. if target was compiled without -g
147 We still want to show the assembly though! */
149 tui_get_begin_asm_address (&gdbarch
, &addr
);
150 if (win_with_focus
== TUI_SRC_WIN
)
151 tui_set_win_focus_to (TUI_SRC_WIN
);
153 tui_set_win_focus_to (TUI_DISASM_WIN
);
155 case SRC_DATA_COMMAND
:
156 if (win_with_focus
!= TUI_DATA_WIN
)
157 tui_set_win_focus_to (TUI_SRC_WIN
);
159 tui_set_win_focus_to (TUI_DATA_WIN
);
161 case DISASSEM_DATA_COMMAND
:
162 /* The previous layout was not showing code.
163 This can happen if there is no source
166 1. if the source file is in another dir OR
167 2. if target was compiled without -g
168 We still want to show the assembly though! */
170 tui_get_begin_asm_address (&gdbarch
, &addr
);
171 if (win_with_focus
!= TUI_DATA_WIN
)
172 tui_set_win_focus_to (TUI_DISASM_WIN
);
174 tui_set_win_focus_to (TUI_DATA_WIN
);
181 * Now update the window content.
183 tui_update_source_windows_with_addr (gdbarch
, addr
);
184 if (new_layout
== SRC_DATA_COMMAND
185 || new_layout
== DISASSEM_DATA_COMMAND
)
186 TUI_DATA_WIN
->show_registers (TUI_DATA_WIN
->get_current_group ());
190 /* See tui-layout.h. */
193 tui_add_win_to_layout (enum tui_win_type type
)
195 gdb_assert (type
== SRC_WIN
|| type
== DISASSEM_WIN
);
197 enum tui_layout_type cur_layout
= tui_current_layout ();
202 if (cur_layout
!= SRC_COMMAND
203 && cur_layout
!= SRC_DISASSEM_COMMAND
204 && cur_layout
!= SRC_DATA_COMMAND
)
206 if (cur_layout
== DISASSEM_DATA_COMMAND
)
207 tui_set_layout (SRC_DATA_COMMAND
);
209 tui_set_layout (SRC_COMMAND
);
213 if (cur_layout
!= DISASSEM_COMMAND
214 && cur_layout
!= SRC_DISASSEM_COMMAND
215 && cur_layout
!= DISASSEM_DATA_COMMAND
)
217 if (cur_layout
== SRC_DATA_COMMAND
)
218 tui_set_layout (DISASSEM_DATA_COMMAND
);
220 tui_set_layout (DISASSEM_COMMAND
);
226 /* Complete possible layout names. TEXT is the complete text entered so
227 far, WORD is the word currently being completed. */
230 layout_completer (struct cmd_list_element
*ignore
,
231 completion_tracker
&tracker
,
232 const char *text
, const char *word
)
234 static const char *layout_names
[] =
235 { "src", "asm", "split", "regs", "next", "prev", NULL
};
237 complete_on_enum (tracker
, layout_names
, text
, word
);
240 /* Function to set the layout to SRC, ASM, SPLIT, NEXT, PREV, DATA, or
243 tui_layout_command (const char *layout_name
, int from_tty
)
245 enum tui_layout_type new_layout
= UNDEFINED_LAYOUT
;
246 enum tui_layout_type cur_layout
= tui_current_layout ();
248 if (layout_name
== NULL
|| *layout_name
== '\0')
249 error (_("Usage: layout prev | next | LAYOUT-NAME"));
251 /* First check for ambiguous input. */
252 if (strcmp (layout_name
, "s") == 0)
253 error (_("Ambiguous command input."));
255 if (subset_compare (layout_name
, "src"))
256 new_layout
= SRC_COMMAND
;
257 else if (subset_compare (layout_name
, "asm"))
258 new_layout
= DISASSEM_COMMAND
;
259 else if (subset_compare (layout_name
, "split"))
260 new_layout
= SRC_DISASSEM_COMMAND
;
261 else if (subset_compare (layout_name
, "regs"))
263 if (cur_layout
== SRC_COMMAND
264 || cur_layout
== SRC_DATA_COMMAND
)
265 new_layout
= SRC_DATA_COMMAND
;
267 new_layout
= DISASSEM_DATA_COMMAND
;
269 else if (subset_compare (layout_name
, "next"))
270 new_layout
= next_layout ();
271 else if (subset_compare (layout_name
, "prev"))
272 new_layout
= prev_layout ();
274 error (_("Unrecognized layout: %s"), layout_name
);
276 /* Make sure the curses mode is enabled. */
278 tui_set_layout (new_layout
);
283 extract_display_start_addr (struct gdbarch
**gdbarch_p
, CORE_ADDR
*addr_p
)
285 enum tui_layout_type cur_layout
= tui_current_layout ();
286 struct gdbarch
*gdbarch
= get_current_arch ();
289 struct symtab_and_line cursal
= get_current_source_symtab_and_line ();
294 case SRC_DATA_COMMAND
:
295 gdbarch
= TUI_SRC_WIN
->gdbarch
;
296 find_line_pc (cursal
.symtab
,
297 TUI_SRC_WIN
->start_line_or_addr
.u
.line_no
,
301 case DISASSEM_COMMAND
:
302 case SRC_DISASSEM_COMMAND
:
303 case DISASSEM_DATA_COMMAND
:
304 gdbarch
= TUI_DISASM_WIN
->gdbarch
;
305 addr
= TUI_DISASM_WIN
->start_line_or_addr
.u
.addr
;
312 *gdbarch_p
= gdbarch
;
317 /* Answer the previous layout to cycle to. */
318 static enum tui_layout_type
323 new_layout
= tui_current_layout ();
324 if (new_layout
== UNDEFINED_LAYOUT
)
325 new_layout
= SRC_COMMAND
;
329 if (new_layout
== UNDEFINED_LAYOUT
)
330 new_layout
= SRC_COMMAND
;
333 return (enum tui_layout_type
) new_layout
;
337 /* Answer the next layout to cycle to. */
338 static enum tui_layout_type
343 new_layout
= tui_current_layout ();
344 if (new_layout
== SRC_COMMAND
)
345 new_layout
= DISASSEM_DATA_COMMAND
;
349 if (new_layout
== UNDEFINED_LAYOUT
)
350 new_layout
= DISASSEM_DATA_COMMAND
;
353 return (enum tui_layout_type
) new_layout
;
357 tui_gen_win_info::resize (int height_
, int width_
,
358 int origin_x_
, int origin_y_
)
360 if (width
== width_
&& height
== height_
361 && x
== origin_x_
&& y
== origin_y_
362 && handle
!= nullptr)
370 if (handle
!= nullptr)
373 wresize (handle
.get (), height
, width
);
374 mvwin (handle
.get (), y
, x
);
375 wmove (handle
.get (), 0, 0);
377 handle
.reset (nullptr);
381 if (handle
== nullptr)
389 /* Helper function that returns a TUI window, given its name. */
391 static tui_gen_win_info
*
392 tui_get_window_by_name (const std::string
&name
)
396 if (tui_win_list
[SRC_WIN
] == nullptr)
397 tui_win_list
[SRC_WIN
] = new tui_source_window ();
398 return tui_win_list
[SRC_WIN
];
400 else if (name
== "cmd")
402 if (tui_win_list
[CMD_WIN
] == nullptr)
403 tui_win_list
[CMD_WIN
] = new tui_cmd_window ();
404 return tui_win_list
[CMD_WIN
];
406 else if (name
== "regs")
408 if (tui_win_list
[DATA_WIN
] == nullptr)
409 tui_win_list
[DATA_WIN
] = new tui_data_window ();
410 return tui_win_list
[DATA_WIN
];
412 else if (name
== "asm")
414 if (tui_win_list
[DISASSEM_WIN
] == nullptr)
415 tui_win_list
[DISASSEM_WIN
] = new tui_disasm_window ();
416 return tui_win_list
[DISASSEM_WIN
];
420 gdb_assert (name
== "locator");
421 return tui_locator_win_info_ptr ();
425 /* See tui-layout.h. */
427 std::unique_ptr
<tui_layout_base
>
428 tui_layout_window::clone () const
430 tui_layout_window
*result
= new tui_layout_window (m_contents
.c_str ());
431 return std::unique_ptr
<tui_layout_base
> (result
);
434 /* See tui-layout.h. */
437 tui_layout_window::apply (int x_
, int y_
, int width_
, int height_
)
443 gdb_assert (m_window
!= nullptr);
444 m_window
->resize (height
, width
, x
, y
);
447 /* See tui-layout.h. */
450 tui_layout_window::get_sizes (int *min_height
, int *max_height
)
452 if (m_window
== nullptr)
453 m_window
= tui_get_window_by_name (m_contents
);
454 *min_height
= m_window
->min_height ();
455 *max_height
= m_window
->max_height ();
458 /* See tui-layout.h. */
461 tui_layout_window::top_boxed_p () const
463 gdb_assert (m_window
!= nullptr);
464 return m_window
->can_box ();
467 /* See tui-layout.h. */
470 tui_layout_window::bottom_boxed_p () const
472 gdb_assert (m_window
!= nullptr);
473 return m_window
->can_box ();
476 /* See tui-layout.h. */
479 tui_layout_split::add_split (int weight
)
481 tui_layout_split
*result
= new tui_layout_split ();
482 split s
= {weight
, std::unique_ptr
<tui_layout_base
> (result
)};
483 m_splits
.push_back (std::move (s
));
487 /* See tui-layout.h. */
490 tui_layout_split::add_window (const char *name
, int weight
)
492 tui_layout_window
*result
= new tui_layout_window (name
);
493 split s
= {weight
, std::unique_ptr
<tui_layout_base
> (result
)};
494 m_splits
.push_back (std::move (s
));
497 /* See tui-layout.h. */
499 std::unique_ptr
<tui_layout_base
>
500 tui_layout_split::clone () const
502 tui_layout_split
*result
= new tui_layout_split ();
503 for (const split
&item
: m_splits
)
505 std::unique_ptr
<tui_layout_base
> next
= item
.layout
->clone ();
506 split s
= {item
.weight
, std::move (next
)};
507 result
->m_splits
.push_back (std::move (s
));
509 return std::unique_ptr
<tui_layout_base
> (result
);
512 /* See tui-layout.h. */
515 tui_layout_split::get_sizes (int *min_height
, int *max_height
)
519 for (const split
&item
: m_splits
)
521 int new_min
, new_max
;
522 item
.layout
->get_sizes (&new_min
, &new_max
);
523 *min_height
+= new_min
;
524 *max_height
+= new_max
;
528 /* See tui-layout.h. */
531 tui_layout_split::top_boxed_p () const
533 if (m_splits
.empty ())
535 return m_splits
[0].layout
->top_boxed_p ();
538 /* See tui-layout.h. */
541 tui_layout_split::bottom_boxed_p () const
543 if (m_splits
.empty ())
545 return m_splits
.back ().layout
->top_boxed_p ();
548 /* See tui-layout.h. */
551 tui_layout_split::set_weights_from_heights ()
553 for (int i
= 0; i
< m_splits
.size (); ++i
)
554 m_splits
[i
].weight
= m_splits
[i
].layout
->height
;
557 /* See tui-layout.h. */
560 tui_layout_split::adjust_size (const char *name
, int new_height
)
562 /* Look through the children. If one is a layout holding the named
563 window, we're done; or if one actually is the named window,
565 int found_index
= -1;
566 for (int i
= 0; i
< m_splits
.size (); ++i
)
568 if (m_splits
[i
].layout
->adjust_size (name
, new_height
))
570 const char *win_name
= m_splits
[i
].layout
->get_name ();
571 if (win_name
!= nullptr && strcmp (name
, win_name
) == 0)
578 if (found_index
== -1)
580 if (m_splits
[found_index
].layout
->height
== new_height
)
583 set_weights_from_heights ();
584 int delta
= m_splits
[found_index
].weight
- new_height
;
585 m_splits
[found_index
].weight
= new_height
;
587 /* Distribute the "delta" over the next window; but if the next
588 window cannot hold it all, keep going until we either find a
589 window that does, or until we loop all the way around. */
590 for (int i
= 0; delta
!= 0 && i
< m_splits
.size () - 1; ++i
)
592 int index
= (found_index
+ 1 + i
) % m_splits
.size ();
594 int new_min
, new_max
;
595 m_splits
[index
].layout
->get_sizes (&new_min
, &new_max
);
599 /* The primary window grew, so we are trying to shrink other
601 int available
= m_splits
[index
].weight
- new_min
;
602 int shrink_by
= std::min (available
, -delta
);
603 m_splits
[index
].weight
-= shrink_by
;
608 /* The primary window shrank, so we are trying to grow other
610 int available
= new_max
- m_splits
[index
].weight
;
611 int grow_by
= std::min (available
, delta
);
612 m_splits
[index
].weight
+= grow_by
;
619 warning (_("Invalid window height specified"));
620 /* Effectively undo any modifications made here. */
621 set_weights_from_heights ();
625 /* Simply re-apply the updated layout. */
626 apply (x
, y
, width
, height
);
632 /* See tui-layout.h. */
635 tui_layout_split::apply (int x_
, int y_
, int width_
, int height_
)
647 /* True if this window will share a box border with the previous
648 window in the list. */
652 std::vector
<height_info
> info (m_splits
.size ());
654 /* Step 1: Find the min and max height of each sub-layout.
655 Fixed-sized layouts are given their desired height, and then the
656 remaining space is distributed among the remaining windows
657 according to the weights given. */
658 int available_height
= height
;
660 int total_weight
= 0;
661 for (int i
= 0; i
< m_splits
.size (); ++i
)
663 bool cmd_win_already_exists
= TUI_CMD_WIN
!= nullptr;
665 /* Always call get_sizes, to ensure that the window is
666 instantiated. This is a bit gross but less gross than adding
667 special cases for this in other places. */
668 m_splits
[i
].layout
->get_sizes (&info
[i
].min_height
, &info
[i
].max_height
);
671 && cmd_win_already_exists
672 && m_splits
[i
].layout
->get_name () != nullptr
673 && strcmp (m_splits
[i
].layout
->get_name (), "cmd") == 0)
675 /* If this layout has never been applied, then it means the
676 user just changed the layout. In this situation, it's
677 desirable to keep the size of the command window the
678 same. Setting the min and max heights this way ensures
679 that the resizing step, below, does the right thing with
681 info
[i
].min_height
= TUI_CMD_WIN
->height
;
682 info
[i
].max_height
= TUI_CMD_WIN
->height
;
685 if (info
[i
].min_height
== info
[i
].max_height
)
686 available_height
-= info
[i
].min_height
;
690 total_weight
+= m_splits
[i
].weight
;
693 /* Two adjacent boxed windows will share a border, making a bit
694 more height available. */
696 && m_splits
[i
- 1].layout
->bottom_boxed_p ()
697 && m_splits
[i
].layout
->top_boxed_p ())
698 info
[i
].share_box
= true;
701 /* Step 2: Compute the height of each sub-layout. Fixed-sized items
702 are given their fixed size, while others are resized according to
705 for (int i
= 0; i
< m_splits
.size (); ++i
)
707 /* Compute the height and clamp to the allowable range. */
708 info
[i
].height
= available_height
* m_splits
[i
].weight
/ total_weight
;
709 if (info
[i
].height
> info
[i
].max_height
)
710 info
[i
].height
= info
[i
].max_height
;
711 if (info
[i
].height
< info
[i
].min_height
)
712 info
[i
].height
= info
[i
].min_height
;
713 /* If there is any leftover height, just redistribute it to the
714 last resizeable window, by dropping it from the allocated
715 height. We could try to be fancier here perhaps, by
716 redistributing this height among all windows, not just the
718 if (info
[i
].min_height
!= info
[i
].max_height
)
720 used_height
+= info
[i
].height
;
721 if (info
[i
].share_box
)
726 /* Allocate any leftover height. */
727 if (available_height
>= used_height
&& last_index
!= -1)
728 info
[last_index
].height
+= available_height
- used_height
;
730 /* Step 3: Resize. */
731 int height_accum
= 0;
732 for (int i
= 0; i
< m_splits
.size (); ++i
)
734 /* If we fall off the bottom, just make allocations overlap.
736 if (height_accum
+ info
[i
].height
> height
)
737 height_accum
= height
- info
[i
].height
;
738 else if (info
[i
].share_box
)
740 m_splits
[i
].layout
->apply (x
, y
+ height_accum
, width
, info
[i
].height
);
741 height_accum
+= info
[i
].height
;
748 initialize_layouts ()
750 standard_layouts
[SRC_COMMAND
] = new tui_layout_split ();
751 standard_layouts
[SRC_COMMAND
]->add_window ("src", 2);
752 standard_layouts
[SRC_COMMAND
]->add_window ("locator", 0);
753 standard_layouts
[SRC_COMMAND
]->add_window ("cmd", 1);
755 standard_layouts
[DISASSEM_COMMAND
] = new tui_layout_split ();
756 standard_layouts
[DISASSEM_COMMAND
]->add_window ("asm", 2);
757 standard_layouts
[DISASSEM_COMMAND
]->add_window ("locator", 0);
758 standard_layouts
[DISASSEM_COMMAND
]->add_window ("cmd", 1);
760 standard_layouts
[SRC_DATA_COMMAND
] = new tui_layout_split ();
761 standard_layouts
[SRC_DATA_COMMAND
]->add_window ("regs", 1);
762 standard_layouts
[SRC_DATA_COMMAND
]->add_window ("src", 1);
763 standard_layouts
[SRC_DATA_COMMAND
]->add_window ("locator", 0);
764 standard_layouts
[SRC_DATA_COMMAND
]->add_window ("cmd", 1);
766 standard_layouts
[DISASSEM_DATA_COMMAND
] = new tui_layout_split ();
767 standard_layouts
[DISASSEM_DATA_COMMAND
]->add_window ("regs", 1);
768 standard_layouts
[DISASSEM_DATA_COMMAND
]->add_window ("asm", 1);
769 standard_layouts
[DISASSEM_DATA_COMMAND
]->add_window ("locator", 0);
770 standard_layouts
[DISASSEM_DATA_COMMAND
]->add_window ("cmd", 1);
772 standard_layouts
[SRC_DISASSEM_COMMAND
] = new tui_layout_split ();
773 standard_layouts
[SRC_DISASSEM_COMMAND
]->add_window ("src", 1);
774 standard_layouts
[SRC_DISASSEM_COMMAND
]->add_window ("asm", 1);
775 standard_layouts
[SRC_DISASSEM_COMMAND
]->add_window ("locator", 0);
776 standard_layouts
[SRC_DISASSEM_COMMAND
]->add_window ("cmd", 1);
781 /* Function to initialize gdb commands, for tui window layout
784 void _initialize_tui_layout ();
786 _initialize_tui_layout ()
788 struct cmd_list_element
*cmd
;
790 cmd
= add_com ("layout", class_tui
, tui_layout_command
, _("\
791 Change the layout of windows.\n\
792 Usage: layout prev | next | LAYOUT-NAME\n\
794 src : Displays source and command windows.\n\
795 asm : Displays disassembly and command windows.\n\
796 split : Displays source, disassembly and command windows.\n\
797 regs : Displays register window. If existing layout\n\
798 is source/command or assembly/command, the \n\
799 register window is displayed. If the\n\
800 source/assembly/command (split) is displayed, \n\
801 the register window is displayed with \n\
802 the window that has current logical focus."));
803 set_cmd_completer (cmd
, layout_completer
);
805 initialize_layouts ();