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