| /* TUI layout window management. |
| |
| Copyright (C) 1998-2024 Free Software Foundation, Inc. |
| |
| Contributed by Hewlett-Packard Company. |
| |
| This file is part of GDB. |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/>. */ |
| |
| #include "command.h" |
| #include "symtab.h" |
| #include "frame.h" |
| #include "cli/cli-decode.h" |
| #include "cli/cli-utils.h" |
| #include <unordered_set> |
| |
| #include "tui/tui.h" |
| #include "tui/tui-command.h" |
| #include "tui/tui-data.h" |
| #include "tui/tui-wingeneral.h" |
| #include "tui/tui-status.h" |
| #include "tui/tui-regs.h" |
| #include "tui/tui-win.h" |
| #include "tui/tui-disasm.h" |
| #include "tui/tui-layout.h" |
| #include "tui/tui-source.h" |
| #include "gdb_curses.h" |
| #include "gdbsupport/gdb-safe-ctype.h" |
| |
| /* The layouts. */ |
| static std::vector<std::unique_ptr<tui_layout_split>> layouts; |
| |
| /* The layout that is currently applied. */ |
| static std::unique_ptr<tui_layout_base> applied_layout; |
| |
| /* The "skeleton" version of the layout that is currently applied. */ |
| static tui_layout_split *applied_skeleton; |
| |
| /* The two special "regs" layouts. Note that these aren't registered |
| as commands and so can never be deleted. */ |
| static tui_layout_split *src_regs_layout; |
| static tui_layout_split *asm_regs_layout; |
| |
| /* See tui-data.h. */ |
| std::vector<tui_win_info *> tui_windows; |
| |
| /* See tui-layout.h. */ |
| |
| void |
| tui_apply_current_layout (bool preserve_cmd_win_size_p) |
| { |
| for (tui_win_info *win_info : tui_windows) |
| win_info->make_visible (false); |
| |
| applied_layout->apply (0, 0, tui_term_width (), tui_term_height (), |
| preserve_cmd_win_size_p); |
| |
| /* Keep the list of internal windows up-to-date. */ |
| for (int win_type = SRC_WIN; (win_type < MAX_MAJOR_WINDOWS); win_type++) |
| if (tui_win_list[win_type] != nullptr |
| && !tui_win_list[win_type]->is_visible ()) |
| tui_win_list[win_type] = nullptr; |
| |
| /* This should always be made visible by a layout. */ |
| gdb_assert (tui_cmd_win () != nullptr); |
| gdb_assert (tui_cmd_win ()->is_visible ()); |
| |
| /* Get the new list of currently visible windows. */ |
| std::vector<tui_win_info *> new_tui_windows; |
| applied_layout->get_windows (&new_tui_windows); |
| |
| /* Now delete any window that was not re-applied. */ |
| tui_win_info *focus = tui_win_with_focus (); |
| for (tui_win_info *win_info : tui_windows) |
| { |
| if (!win_info->is_visible ()) |
| { |
| if (focus == win_info) |
| tui_set_win_focus_to (new_tui_windows[0]); |
| delete win_info; |
| } |
| } |
| |
| /* Replace the global list of active windows. */ |
| tui_windows = std::move (new_tui_windows); |
| } |
| |
| /* See tui-layout. */ |
| |
| void |
| tui_adjust_window_height (struct tui_win_info *win, int new_height) |
| { |
| applied_layout->set_height (win->name (), new_height); |
| } |
| |
| /* See tui-layout. */ |
| |
| void |
| tui_adjust_window_width (struct tui_win_info *win, int new_width) |
| { |
| applied_layout->set_width (win->name (), new_width); |
| } |
| |
| /* Set the current layout to LAYOUT. */ |
| |
| static void |
| tui_set_layout (tui_layout_split *layout) |
| { |
| std::string old_fingerprint; |
| if (applied_layout != nullptr) |
| old_fingerprint = applied_layout->layout_fingerprint (); |
| |
| applied_skeleton = layout; |
| applied_layout = layout->clone (); |
| |
| std::string new_fingerprint = applied_layout->layout_fingerprint (); |
| bool preserve_command_window_size |
| = (tui_cmd_win () != nullptr && old_fingerprint == new_fingerprint); |
| |
| tui_apply_current_layout (preserve_command_window_size); |
| } |
| |
| /* See tui-layout.h. */ |
| |
| void |
| tui_add_win_to_layout (enum tui_win_type type) |
| { |
| gdb_assert (type == SRC_WIN || type == DISASSEM_WIN); |
| |
| /* If the window already exists, no need to add it. */ |
| if (tui_win_list[type] != nullptr) |
| return; |
| |
| /* If the window we are trying to replace doesn't exist, we're |
| done. */ |
| enum tui_win_type other = type == SRC_WIN ? DISASSEM_WIN : SRC_WIN; |
| if (tui_win_list[other] == nullptr) |
| return; |
| |
| const char *name = type == SRC_WIN ? SRC_NAME : DISASSEM_NAME; |
| applied_layout->replace_window (tui_win_list[other]->name (), name); |
| tui_apply_current_layout (true); |
| } |
| |
| /* Find LAYOUT in the "layouts" global and return its index. */ |
| |
| static size_t |
| find_layout (tui_layout_split *layout) |
| { |
| for (size_t i = 0; i < layouts.size (); ++i) |
| { |
| if (layout == layouts[i].get ()) |
| return i; |
| } |
| gdb_assert_not_reached ("layout not found!?"); |
| } |
| |
| /* Function to set the layout. */ |
| |
| static void |
| tui_apply_layout (const char *args, int from_tty, cmd_list_element *command) |
| { |
| tui_layout_split *layout = (tui_layout_split *) command->context (); |
| |
| /* Make sure the curses mode is enabled. */ |
| tui_enable (); |
| tui_set_layout (layout); |
| } |
| |
| /* See tui-layout.h. */ |
| |
| void |
| tui_next_layout () |
| { |
| size_t index = find_layout (applied_skeleton); |
| ++index; |
| if (index == layouts.size ()) |
| index = 0; |
| tui_set_layout (layouts[index].get ()); |
| } |
| |
| /* Implement the "layout next" command. */ |
| |
| static void |
| tui_next_layout_command (const char *arg, int from_tty) |
| { |
| tui_enable (); |
| tui_next_layout (); |
| } |
| |
| /* See tui-layout.h. */ |
| |
| void |
| tui_set_initial_layout () |
| { |
| tui_set_layout (layouts[0].get ()); |
| } |
| |
| /* Implement the "layout prev" command. */ |
| |
| static void |
| tui_prev_layout_command (const char *arg, int from_tty) |
| { |
| tui_enable (); |
| size_t index = find_layout (applied_skeleton); |
| if (index == 0) |
| index = layouts.size (); |
| --index; |
| tui_set_layout (layouts[index].get ()); |
| } |
| |
| |
| /* See tui-layout.h. */ |
| |
| void |
| tui_regs_layout () |
| { |
| /* If there's already a register window, we're done. */ |
| if (tui_data_win () != nullptr) |
| return; |
| |
| tui_set_layout (tui_disasm_win () != nullptr |
| ? asm_regs_layout |
| : src_regs_layout); |
| } |
| |
| /* Implement the "layout regs" command. */ |
| |
| static void |
| tui_regs_layout_command (const char *arg, int from_tty) |
| { |
| tui_enable (); |
| tui_regs_layout (); |
| } |
| |
| /* See tui-layout.h. */ |
| |
| void |
| tui_remove_some_windows () |
| { |
| tui_win_info *focus = tui_win_with_focus (); |
| |
| if (strcmp (focus->name (), CMD_NAME) == 0) |
| { |
| /* Try leaving the source or disassembly window. If neither |
| exists, just do nothing. */ |
| focus = tui_src_win (); |
| if (focus == nullptr) |
| focus = tui_disasm_win (); |
| if (focus == nullptr) |
| return; |
| } |
| |
| applied_layout->remove_windows (focus->name ()); |
| tui_apply_current_layout (true); |
| } |
| |
| void |
| tui_win_info::resize (int height_, int width_, |
| int origin_x_, int origin_y_) |
| { |
| if (width == width_ && height == height_ |
| && x == origin_x_ && y == origin_y_ |
| && handle != nullptr) |
| return; |
| |
| width = width_; |
| height = height_; |
| x = origin_x_; |
| y = origin_y_; |
| |
| if (handle != nullptr) |
| { |
| #ifdef HAVE_WRESIZE |
| wresize (handle.get (), height, width); |
| mvwin (handle.get (), y, x); |
| wmove (handle.get (), 0, 0); |
| #else |
| handle.reset (nullptr); |
| #endif |
| } |
| |
| if (handle == nullptr) |
| make_window (); |
| |
| rerender (); |
| } |
| |
| |
| |
| /* Helper function to create one of the built-in (non-status) |
| windows. */ |
| |
| template<enum tui_win_type V, class T> |
| static tui_win_info * |
| make_standard_window (const char *) |
| { |
| if (tui_win_list[V] == nullptr) |
| tui_win_list[V] = new T (); |
| return tui_win_list[V]; |
| } |
| |
| /* A map holding all the known window types, keyed by name. */ |
| |
| static window_types_map known_window_types; |
| |
| /* See tui-layout.h. */ |
| |
| known_window_names_range |
| all_known_window_names () |
| { |
| auto begin = known_window_names_iterator (known_window_types.begin ()); |
| auto end = known_window_names_iterator (known_window_types.end ()); |
| return known_window_names_range (begin, end); |
| } |
| |
| /* Helper function that returns a TUI window, given its name. */ |
| |
| static tui_win_info * |
| tui_get_window_by_name (const std::string &name) |
| { |
| for (tui_win_info *window : tui_windows) |
| if (name == window->name ()) |
| return window; |
| |
| auto iter = known_window_types.find (name); |
| if (iter == known_window_types.end ()) |
| error (_("Unknown window type \"%s\""), name.c_str ()); |
| |
| tui_win_info *result = iter->second (name.c_str ()); |
| if (result == nullptr) |
| error (_("Could not create window \"%s\""), name.c_str ()); |
| return result; |
| } |
| |
| /* Initialize the known window types. */ |
| |
| static void |
| initialize_known_windows () |
| { |
| known_window_types.emplace (SRC_NAME, |
| make_standard_window<SRC_WIN, |
| tui_source_window>); |
| known_window_types.emplace (CMD_NAME, |
| make_standard_window<CMD_WIN, tui_cmd_window>); |
| known_window_types.emplace (DATA_NAME, |
| make_standard_window<DATA_WIN, |
| tui_data_window>); |
| known_window_types.emplace (DISASSEM_NAME, |
| make_standard_window<DISASSEM_WIN, |
| tui_disasm_window>); |
| known_window_types.emplace (STATUS_NAME, |
| make_standard_window<STATUS_WIN, |
| tui_status_window>); |
| } |
| |
| /* See tui-layout.h. */ |
| |
| void |
| tui_register_window (const char *name, window_factory &&factory) |
| { |
| std::string name_copy = name; |
| |
| if (name_copy == SRC_NAME || name_copy == CMD_NAME || name_copy == DATA_NAME |
| || name_copy == DISASSEM_NAME || name_copy == STATUS_NAME) |
| error (_("Window type \"%s\" is built-in"), name); |
| |
| for (const char &c : name_copy) |
| { |
| if (ISSPACE (c)) |
| error (_("invalid whitespace character in window name")); |
| |
| if (!ISALNUM (c) && strchr ("-_.", c) == nullptr) |
| error (_("invalid character '%c' in window name"), c); |
| } |
| |
| if (!ISALPHA (name_copy[0])) |
| error (_("window name must start with a letter, not '%c'"), name_copy[0]); |
| |
| /* We already check above for all the builtin window names. If we get |
| this far then NAME must be a user defined window. Remove any existing |
| factory and replace it with this new version. */ |
| |
| auto iter = known_window_types.find (name); |
| if (iter != known_window_types.end ()) |
| known_window_types.erase (iter); |
| |
| known_window_types.emplace (std::move (name_copy), |
| std::move (factory)); |
| } |
| |
| /* See tui-layout.h. */ |
| |
| std::unique_ptr<tui_layout_base> |
| tui_layout_window::clone () const |
| { |
| tui_layout_window *result = new tui_layout_window (m_contents.c_str ()); |
| return std::unique_ptr<tui_layout_base> (result); |
| } |
| |
| /* See tui-layout.h. */ |
| |
| void |
| tui_layout_window::apply (int x_, int y_, int width_, int height_, |
| bool preserve_cmd_win_size_p) |
| { |
| x = x_; |
| y = y_; |
| width = width_; |
| height = height_; |
| gdb_assert (m_window != nullptr); |
| if (width == 0 || height == 0) |
| { |
| /* The window was dropped, so it's going to be deleted, reset the |
| soon to be dangling pointer. */ |
| m_window = nullptr; |
| return; |
| } |
| m_window->resize (height, width, x, y); |
| } |
| |
| /* See tui-layout.h. */ |
| |
| void |
| tui_layout_window::get_sizes (bool height, int *min_value, int *max_value) |
| { |
| TUI_SCOPED_DEBUG_ENTER_EXIT; |
| |
| if (m_window == nullptr) |
| m_window = tui_get_window_by_name (m_contents); |
| |
| tui_debug_printf ("window = %s, getting %s", |
| m_window->name (), (height ? "height" : "width")); |
| |
| if (height) |
| { |
| *min_value = m_window->min_height (); |
| *max_value = m_window->max_height (); |
| } |
| else |
| { |
| *min_value = m_window->min_width (); |
| *max_value = m_window->max_width (); |
| } |
| |
| tui_debug_printf ("min = %d, max = %d", *min_value, *max_value); |
| } |
| |
| /* See tui-layout.h. */ |
| |
| bool |
| tui_layout_window::first_edge_has_border_p () const |
| { |
| gdb_assert (m_window != nullptr); |
| return m_window->can_box (); |
| } |
| |
| /* See tui-layout.h. */ |
| |
| bool |
| tui_layout_window::last_edge_has_border_p () const |
| { |
| gdb_assert (m_window != nullptr); |
| return m_window->can_box (); |
| } |
| |
| /* See tui-layout.h. */ |
| |
| void |
| tui_layout_window::replace_window (const char *name, const char *new_window) |
| { |
| if (m_contents == name) |
| { |
| m_contents = new_window; |
| if (m_window != nullptr) |
| { |
| m_window->make_visible (false); |
| m_window = tui_get_window_by_name (m_contents); |
| } |
| } |
| } |
| |
| /* See tui-layout.h. */ |
| |
| void |
| tui_layout_window::specification (ui_file *output, int depth) |
| { |
| gdb_puts (get_name (), output); |
| } |
| |
| /* See tui-layout.h. */ |
| |
| std::string |
| tui_layout_window::layout_fingerprint () const |
| { |
| if (strcmp (get_name (), "cmd") == 0) |
| return "C"; |
| else |
| return ""; |
| } |
| |
| /* See tui-layout.h. */ |
| |
| void |
| tui_layout_split::add_split (std::unique_ptr<tui_layout_split> &&layout, |
| int weight) |
| { |
| split s = {weight, std::move (layout)}; |
| m_splits.push_back (std::move (s)); |
| } |
| |
| /* See tui-layout.h. */ |
| |
| void |
| tui_layout_split::add_window (const char *name, int weight) |
| { |
| tui_layout_window *result = new tui_layout_window (name); |
| split s = {weight, std::unique_ptr<tui_layout_base> (result)}; |
| m_splits.push_back (std::move (s)); |
| } |
| |
| /* See tui-layout.h. */ |
| |
| std::unique_ptr<tui_layout_base> |
| tui_layout_split::clone () const |
| { |
| tui_layout_split *result = new tui_layout_split (m_vertical); |
| for (const split &item : m_splits) |
| { |
| std::unique_ptr<tui_layout_base> next = item.layout->clone (); |
| split s = {item.weight, std::move (next)}; |
| result->m_splits.push_back (std::move (s)); |
| } |
| return std::unique_ptr<tui_layout_base> (result); |
| } |
| |
| /* See tui-layout.h. */ |
| |
| void |
| tui_layout_split::get_sizes (bool height, int *min_value, int *max_value) |
| { |
| TUI_SCOPED_DEBUG_ENTER_EXIT; |
| |
| *min_value = 0; |
| *max_value = 0; |
| bool first_time = true; |
| for (const split &item : m_splits) |
| { |
| int new_min, new_max; |
| item.layout->get_sizes (height, &new_min, &new_max); |
| /* For the mismatch case, the first time through we want to set |
| the min and max to the computed values -- the "first_time" |
| check here is just a funny way of doing that. */ |
| if (height == m_vertical || first_time) |
| { |
| *min_value += new_min; |
| *max_value += new_max; |
| } |
| else |
| { |
| *min_value = std::max (*min_value, new_min); |
| *max_value = std::min (*max_value, new_max); |
| } |
| first_time = false; |
| } |
| |
| tui_debug_printf ("min_value = %d, max_value = %d", *min_value, *max_value); |
| } |
| |
| /* See tui-layout.h. */ |
| |
| bool |
| tui_layout_split::first_edge_has_border_p () const |
| { |
| if (m_splits.empty ()) |
| return false; |
| return m_splits[0].layout->first_edge_has_border_p (); |
| } |
| |
| /* See tui-layout.h. */ |
| |
| bool |
| tui_layout_split::last_edge_has_border_p () const |
| { |
| if (m_splits.empty ()) |
| return false; |
| return m_splits.back ().layout->last_edge_has_border_p (); |
| } |
| |
| /* See tui-layout.h. */ |
| |
| void |
| tui_layout_split::set_weights_from_sizes () |
| { |
| for (int i = 0; i < m_splits.size (); ++i) |
| m_splits[i].weight |
| = m_vertical ? m_splits[i].layout->height : m_splits[i].layout->width; |
| } |
| |
| /* See tui-layout.h. */ |
| |
| std::string |
| tui_layout_split::tui_debug_weights_to_string () const |
| { |
| std::string str; |
| |
| for (int i = 0; i < m_splits.size (); ++i) |
| { |
| if (i > 0) |
| str += ", "; |
| str += string_printf ("[%d] %d", i, m_splits[i].weight); |
| } |
| |
| return str; |
| } |
| |
| /* See tui-layout.h. */ |
| |
| void |
| tui_layout_split::tui_debug_print_size_info |
| (const std::vector<tui_layout_split::size_info> &info) |
| { |
| gdb_assert (debug_tui); |
| |
| tui_debug_printf ("current size info data:"); |
| for (int i = 0; i < info.size (); ++i) |
| tui_debug_printf (" [%d] { size = %d, min = %d, max = %d, share_box = %d }", |
| i, info[i].size, info[i].min_size, |
| info[i].max_size, info[i].share_box); |
| } |
| |
| /* See tui-layout.h. */ |
| |
| tui_adjust_result |
| tui_layout_split::set_size (const char *name, int new_size, bool set_width_p) |
| { |
| TUI_SCOPED_DEBUG_ENTER_EXIT; |
| |
| tui_debug_printf ("this = %p, name = %s, new_size = %d", |
| this, name, new_size); |
| |
| /* Look through the children. If one is a layout holding the named |
| window, we're done; or if one actually is the named window, |
| update it. */ |
| int found_index = -1; |
| for (int i = 0; i < m_splits.size (); ++i) |
| { |
| tui_adjust_result adjusted; |
| if (set_width_p) |
| adjusted = m_splits[i].layout->set_width (name, new_size); |
| else |
| adjusted = m_splits[i].layout->set_height (name, new_size); |
| if (adjusted == HANDLED) |
| return HANDLED; |
| if (adjusted == FOUND) |
| { |
| if (set_width_p ? m_vertical : !m_vertical) |
| return FOUND; |
| found_index = i; |
| break; |
| } |
| } |
| |
| if (found_index == -1) |
| return NOT_FOUND; |
| int curr_size = (set_width_p |
| ? m_splits[found_index].layout->width |
| : m_splits[found_index].layout->height); |
| if (curr_size == new_size) |
| return HANDLED; |
| |
| tui_debug_printf ("found window %s at index %d", name, found_index); |
| |
| set_weights_from_sizes (); |
| int delta = m_splits[found_index].weight - new_size; |
| m_splits[found_index].weight = new_size; |
| |
| tui_debug_printf ("before delta (%d) distribution, weights: %s", |
| delta, tui_debug_weights_to_string ().c_str ()); |
| |
| /* Distribute the "delta" over all other windows, while respecting their |
| min/max sizes. We grow each window by 1 line at a time continually |
| looping over all the windows. However, skip the window that the user |
| just resized, obviously we don't want to readjust that window. */ |
| bool found_window_that_can_grow_p = true; |
| for (int i = 0; delta != 0; i = (i + 1) % m_splits.size ()) |
| { |
| int index = (found_index + 1 + i) % m_splits.size (); |
| if (index == found_index) |
| { |
| if (!found_window_that_can_grow_p) |
| break; |
| found_window_that_can_grow_p = false; |
| continue; |
| } |
| |
| int new_min, new_max; |
| m_splits[index].layout->get_sizes (m_vertical, &new_min, &new_max); |
| |
| if (delta < 0) |
| { |
| /* The primary window grew, so we are trying to shrink other |
| windows. */ |
| if (m_splits[index].weight > new_min) |
| { |
| m_splits[index].weight -= 1; |
| delta += 1; |
| found_window_that_can_grow_p = true; |
| } |
| } |
| else |
| { |
| /* The primary window shrank, so we are trying to grow other |
| windows. */ |
| if (m_splits[index].weight < new_max) |
| { |
| m_splits[index].weight += 1; |
| delta -= 1; |
| found_window_that_can_grow_p = true; |
| } |
| } |
| |
| tui_debug_printf ("index = %d, weight now: %d", |
| index, m_splits[index].weight); |
| } |
| |
| tui_debug_printf ("after delta (%d) distribution, weights: %s", |
| delta, tui_debug_weights_to_string ().c_str ()); |
| |
| if (delta != 0) |
| { |
| if (set_width_p) |
| warning (_("Invalid window width specified")); |
| else |
| warning (_("Invalid window height specified")); |
| /* Effectively undo any modifications made here. */ |
| set_weights_from_sizes (); |
| } |
| else |
| { |
| /* Simply re-apply the updated layout. We pass false here so that |
| the cmd window can be resized. However, we should have already |
| resized everything above to be "just right", so the apply call |
| here should not end up changing the sizes at all. */ |
| apply (x, y, width, height, false); |
| } |
| |
| return HANDLED; |
| } |
| |
| /* See tui-layout.h. */ |
| |
| void |
| tui_layout_split::apply (int x_, int y_, int width_, int height_, |
| bool preserve_cmd_win_size_p) |
| { |
| TUI_SCOPED_DEBUG_ENTER_EXIT; |
| |
| x = x_; |
| y = y_; |
| width = width_; |
| height = height_; |
| |
| /* In some situations we fix the size of the cmd window. However, |
| occasionally this turns out to be a mistake. This struct is used to |
| hold the original information about the cmd window, so we can restore |
| it if needed. */ |
| struct old_size_info |
| { |
| /* Constructor. */ |
| old_size_info (int index_, int min_size_, int max_size_) |
| : index (index_), |
| min_size (min_size_), |
| max_size (max_size_) |
| { /* Nothing. */ } |
| |
| /* The index in m_splits where the cmd window was found. */ |
| int index; |
| |
| /* The previous min/max size. */ |
| int min_size; |
| int max_size; |
| }; |
| |
| /* This is given a value only if we fix the size of the cmd window. */ |
| std::optional<old_size_info> old_cmd_info; |
| |
| std::vector<size_info> info (m_splits.size ()); |
| |
| tui_debug_printf ("weights are: %s", |
| tui_debug_weights_to_string ().c_str ()); |
| |
| /* Step 1: Find the min and max size of each sub-layout. |
| Fixed-sized layouts are given their desired size, and then the |
| remaining space is distributed among the remaining windows |
| according to the weights given. */ |
| int available_size = m_vertical ? height : width; |
| int last_index = -1; |
| int total_weight = 0; |
| int prev = -1; |
| for (int i = 0; i < m_splits.size (); ++i) |
| { |
| bool cmd_win_already_exists = tui_cmd_win () != nullptr; |
| |
| /* Always call get_sizes, to ensure that the window is |
| instantiated. This is a bit gross but less gross than adding |
| special cases for this in other places. */ |
| m_splits[i].layout->get_sizes (m_vertical, &info[i].min_size, |
| &info[i].max_size); |
| |
| if (preserve_cmd_win_size_p |
| && cmd_win_already_exists |
| && m_splits[i].layout->get_name () != nullptr |
| && strcmp (m_splits[i].layout->get_name (), "cmd") == 0) |
| { |
| /* Save the old cmd window information, in case we need to |
| restore it later. */ |
| old_cmd_info.emplace (i, info[i].min_size, info[i].max_size); |
| |
| /* If this layout has never been applied, then it means the |
| user just changed the layout. In this situation, it's |
| desirable to keep the size of the command window the |
| same. Setting the min and max sizes this way ensures |
| that the resizing step, below, does the right thing with |
| this window. */ |
| info[i].min_size = (m_vertical |
| ? tui_cmd_win ()->height |
| : tui_cmd_win ()->width); |
| info[i].max_size = info[i].min_size; |
| } |
| |
| if (info[i].min_size > info[i].max_size) |
| { |
| /* There is not enough room for this window, drop it. */ |
| info[i].min_size = 0; |
| info[i].max_size = 0; |
| continue; |
| } |
| |
| /* Two adjacent boxed windows will share a border. */ |
| if (prev != -1 |
| && m_splits[prev].layout->last_edge_has_border_p () |
| && m_splits[i].layout->first_edge_has_border_p ()) |
| info[i].share_box = true; |
| |
| if (info[i].min_size == info[i].max_size) |
| { |
| available_size -= info[i].min_size; |
| if (info[i].share_box) |
| { |
| /* A shared border makes a bit more size available. */ |
| ++available_size; |
| } |
| } |
| else |
| { |
| last_index = i; |
| total_weight += m_splits[i].weight; |
| } |
| |
| prev = i; |
| } |
| |
| /* If last_index is set then we have a window that is not of a fixed |
| size. This window will have its size calculated below, which requires |
| that the total_weight not be zero (we divide by total_weight, so don't |
| want a floating-point exception). */ |
| gdb_assert (last_index == -1 || total_weight > 0); |
| |
| /* Step 2: Compute the size of each sub-layout. Fixed-sized items |
| are given their fixed size, while others are resized according to |
| their weight. */ |
| int used_size = 0; |
| for (int i = 0; i < m_splits.size (); ++i) |
| { |
| if (info[i].min_size != info[i].max_size) |
| { |
| /* Compute the height and clamp to the allowable range. */ |
| info[i].size = available_size * m_splits[i].weight / total_weight; |
| if (info[i].size > info[i].max_size) |
| info[i].size = info[i].max_size; |
| if (info[i].size < info[i].min_size) |
| info[i].size = info[i].min_size; |
| /* Keep a total of all the size we've used so far (we gain some |
| size back if this window can share a border with a preceding |
| window). Any unused space will be distributed between all of |
| the other windows (while respecting min/max sizes) later in |
| this function. */ |
| used_size += info[i].size; |
| if (info[i].share_box) |
| { |
| /* A shared border makes a bit more size available. */ |
| --used_size; |
| } |
| } |
| else |
| info[i].size = info[i].min_size; |
| } |
| |
| if (debug_tui) |
| { |
| tui_debug_printf ("after initial size calculation"); |
| tui_debug_printf ("available_size = %d, used_size = %d", |
| available_size, used_size); |
| tui_debug_printf ("total_weight = %d, last_index = %d", |
| total_weight, last_index); |
| tui_debug_print_size_info (info); |
| } |
| |
| /* If we didn't find any sub-layouts that were of a non-fixed size, but |
| we did find the cmd window, then we can consider that a sort-of |
| non-fixed size sub-layout. |
| |
| The cmd window might, initially, be of a fixed size (see above), but, |
| we are willing to relax this constraint if required to correctly apply |
| this layout (see below). */ |
| if (last_index == -1 && old_cmd_info.has_value ()) |
| last_index = old_cmd_info->index; |
| |
| /* Allocate any leftover size. */ |
| if (available_size != used_size && last_index != -1) |
| { |
| /* Loop over all windows until the amount of used space is equal to |
| the amount of available space. There's an escape hatch within |
| the loop in case we can't find any sub-layouts to resize. */ |
| bool found_window_that_can_grow_p = true; |
| for (int idx = last_index; |
| available_size != used_size; |
| idx = (idx + 1) % m_splits.size ()) |
| { |
| /* Every time we get back to last_index, which is where the loop |
| started, we check to make sure that we did assign some space |
| to a window, bringing used_size closer to available_size. |
| |
| If we didn't, but the cmd window is of a fixed size, then we |
| can make the console window non-fixed-size, and continue |
| around the loop, hopefully, this will allow the layout to be |
| applied correctly. |
| |
| If we still make it around the loop without moving used_size |
| closer to available_size, then there's nothing more we can do, |
| and we break out of the loop. */ |
| if (idx == last_index) |
| { |
| /* If the used_size is greater than the available_size then |
| this indicates that the fixed-sized sub-layouts claimed |
| more space than is available. This layout is not going to |
| work. Our only hope at this point is to make the cmd |
| window non-fixed-size (if possible), and hope we can |
| shrink this enough to fit the rest of the sub-layouts in. |
| |
| Alternatively, we've made it around the loop without |
| adjusting any window's size. This likely means all |
| windows have hit their min or max size. Again, our only |
| hope is to make the cmd window non-fixed-size, and hope |
| this fixes all our problems. */ |
| if (old_cmd_info.has_value () |
| && ((available_size < used_size) |
| || !found_window_that_can_grow_p)) |
| { |
| info[old_cmd_info->index].min_size = old_cmd_info->min_size; |
| info[old_cmd_info->index].max_size = old_cmd_info->max_size; |
| tui_debug_printf |
| ("restoring index %d (cmd) size limits, min = %d, max = %d", |
| old_cmd_info->index, old_cmd_info->min_size, |
| old_cmd_info->max_size); |
| old_cmd_info.reset (); |
| } |
| else if (!found_window_that_can_grow_p) |
| break; |
| found_window_that_can_grow_p = false; |
| } |
| |
| if (available_size > used_size |
| && info[idx].size < info[idx].max_size) |
| { |
| found_window_that_can_grow_p = true; |
| info[idx].size += 1; |
| used_size += 1; |
| } |
| else if (available_size < used_size |
| && info[idx].size > info[idx].min_size) |
| { |
| found_window_that_can_grow_p = true; |
| info[idx].size -= 1; |
| used_size -= 1; |
| } |
| } |
| |
| if (debug_tui) |
| { |
| tui_debug_printf ("after final size calculation"); |
| tui_debug_printf ("available_size = %d, used_size = %d", |
| available_size, used_size); |
| tui_debug_printf ("total_weight = %d, last_index = %d", |
| total_weight, last_index); |
| tui_debug_print_size_info (info); |
| } |
| } |
| |
| /* Step 3: Resize. */ |
| int size_accum = 0; |
| const int maximum = m_vertical ? height : width; |
| for (int i = 0; i < m_splits.size (); ++i) |
| { |
| /* If we fall off the bottom, just make allocations overlap. |
| GIGO. */ |
| if (size_accum + info[i].size > maximum) |
| size_accum = maximum - info[i].size; |
| else if (info[i].share_box) |
| --size_accum; |
| if (m_vertical) |
| m_splits[i].layout->apply (x, y + size_accum, width, info[i].size, |
| preserve_cmd_win_size_p); |
| else |
| m_splits[i].layout->apply (x + size_accum, y, info[i].size, height, |
| preserve_cmd_win_size_p); |
| size_accum += info[i].size; |
| } |
| } |
| |
| /* See tui-layout.h. */ |
| |
| void |
| tui_layout_split::remove_windows (const char *name) |
| { |
| for (int i = 0; i < m_splits.size (); ++i) |
| { |
| const char *this_name = m_splits[i].layout->get_name (); |
| if (this_name == nullptr) |
| m_splits[i].layout->remove_windows (name); |
| else if (strcmp (this_name, name) == 0 |
| || strcmp (this_name, CMD_NAME) == 0 |
| || strcmp (this_name, STATUS_NAME) == 0) |
| { |
| /* Keep. */ |
| } |
| else |
| { |
| m_splits.erase (m_splits.begin () + i); |
| --i; |
| } |
| } |
| } |
| |
| /* See tui-layout.h. */ |
| |
| void |
| tui_layout_split::replace_window (const char *name, const char *new_window) |
| { |
| for (auto &item : m_splits) |
| item.layout->replace_window (name, new_window); |
| } |
| |
| /* See tui-layout.h. */ |
| |
| void |
| tui_layout_split::specification (ui_file *output, int depth) |
| { |
| if (depth > 0) |
| gdb_puts ("{", output); |
| |
| if (!m_vertical) |
| gdb_puts ("-horizontal ", output); |
| |
| bool first = true; |
| for (auto &item : m_splits) |
| { |
| if (!first) |
| gdb_puts (" ", output); |
| first = false; |
| item.layout->specification (output, depth + 1); |
| gdb_printf (output, " %d", item.weight); |
| } |
| |
| if (depth > 0) |
| gdb_puts ("}", output); |
| } |
| |
| /* See tui-layout.h. */ |
| |
| std::string |
| tui_layout_split::layout_fingerprint () const |
| { |
| for (auto &item : m_splits) |
| { |
| std::string fp = item.layout->layout_fingerprint (); |
| if (!fp.empty () && m_splits.size () != 1) |
| return std::string (m_vertical ? "V" : "H") + fp; |
| } |
| |
| return ""; |
| } |
| |
| /* Destroy the layout associated with SELF. */ |
| |
| static void |
| destroy_layout (struct cmd_list_element *self, void *context) |
| { |
| tui_layout_split *layout = (tui_layout_split *) context; |
| size_t index = find_layout (layout); |
| layouts.erase (layouts.begin () + index); |
| } |
| |
| /* List holding the sub-commands of "layout". */ |
| |
| static struct cmd_list_element *layout_list; |
| |
| /* Called to implement 'tui layout'. */ |
| |
| static void |
| tui_layout_command (const char *args, int from_tty) |
| { |
| help_list (layout_list, "tui layout ", all_commands, gdb_stdout); |
| } |
| |
| /* Add a "layout" command with name NAME that switches to LAYOUT. */ |
| |
| static struct cmd_list_element * |
| add_layout_command (const char *name, tui_layout_split *layout) |
| { |
| struct cmd_list_element *cmd; |
| |
| string_file spec; |
| layout->specification (&spec, 0); |
| |
| gdb::unique_xmalloc_ptr<char> doc |
| = xstrprintf (_("Apply the \"%s\" layout.\n\ |
| This layout was created using:\n\ |
| tui new-layout %s %s"), |
| name, name, spec.c_str ()); |
| |
| cmd = add_cmd (name, class_tui, nullptr, doc.get (), &layout_list); |
| cmd->set_context (layout); |
| /* There is no API to set this. */ |
| cmd->func = tui_apply_layout; |
| cmd->destroyer = destroy_layout; |
| cmd->doc_allocated = 1; |
| doc.release (); |
| layouts.emplace_back (layout); |
| |
| return cmd; |
| } |
| |
| /* Initialize the standard layouts. */ |
| |
| static void |
| initialize_layouts () |
| { |
| tui_layout_split *layout; |
| |
| layout = new tui_layout_split (); |
| layout->add_window (SRC_NAME, 2); |
| layout->add_window (STATUS_NAME, 0); |
| layout->add_window (CMD_NAME, 1); |
| add_layout_command (SRC_NAME, layout); |
| |
| layout = new tui_layout_split (); |
| layout->add_window (DISASSEM_NAME, 2); |
| layout->add_window (STATUS_NAME, 0); |
| layout->add_window (CMD_NAME, 1); |
| add_layout_command (DISASSEM_NAME, layout); |
| |
| layout = new tui_layout_split (); |
| layout->add_window (SRC_NAME, 1); |
| layout->add_window (DISASSEM_NAME, 1); |
| layout->add_window (STATUS_NAME, 0); |
| layout->add_window (CMD_NAME, 1); |
| add_layout_command ("split", layout); |
| |
| layout = new tui_layout_split (); |
| layout->add_window (DATA_NAME, 1); |
| layout->add_window (SRC_NAME, 1); |
| layout->add_window (STATUS_NAME, 0); |
| layout->add_window (CMD_NAME, 1); |
| layouts.emplace_back (layout); |
| src_regs_layout = layout; |
| |
| layout = new tui_layout_split (); |
| layout->add_window (DATA_NAME, 1); |
| layout->add_window (DISASSEM_NAME, 1); |
| layout->add_window (STATUS_NAME, 0); |
| layout->add_window (CMD_NAME, 1); |
| layouts.emplace_back (layout); |
| asm_regs_layout = layout; |
| } |
| |
| |
| |
| /* A helper function that returns true if NAME is the name of an |
| available window. */ |
| |
| static bool |
| validate_window_name (const std::string &name) |
| { |
| auto iter = known_window_types.find (name); |
| return iter != known_window_types.end (); |
| } |
| |
| /* Implementation of the "tui new-layout" command. */ |
| |
| static void |
| tui_new_layout_command (const char *spec, int from_tty) |
| { |
| std::string new_name = extract_arg (&spec); |
| if (new_name.empty ()) |
| error (_("No layout name specified")); |
| if (new_name[0] == '-') |
| error (_("Layout name cannot start with '-'")); |
| |
| bool is_vertical = true; |
| spec = skip_spaces (spec); |
| if (check_for_argument (&spec, "-horizontal")) |
| is_vertical = false; |
| |
| std::vector<std::unique_ptr<tui_layout_split>> splits; |
| splits.emplace_back (new tui_layout_split (is_vertical)); |
| std::unordered_set<std::string> seen_windows; |
| while (true) |
| { |
| spec = skip_spaces (spec); |
| if (spec[0] == '\0') |
| break; |
| |
| if (spec[0] == '{') |
| { |
| is_vertical = true; |
| spec = skip_spaces (spec + 1); |
| if (check_for_argument (&spec, "-horizontal")) |
| is_vertical = false; |
| splits.emplace_back (new tui_layout_split (is_vertical)); |
| continue; |
| } |
| |
| bool is_close = false; |
| std::string name; |
| if (spec[0] == '}') |
| { |
| is_close = true; |
| ++spec; |
| if (splits.size () == 1) |
| error (_("Extra '}' in layout specification")); |
| } |
| else |
| { |
| name = extract_arg (&spec); |
| if (name.empty ()) |
| break; |
| if (!validate_window_name (name)) |
| error (_("Unknown window \"%s\""), name.c_str ()); |
| if (seen_windows.find (name) != seen_windows.end ()) |
| error (_("Window \"%s\" seen twice in layout"), name.c_str ()); |
| } |
| |
| ULONGEST weight = get_ulongest (&spec, '}'); |
| if ((int) weight != weight) |
| error (_("Weight out of range: %s"), pulongest (weight)); |
| if (is_close) |
| { |
| std::unique_ptr<tui_layout_split> last_split |
| = std::move (splits.back ()); |
| splits.pop_back (); |
| splits.back ()->add_split (std::move (last_split), weight); |
| } |
| else |
| { |
| splits.back ()->add_window (name.c_str (), weight); |
| seen_windows.insert (name); |
| } |
| } |
| if (splits.size () > 1) |
| error (_("Missing '}' in layout specification")); |
| if (seen_windows.empty ()) |
| error (_("New layout does not contain any windows")); |
| if (seen_windows.find (CMD_NAME) == seen_windows.end ()) |
| error (_("New layout does not contain the \"" CMD_NAME "\" window")); |
| |
| gdb::unique_xmalloc_ptr<char> cmd_name |
| = make_unique_xstrdup (new_name.c_str ()); |
| std::unique_ptr<tui_layout_split> new_layout = std::move (splits.back ()); |
| struct cmd_list_element *cmd |
| = add_layout_command (cmd_name.get (), new_layout.get ()); |
| cmd->name_allocated = 1; |
| cmd_name.release (); |
| new_layout.release (); |
| } |
| |
| /* Function to initialize gdb commands, for tui window layout |
| manipulation. */ |
| |
| void _initialize_tui_layout (); |
| void |
| _initialize_tui_layout () |
| { |
| struct cmd_list_element *layout_cmd |
| = add_prefix_cmd ("layout", class_tui, tui_layout_command, _("\ |
| Change the layout of windows.\n\ |
| Usage: tui layout prev | next | LAYOUT-NAME"), |
| &layout_list, 0, tui_get_cmd_list ()); |
| add_com_alias ("layout", layout_cmd, class_tui, 0); |
| |
| add_cmd ("next", class_tui, tui_next_layout_command, |
| _("Apply the next TUI layout."), |
| &layout_list); |
| add_cmd ("prev", class_tui, tui_prev_layout_command, |
| _("Apply the previous TUI layout."), |
| &layout_list); |
| add_cmd ("regs", class_tui, tui_regs_layout_command, |
| _("Apply the TUI register layout."), |
| &layout_list); |
| |
| add_cmd ("new-layout", class_tui, tui_new_layout_command, |
| _("Create a new TUI layout.\n\ |
| Usage: tui new-layout [-horizontal] NAME WINDOW WEIGHT [WINDOW WEIGHT]...\n\ |
| Create a new TUI layout. The new layout will be named NAME,\n\ |
| and can be accessed using \"layout NAME\".\n\ |
| The windows will be displayed in the specified order.\n\ |
| A WINDOW can also be of the form:\n\ |
| { [-horizontal] NAME WEIGHT [NAME WEIGHT]... }\n\ |
| This form indicates a sub-frame.\n\ |
| Each WEIGHT is an integer, which holds the relative size\n\ |
| to be allocated to the window."), |
| tui_get_cmd_list ()); |
| |
| initialize_layouts (); |
| initialize_known_windows (); |
| } |