]> git.ipfire.org Git - thirdparty/binutils-gdb.git/blob - gdb/tui/tui-layout.c
Update copyright year range in header of all files managed by GDB
[thirdparty/binutils-gdb.git] / gdb / tui / tui-layout.c
1 /* TUI layout window management.
2
3 Copyright (C) 1998-2024 Free Software Foundation, Inc.
4
5 Contributed by Hewlett-Packard Company.
6
7 This file is part of GDB.
8
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 3 of the License, or
12 (at your option) any later version.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>. */
21
22 #include "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-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"
47
48 /* The layouts. */
49 static std::vector<std::unique_ptr<tui_layout_split>> layouts;
50
51 /* The layout that is currently applied. */
52 static std::unique_ptr<tui_layout_base> applied_layout;
53
54 /* The "skeleton" version of the layout that is currently applied. */
55 static tui_layout_split *applied_skeleton;
56
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;
61
62 /* See tui-data.h. */
63 std::vector<tui_win_info *> tui_windows;
64
65 /* See tui-layout.h. */
66
67 void
68 tui_apply_current_layout (bool preserve_cmd_win_size_p)
69 {
70 for (tui_win_info *win_info : tui_windows)
71 win_info->make_visible (false);
72
73 applied_layout->apply (0, 0, tui_term_width (), tui_term_height (),
74 preserve_cmd_win_size_p);
75
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;
81
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 ());
85
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);
89
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)
93 {
94 if (!win_info->is_visible ())
95 {
96 if (focus == win_info)
97 tui_set_win_focus_to (new_tui_windows[0]);
98 delete win_info;
99 }
100 }
101
102 /* Replace the global list of active windows. */
103 tui_windows = std::move (new_tui_windows);
104 }
105
106 /* See tui-layout. */
107
108 void
109 tui_adjust_window_height (struct tui_win_info *win, int new_height)
110 {
111 applied_layout->set_height (win->name (), new_height);
112 }
113
114 /* See tui-layout. */
115
116 void
117 tui_adjust_window_width (struct tui_win_info *win, int new_width)
118 {
119 applied_layout->set_width (win->name (), new_width);
120 }
121
122 /* Set the current layout to LAYOUT. */
123
124 static void
125 tui_set_layout (tui_layout_split *layout)
126 {
127 std::string old_fingerprint;
128 if (applied_layout != nullptr)
129 old_fingerprint = applied_layout->layout_fingerprint ();
130
131 applied_skeleton = layout;
132 applied_layout = layout->clone ();
133
134 std::string new_fingerprint = applied_layout->layout_fingerprint ();
135 bool preserve_command_window_size
136 = (TUI_CMD_WIN != nullptr && old_fingerprint == new_fingerprint);
137
138 tui_apply_current_layout (preserve_command_window_size);
139 }
140
141 /* See tui-layout.h. */
142
143 void
144 tui_add_win_to_layout (enum tui_win_type type)
145 {
146 gdb_assert (type == SRC_WIN || type == DISASSEM_WIN);
147
148 /* If the window already exists, no need to add it. */
149 if (tui_win_list[type] != nullptr)
150 return;
151
152 /* If the window we are trying to replace doesn't exist, we're
153 done. */
154 enum tui_win_type other = type == SRC_WIN ? DISASSEM_WIN : SRC_WIN;
155 if (tui_win_list[other] == nullptr)
156 return;
157
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);
161 }
162
163 /* Find LAYOUT in the "layouts" global and return its index. */
164
165 static size_t
166 find_layout (tui_layout_split *layout)
167 {
168 for (size_t i = 0; i < layouts.size (); ++i)
169 {
170 if (layout == layouts[i].get ())
171 return i;
172 }
173 gdb_assert_not_reached ("layout not found!?");
174 }
175
176 /* Function to set the layout. */
177
178 static void
179 tui_apply_layout (const char *args, int from_tty, cmd_list_element *command)
180 {
181 tui_layout_split *layout = (tui_layout_split *) command->context ();
182
183 /* Make sure the curses mode is enabled. */
184 tui_enable ();
185 tui_set_layout (layout);
186 }
187
188 /* See tui-layout.h. */
189
190 void
191 tui_next_layout ()
192 {
193 size_t index = find_layout (applied_skeleton);
194 ++index;
195 if (index == layouts.size ())
196 index = 0;
197 tui_set_layout (layouts[index].get ());
198 }
199
200 /* Implement the "layout next" command. */
201
202 static void
203 tui_next_layout_command (const char *arg, int from_tty)
204 {
205 tui_enable ();
206 tui_next_layout ();
207 }
208
209 /* See tui-layout.h. */
210
211 void
212 tui_set_initial_layout ()
213 {
214 tui_set_layout (layouts[0].get ());
215 }
216
217 /* Implement the "layout prev" command. */
218
219 static void
220 tui_prev_layout_command (const char *arg, int from_tty)
221 {
222 tui_enable ();
223 size_t index = find_layout (applied_skeleton);
224 if (index == 0)
225 index = layouts.size ();
226 --index;
227 tui_set_layout (layouts[index].get ());
228 }
229
230
231 /* See tui-layout.h. */
232
233 void
234 tui_regs_layout ()
235 {
236 /* If there's already a register window, we're done. */
237 if (TUI_DATA_WIN != nullptr)
238 return;
239
240 tui_set_layout (TUI_DISASM_WIN != nullptr
241 ? asm_regs_layout
242 : src_regs_layout);
243 }
244
245 /* Implement the "layout regs" command. */
246
247 static void
248 tui_regs_layout_command (const char *arg, int from_tty)
249 {
250 tui_enable ();
251 tui_regs_layout ();
252 }
253
254 /* See tui-layout.h. */
255
256 void
257 tui_remove_some_windows ()
258 {
259 tui_win_info *focus = tui_win_with_focus ();
260
261 if (strcmp (focus->name (), CMD_NAME) == 0)
262 {
263 /* Try leaving the source or disassembly window. If neither
264 exists, just do nothing. */
265 focus = TUI_SRC_WIN;
266 if (focus == nullptr)
267 focus = TUI_DISASM_WIN;
268 if (focus == nullptr)
269 return;
270 }
271
272 applied_layout->remove_windows (focus->name ());
273 tui_apply_current_layout (true);
274 }
275
276 void
277 tui_win_info::resize (int height_, int width_,
278 int origin_x_, int origin_y_)
279 {
280 if (width == width_ && height == height_
281 && x == origin_x_ && y == origin_y_
282 && handle != nullptr)
283 return;
284
285 width = width_;
286 height = height_;
287 x = origin_x_;
288 y = origin_y_;
289
290 if (handle != nullptr)
291 {
292 #ifdef HAVE_WRESIZE
293 wresize (handle.get (), height, width);
294 mvwin (handle.get (), y, x);
295 wmove (handle.get (), 0, 0);
296 #else
297 handle.reset (nullptr);
298 #endif
299 }
300
301 if (handle == nullptr)
302 make_window ();
303
304 rerender ();
305 }
306
307 \f
308
309 /* Helper function to create one of the built-in (non-status)
310 windows. */
311
312 template<enum tui_win_type V, class T>
313 static tui_win_info *
314 make_standard_window (const char *)
315 {
316 if (tui_win_list[V] == nullptr)
317 tui_win_list[V] = new T ();
318 return tui_win_list[V];
319 }
320
321 /* A map holding all the known window types, keyed by name. */
322
323 static window_types_map known_window_types;
324
325 /* See tui-layout.h. */
326
327 known_window_names_range
328 all_known_window_names ()
329 {
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);
333 }
334
335 /* Helper function that returns a TUI window, given its name. */
336
337 static tui_win_info *
338 tui_get_window_by_name (const std::string &name)
339 {
340 for (tui_win_info *window : tui_windows)
341 if (name == window->name ())
342 return window;
343
344 auto iter = known_window_types.find (name);
345 if (iter == known_window_types.end ())
346 error (_("Unknown window type \"%s\""), name.c_str ());
347
348 tui_win_info *result = iter->second (name.c_str ());
349 if (result == nullptr)
350 error (_("Could not create window \"%s\""), name.c_str ());
351 return result;
352 }
353
354 /* Initialize the known window types. */
355
356 static void
357 initialize_known_windows ()
358 {
359 known_window_types.emplace (SRC_NAME,
360 make_standard_window<SRC_WIN,
361 tui_source_window>);
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,
366 tui_data_window>);
367 known_window_types.emplace (DISASSEM_NAME,
368 make_standard_window<DISASSEM_WIN,
369 tui_disasm_window>);
370 known_window_types.emplace (STATUS_NAME,
371 make_standard_window<STATUS_WIN,
372 tui_status_window>);
373 }
374
375 /* See tui-layout.h. */
376
377 void
378 tui_register_window (const char *name, window_factory &&factory)
379 {
380 std::string name_copy = name;
381
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);
385
386 for (const char &c : name_copy)
387 {
388 if (ISSPACE (c))
389 error (_("invalid whitespace character in window name"));
390
391 if (!ISALNUM (c) && strchr ("-_.", c) == nullptr)
392 error (_("invalid character '%c' in window name"), c);
393 }
394
395 if (!ISALPHA (name_copy[0]))
396 error (_("window name must start with a letter, not '%c'"), name_copy[0]);
397
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. */
401
402 auto iter = known_window_types.find (name);
403 if (iter != known_window_types.end ())
404 known_window_types.erase (iter);
405
406 known_window_types.emplace (std::move (name_copy),
407 std::move (factory));
408 }
409
410 /* See tui-layout.h. */
411
412 std::unique_ptr<tui_layout_base>
413 tui_layout_window::clone () const
414 {
415 tui_layout_window *result = new tui_layout_window (m_contents.c_str ());
416 return std::unique_ptr<tui_layout_base> (result);
417 }
418
419 /* See tui-layout.h. */
420
421 void
422 tui_layout_window::apply (int x_, int y_, int width_, int height_,
423 bool preserve_cmd_win_size_p)
424 {
425 x = x_;
426 y = y_;
427 width = width_;
428 height = height_;
429 gdb_assert (m_window != nullptr);
430 if (width == 0 || height == 0)
431 {
432 /* The window was dropped, so it's going to be deleted, reset the
433 soon to be dangling pointer. */
434 m_window = nullptr;
435 return;
436 }
437 m_window->resize (height, width, x, y);
438 }
439
440 /* See tui-layout.h. */
441
442 void
443 tui_layout_window::get_sizes (bool height, int *min_value, int *max_value)
444 {
445 TUI_SCOPED_DEBUG_ENTER_EXIT;
446
447 if (m_window == nullptr)
448 m_window = tui_get_window_by_name (m_contents);
449
450 tui_debug_printf ("window = %s, getting %s",
451 m_window->name (), (height ? "height" : "width"));
452
453 if (height)
454 {
455 *min_value = m_window->min_height ();
456 *max_value = m_window->max_height ();
457 }
458 else
459 {
460 *min_value = m_window->min_width ();
461 *max_value = m_window->max_width ();
462 }
463
464 tui_debug_printf ("min = %d, max = %d", *min_value, *max_value);
465 }
466
467 /* See tui-layout.h. */
468
469 bool
470 tui_layout_window::first_edge_has_border_p () const
471 {
472 gdb_assert (m_window != nullptr);
473 return m_window->can_box ();
474 }
475
476 /* See tui-layout.h. */
477
478 bool
479 tui_layout_window::last_edge_has_border_p () const
480 {
481 gdb_assert (m_window != nullptr);
482 return m_window->can_box ();
483 }
484
485 /* See tui-layout.h. */
486
487 void
488 tui_layout_window::replace_window (const char *name, const char *new_window)
489 {
490 if (m_contents == name)
491 {
492 m_contents = new_window;
493 if (m_window != nullptr)
494 {
495 m_window->make_visible (false);
496 m_window = tui_get_window_by_name (m_contents);
497 }
498 }
499 }
500
501 /* See tui-layout.h. */
502
503 void
504 tui_layout_window::specification (ui_file *output, int depth)
505 {
506 gdb_puts (get_name (), output);
507 }
508
509 /* See tui-layout.h. */
510
511 std::string
512 tui_layout_window::layout_fingerprint () const
513 {
514 if (strcmp (get_name (), "cmd") == 0)
515 return "C";
516 else
517 return "";
518 }
519
520 /* See tui-layout.h. */
521
522 void
523 tui_layout_split::add_split (std::unique_ptr<tui_layout_split> &&layout,
524 int weight)
525 {
526 split s = {weight, std::move (layout)};
527 m_splits.push_back (std::move (s));
528 }
529
530 /* See tui-layout.h. */
531
532 void
533 tui_layout_split::add_window (const char *name, int weight)
534 {
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));
538 }
539
540 /* See tui-layout.h. */
541
542 std::unique_ptr<tui_layout_base>
543 tui_layout_split::clone () const
544 {
545 tui_layout_split *result = new tui_layout_split (m_vertical);
546 for (const split &item : m_splits)
547 {
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));
551 }
552 return std::unique_ptr<tui_layout_base> (result);
553 }
554
555 /* See tui-layout.h. */
556
557 void
558 tui_layout_split::get_sizes (bool height, int *min_value, int *max_value)
559 {
560 TUI_SCOPED_DEBUG_ENTER_EXIT;
561
562 *min_value = 0;
563 *max_value = 0;
564 bool first_time = true;
565 for (const split &item : m_splits)
566 {
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)
573 {
574 *min_value += new_min;
575 *max_value += new_max;
576 }
577 else
578 {
579 *min_value = std::max (*min_value, new_min);
580 *max_value = std::min (*max_value, new_max);
581 }
582 first_time = false;
583 }
584
585 tui_debug_printf ("min_value = %d, max_value = %d", *min_value, *max_value);
586 }
587
588 /* See tui-layout.h. */
589
590 bool
591 tui_layout_split::first_edge_has_border_p () const
592 {
593 if (m_splits.empty ())
594 return false;
595 return m_splits[0].layout->first_edge_has_border_p ();
596 }
597
598 /* See tui-layout.h. */
599
600 bool
601 tui_layout_split::last_edge_has_border_p () const
602 {
603 if (m_splits.empty ())
604 return false;
605 return m_splits.back ().layout->last_edge_has_border_p ();
606 }
607
608 /* See tui-layout.h. */
609
610 void
611 tui_layout_split::set_weights_from_sizes ()
612 {
613 for (int i = 0; i < m_splits.size (); ++i)
614 m_splits[i].weight
615 = m_vertical ? m_splits[i].layout->height : m_splits[i].layout->width;
616 }
617
618 /* See tui-layout.h. */
619
620 std::string
621 tui_layout_split::tui_debug_weights_to_string () const
622 {
623 std::string str;
624
625 for (int i = 0; i < m_splits.size (); ++i)
626 {
627 if (i > 0)
628 str += ", ";
629 str += string_printf ("[%d] %d", i, m_splits[i].weight);
630 }
631
632 return str;
633 }
634
635 /* See tui-layout.h. */
636
637 void
638 tui_layout_split::tui_debug_print_size_info
639 (const std::vector<tui_layout_split::size_info> &info)
640 {
641 gdb_assert (debug_tui);
642
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);
648 }
649
650 /* See tui-layout.h. */
651
652 tui_adjust_result
653 tui_layout_split::set_size (const char *name, int new_size, bool set_width_p)
654 {
655 TUI_SCOPED_DEBUG_ENTER_EXIT;
656
657 tui_debug_printf ("this = %p, name = %s, new_size = %d",
658 this, name, new_size);
659
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,
662 update it. */
663 int found_index = -1;
664 for (int i = 0; i < m_splits.size (); ++i)
665 {
666 tui_adjust_result adjusted;
667 if (set_width_p)
668 adjusted = m_splits[i].layout->set_width (name, new_size);
669 else
670 adjusted = m_splits[i].layout->set_height (name, new_size);
671 if (adjusted == HANDLED)
672 return HANDLED;
673 if (adjusted == FOUND)
674 {
675 if (set_width_p ? m_vertical : !m_vertical)
676 return FOUND;
677 found_index = i;
678 break;
679 }
680 }
681
682 if (found_index == -1)
683 return NOT_FOUND;
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)
688 return HANDLED;
689
690 tui_debug_printf ("found window %s at index %d", name, found_index);
691
692 set_weights_from_sizes ();
693 int delta = m_splits[found_index].weight - new_size;
694 m_splits[found_index].weight = new_size;
695
696 tui_debug_printf ("before delta (%d) distribution, weights: %s",
697 delta, tui_debug_weights_to_string ().c_str ());
698
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 ())
705 {
706 int index = (found_index + 1 + i) % m_splits.size ();
707 if (index == found_index)
708 {
709 if (!found_window_that_can_grow_p)
710 break;
711 found_window_that_can_grow_p = false;
712 continue;
713 }
714
715 int new_min, new_max;
716 m_splits[index].layout->get_sizes (m_vertical, &new_min, &new_max);
717
718 if (delta < 0)
719 {
720 /* The primary window grew, so we are trying to shrink other
721 windows. */
722 if (m_splits[index].weight > new_min)
723 {
724 m_splits[index].weight -= 1;
725 delta += 1;
726 found_window_that_can_grow_p = true;
727 }
728 }
729 else
730 {
731 /* The primary window shrank, so we are trying to grow other
732 windows. */
733 if (m_splits[index].weight < new_max)
734 {
735 m_splits[index].weight += 1;
736 delta -= 1;
737 found_window_that_can_grow_p = true;
738 }
739 }
740
741 tui_debug_printf ("index = %d, weight now: %d",
742 index, m_splits[index].weight);
743 }
744
745 tui_debug_printf ("after delta (%d) distribution, weights: %s",
746 delta, tui_debug_weights_to_string ().c_str ());
747
748 if (delta != 0)
749 {
750 if (set_width_p)
751 warning (_("Invalid window width specified"));
752 else
753 warning (_("Invalid window height specified"));
754 /* Effectively undo any modifications made here. */
755 set_weights_from_sizes ();
756 }
757 else
758 {
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);
764 }
765
766 return HANDLED;
767 }
768
769 /* See tui-layout.h. */
770
771 void
772 tui_layout_split::apply (int x_, int y_, int width_, int height_,
773 bool preserve_cmd_win_size_p)
774 {
775 TUI_SCOPED_DEBUG_ENTER_EXIT;
776
777 x = x_;
778 y = y_;
779 width = width_;
780 height = height_;
781
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
785 it if needed. */
786 struct old_size_info
787 {
788 /* Constructor. */
789 old_size_info (int index_, int min_size_, int max_size_)
790 : index (index_),
791 min_size (min_size_),
792 max_size (max_size_)
793 { /* Nothing. */ }
794
795 /* The index in m_splits where the cmd window was found. */
796 int index;
797
798 /* The previous min/max size. */
799 int min_size;
800 int max_size;
801 };
802
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;
805
806 std::vector<size_info> info (m_splits.size ());
807
808 tui_debug_printf ("weights are: %s",
809 tui_debug_weights_to_string ().c_str ());
810
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;
816 int last_index = -1;
817 int total_weight = 0;
818 int prev = -1;
819 for (int i = 0; i < m_splits.size (); ++i)
820 {
821 bool cmd_win_already_exists = TUI_CMD_WIN != nullptr;
822
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,
827 &info[i].max_size);
828
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)
833 {
834 /* Save the old cmd window information, in case we need to
835 restore it later. */
836 old_cmd_info.emplace (i, info[i].min_size, info[i].max_size);
837
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
843 this window. */
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;
848 }
849
850 if (info[i].min_size > info[i].max_size)
851 {
852 /* There is not enough room for this window, drop it. */
853 info[i].min_size = 0;
854 info[i].max_size = 0;
855 continue;
856 }
857
858 /* Two adjacent boxed windows will share a border. */
859 if (prev != -1
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;
863
864 if (info[i].min_size == info[i].max_size)
865 {
866 available_size -= info[i].min_size;
867 if (info[i].share_box)
868 {
869 /* A shared border makes a bit more size available. */
870 ++available_size;
871 }
872 }
873 else
874 {
875 last_index = i;
876 total_weight += m_splits[i].weight;
877 }
878
879 prev = i;
880 }
881
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);
887
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
890 their weight. */
891 int used_size = 0;
892 for (int i = 0; i < m_splits.size (); ++i)
893 {
894 if (info[i].min_size != info[i].max_size)
895 {
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
906 this function. */
907 used_size += info[i].size;
908 if (info[i].share_box)
909 {
910 /* A shared border makes a bit more size available. */
911 --used_size;
912 }
913 }
914 else
915 info[i].size = info[i].min_size;
916 }
917
918 if (debug_tui)
919 {
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);
926 }
927
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.
931
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;
937
938 /* Allocate any leftover size. */
939 if (available_size != used_size && last_index != -1)
940 {
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 ())
948 {
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.
952
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
956 applied correctly.
957
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)
962 {
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.
969
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))
978 {
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;
981 tui_debug_printf
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 ();
986 }
987 else if (!found_window_that_can_grow_p)
988 break;
989 found_window_that_can_grow_p = false;
990 }
991
992 if (available_size > used_size
993 && info[idx].size < info[idx].max_size)
994 {
995 found_window_that_can_grow_p = true;
996 info[idx].size += 1;
997 used_size += 1;
998 }
999 else if (available_size < used_size
1000 && info[idx].size > info[idx].min_size)
1001 {
1002 found_window_that_can_grow_p = true;
1003 info[idx].size -= 1;
1004 used_size -= 1;
1005 }
1006 }
1007
1008 if (debug_tui)
1009 {
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);
1016 }
1017 }
1018
1019 /* Step 3: Resize. */
1020 int size_accum = 0;
1021 const int maximum = m_vertical ? height : width;
1022 for (int i = 0; i < m_splits.size (); ++i)
1023 {
1024 /* If we fall off the bottom, just make allocations overlap.
1025 GIGO. */
1026 if (size_accum + info[i].size > maximum)
1027 size_accum = maximum - info[i].size;
1028 else if (info[i].share_box)
1029 --size_accum;
1030 if (m_vertical)
1031 m_splits[i].layout->apply (x, y + size_accum, width, info[i].size,
1032 preserve_cmd_win_size_p);
1033 else
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;
1037 }
1038 }
1039
1040 /* See tui-layout.h. */
1041
1042 void
1043 tui_layout_split::remove_windows (const char *name)
1044 {
1045 for (int i = 0; i < m_splits.size (); ++i)
1046 {
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)
1053 {
1054 /* Keep. */
1055 }
1056 else
1057 {
1058 m_splits.erase (m_splits.begin () + i);
1059 --i;
1060 }
1061 }
1062 }
1063
1064 /* See tui-layout.h. */
1065
1066 void
1067 tui_layout_split::replace_window (const char *name, const char *new_window)
1068 {
1069 for (auto &item : m_splits)
1070 item.layout->replace_window (name, new_window);
1071 }
1072
1073 /* See tui-layout.h. */
1074
1075 void
1076 tui_layout_split::specification (ui_file *output, int depth)
1077 {
1078 if (depth > 0)
1079 gdb_puts ("{", output);
1080
1081 if (!m_vertical)
1082 gdb_puts ("-horizontal ", output);
1083
1084 bool first = true;
1085 for (auto &item : m_splits)
1086 {
1087 if (!first)
1088 gdb_puts (" ", output);
1089 first = false;
1090 item.layout->specification (output, depth + 1);
1091 gdb_printf (output, " %d", item.weight);
1092 }
1093
1094 if (depth > 0)
1095 gdb_puts ("}", output);
1096 }
1097
1098 /* See tui-layout.h. */
1099
1100 std::string
1101 tui_layout_split::layout_fingerprint () const
1102 {
1103 for (auto &item : m_splits)
1104 {
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;
1108 }
1109
1110 return "";
1111 }
1112
1113 /* Destroy the layout associated with SELF. */
1114
1115 static void
1116 destroy_layout (struct cmd_list_element *self, void *context)
1117 {
1118 tui_layout_split *layout = (tui_layout_split *) context;
1119 size_t index = find_layout (layout);
1120 layouts.erase (layouts.begin () + index);
1121 }
1122
1123 /* List holding the sub-commands of "layout". */
1124
1125 static struct cmd_list_element *layout_list;
1126
1127 /* Called to implement 'tui layout'. */
1128
1129 static void
1130 tui_layout_command (const char *args, int from_tty)
1131 {
1132 help_list (layout_list, "tui layout ", all_commands, gdb_stdout);
1133 }
1134
1135 /* Add a "layout" command with name NAME that switches to LAYOUT. */
1136
1137 static struct cmd_list_element *
1138 add_layout_command (const char *name, tui_layout_split *layout)
1139 {
1140 struct cmd_list_element *cmd;
1141
1142 string_file spec;
1143 layout->specification (&spec, 0);
1144
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 ());
1150
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;
1157 doc.release ();
1158 layouts.emplace_back (layout);
1159
1160 return cmd;
1161 }
1162
1163 /* Initialize the standard layouts. */
1164
1165 static void
1166 initialize_layouts ()
1167 {
1168 tui_layout_split *layout;
1169
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);
1175
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);
1181
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);
1188
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;
1196
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;
1204 }
1205
1206 \f
1207
1208 /* A helper function that returns true if NAME is the name of an
1209 available window. */
1210
1211 static bool
1212 validate_window_name (const std::string &name)
1213 {
1214 auto iter = known_window_types.find (name);
1215 return iter != known_window_types.end ();
1216 }
1217
1218 /* Implementation of the "tui new-layout" command. */
1219
1220 static void
1221 tui_new_layout_command (const char *spec, int from_tty)
1222 {
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 '-'"));
1228
1229 bool is_vertical = true;
1230 spec = skip_spaces (spec);
1231 if (check_for_argument (&spec, "-horizontal"))
1232 is_vertical = false;
1233
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;
1237 while (true)
1238 {
1239 spec = skip_spaces (spec);
1240 if (spec[0] == '\0')
1241 break;
1242
1243 if (spec[0] == '{')
1244 {
1245 is_vertical = true;
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));
1250 continue;
1251 }
1252
1253 bool is_close = false;
1254 std::string name;
1255 if (spec[0] == '}')
1256 {
1257 is_close = true;
1258 ++spec;
1259 if (splits.size () == 1)
1260 error (_("Extra '}' in layout specification"));
1261 }
1262 else
1263 {
1264 name = extract_arg (&spec);
1265 if (name.empty ())
1266 break;
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 ());
1271 }
1272
1273 ULONGEST weight = get_ulongest (&spec, '}');
1274 if ((int) weight != weight)
1275 error (_("Weight out of range: %s"), pulongest (weight));
1276 if (is_close)
1277 {
1278 std::unique_ptr<tui_layout_split> last_split
1279 = std::move (splits.back ());
1280 splits.pop_back ();
1281 splits.back ()->add_split (std::move (last_split), weight);
1282 }
1283 else
1284 {
1285 splits.back ()->add_window (name.c_str (), weight);
1286 seen_windows.insert (name);
1287 }
1288 }
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"));
1295
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 ();
1304 }
1305
1306 /* Function to initialize gdb commands, for tui window layout
1307 manipulation. */
1308
1309 void _initialize_tui_layout ();
1310 void
1311 _initialize_tui_layout ()
1312 {
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);
1319
1320 add_cmd ("next", class_tui, tui_next_layout_command,
1321 _("Apply the next TUI layout."),
1322 &layout_list);
1323 add_cmd ("prev", class_tui, tui_prev_layout_command,
1324 _("Apply the previous TUI layout."),
1325 &layout_list);
1326 add_cmd ("regs", class_tui, tui_regs_layout_command,
1327 _("Apply the TUI register layout."),
1328 &layout_list);
1329
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 ());
1342
1343 initialize_layouts ();
1344 initialize_known_windows ();
1345 }