#include "cli/cli-decode.h"
#include "cli/cli-utils.h"
#include <ctype.h>
+#include <unordered_map>
#include <unordered_set>
#include "tui/tui.h"
#include "tui/tui-source.h"
#include "gdb_curses.h"
-static void tui_layout_command (const char *, int);
static void extract_display_start_addr (struct gdbarch **, CORE_ADDR *);
/* The layouts. */
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;
+
+/* When applying a layout, this is the list of all windows that were
+ in the previous layout. This is used to re-use windows when
+ changing a layout. */
+static std::vector<tui_win_info *> saved_tui_windows;
+
/* See tui-layout.h. */
void
tui_apply_current_layout ()
{
+ struct gdbarch *gdbarch;
+ CORE_ADDR addr;
+
+ extract_display_start_addr (&gdbarch, &addr);
+
+ saved_tui_windows = std::move (tui_windows);
+ tui_windows.clear ();
+
+ for (tui_win_info *win_info : saved_tui_windows)
+ win_info->make_visible (false);
+
applied_layout->apply (0, 0, tui_term_width (), tui_term_height ());
+
+ /* 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->is_visible ());
+
+ /* Now delete any window that was not re-applied. */
+ tui_win_info *focus = tui_win_with_focus ();
+ for (tui_win_info *win_info : saved_tui_windows)
+ {
+ if (!win_info->is_visible ())
+ {
+ if (focus == win_info)
+ tui_set_win_focus_to (tui_windows[0]);
+ delete win_info;
+ }
+ }
+
+ if (gdbarch == nullptr && TUI_DISASM_WIN != nullptr)
+ tui_get_begin_asm_address (&gdbarch, &addr);
+ tui_update_source_windows_with_addr (gdbarch, addr);
+
+ saved_tui_windows.clear ();
}
/* See tui-layout. */
static void
tui_set_layout (tui_layout_split *layout)
{
- struct gdbarch *gdbarch;
- CORE_ADDR addr;
-
- extract_display_start_addr (&gdbarch, &addr);
- tui_make_all_invisible ();
applied_skeleton = layout;
applied_layout = layout->clone ();
tui_apply_current_layout ();
- tui_delete_invisible_windows ();
-
- if (gdbarch == nullptr && TUI_DISASM_WIN != nullptr)
- tui_get_begin_asm_address (&gdbarch, &addr);
- tui_update_source_windows_with_addr (gdbarch, addr);
}
/* See tui-layout.h. */
const char *name = type == SRC_WIN ? SRC_NAME : DISASSEM_NAME;
applied_layout->replace_window (tui_win_list[other]->name (), name);
tui_apply_current_layout ();
- tui_delete_invisible_windows ();
}
/* Find LAYOUT in the "layouts" global and return its index. */
static void
extract_display_start_addr (struct gdbarch **gdbarch_p, CORE_ADDR *addr_p)
{
- struct gdbarch *gdbarch = nullptr;
- CORE_ADDR addr = 0;
- CORE_ADDR pc;
- struct symtab_and_line cursal = get_current_source_symtab_and_line ();
-
if (TUI_SRC_WIN != nullptr)
- {
- gdbarch = TUI_SRC_WIN->gdbarch;
- find_line_pc (cursal.symtab,
- TUI_SRC_WIN->start_line_or_addr.u.line_no,
- &pc);
- addr = pc;
- }
+ TUI_SRC_WIN->display_start_addr (gdbarch_p, addr_p);
else if (TUI_DISASM_WIN != nullptr)
+ TUI_DISASM_WIN->display_start_addr (gdbarch_p, addr_p);
+ else
{
- gdbarch = TUI_DISASM_WIN->gdbarch;
- addr = TUI_DISASM_WIN->start_line_or_addr.u.addr;
+ *gdbarch_p = nullptr;
+ *addr_p = 0;
}
-
- *gdbarch_p = gdbarch;
- *addr_p = addr;
}
void
\f
+/* Helper function to create one of the built-in (non-locator)
+ windows. */
+
+template<enum tui_win_type V, class T>
+static tui_gen_win_info *
+make_standard_window (const char *)
+{
+ if (tui_win_list[V] == nullptr)
+ tui_win_list[V] = new T ();
+ return tui_win_list[V];
+}
+
+/* Helper function to wrap tui_locator_win_info_ptr for
+ tui_get_window_by_name. */
+
+static tui_gen_win_info *
+get_locator_window (const char *)
+{
+ return tui_locator_win_info_ptr ();
+}
+
+/* A map holding all the known window types, keyed by name. Note that
+ this is heap-allocated and "leaked" at gdb exit. This avoids
+ ordering issues with destroying elements in the map at shutdown.
+ In particular, destroying this map can occur after Python has been
+ shut down, causing crashes if any window destruction requires
+ running Python code. */
+
+static std::unordered_map<std::string, window_factory> *known_window_types;
+
/* Helper function that returns a TUI window, given its name. */
static tui_gen_win_info *
tui_get_window_by_name (const std::string &name)
{
- if (name == "src")
- {
- if (tui_win_list[SRC_WIN] == nullptr)
- tui_win_list[SRC_WIN] = new tui_source_window ();
- return tui_win_list[SRC_WIN];
- }
- else if (name == "cmd")
- {
- if (tui_win_list[CMD_WIN] == nullptr)
- tui_win_list[CMD_WIN] = new tui_cmd_window ();
- return tui_win_list[CMD_WIN];
- }
- else if (name == "regs")
- {
- if (tui_win_list[DATA_WIN] == nullptr)
- tui_win_list[DATA_WIN] = new tui_data_window ();
- return tui_win_list[DATA_WIN];
- }
- else if (name == "asm")
- {
- if (tui_win_list[DISASSEM_WIN] == nullptr)
- tui_win_list[DISASSEM_WIN] = new tui_disasm_window ();
- return tui_win_list[DISASSEM_WIN];
- }
- else
- {
- gdb_assert (name == "status");
- return tui_locator_win_info_ptr ();
- }
+ for (tui_win_info *window : saved_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_gen_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 = new std::unordered_map<std::string, window_factory>;
+
+ known_window_types->emplace ("src",
+ make_standard_window<SRC_WIN,
+ tui_source_window>);
+ known_window_types->emplace ("cmd",
+ make_standard_window<CMD_WIN, tui_cmd_window>);
+ known_window_types->emplace ("regs",
+ make_standard_window<DATA_WIN,
+ tui_data_window>);
+ known_window_types->emplace ("asm",
+ make_standard_window<DISASSEM_WIN,
+ tui_disasm_window>);
+ known_window_types->emplace ("status", get_locator_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_copy == "cmd" || name_copy == "regs"
+ || name_copy == "asm" || name_copy == "status")
+ error (_("Window type \"%s\" is built-in"), name);
+
+ known_window_types->emplace (std::move (name_copy),
+ std::move (factory));
}
/* See tui-layout.h. */
height = height_;
gdb_assert (m_window != nullptr);
m_window->resize (height, width, x, y);
+ if (dynamic_cast<tui_win_info *> (m_window) != nullptr)
+ tui_windows.push_back ((tui_win_info *) m_window);
}
/* See tui-layout.h. */
void
-tui_layout_window::get_sizes (int *min_height, int *max_height)
+tui_layout_window::get_sizes (bool height, int *min_value, int *max_value)
{
if (m_window == nullptr)
m_window = tui_get_window_by_name (m_contents);
- *min_height = m_window->min_height ();
- *max_height = m_window->max_height ();
+ 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 ();
+ }
}
/* See tui-layout.h. */
std::unique_ptr<tui_layout_base>
tui_layout_split::clone () const
{
- tui_layout_split *result = new tui_layout_split ();
+ 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 ();
/* See tui-layout.h. */
void
-tui_layout_split::get_sizes (int *min_height, int *max_height)
+tui_layout_split::get_sizes (bool height, int *min_value, int *max_value)
{
- *min_height = 0;
- *max_height = 0;
+ *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 (&new_min, &new_max);
- *min_height += new_min;
- *max_height += 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;
}
}
/* See tui-layout.h. */
-bool
+tui_adjust_result
tui_layout_split::adjust_size (const char *name, int new_height)
{
/* Look through the children. If one is a layout holding the named
int found_index = -1;
for (int i = 0; i < m_splits.size (); ++i)
{
- if (m_splits[i].layout->adjust_size (name, new_height))
- return true;
- const char *win_name = m_splits[i].layout->get_name ();
- if (win_name != nullptr && strcmp (name, win_name) == 0)
+ tui_adjust_result adjusted
+ = m_splits[i].layout->adjust_size (name, new_height);
+ if (adjusted == HANDLED)
+ return HANDLED;
+ if (adjusted == FOUND)
{
+ if (!m_vertical)
+ return FOUND;
found_index = i;
break;
}
}
if (found_index == -1)
- return false;
+ return NOT_FOUND;
if (m_splits[found_index].layout->height == new_height)
- return true;
+ return HANDLED;
set_weights_from_heights ();
int delta = m_splits[found_index].weight - new_height;
int index = (found_index + 1 + i) % m_splits.size ();
int new_min, new_max;
- m_splits[index].layout->get_sizes (&new_min, &new_max);
+ m_splits[index].layout->get_sizes (m_vertical, &new_min, &new_max);
if (delta < 0)
{
apply (x, y, width, height);
}
- return true;
+ return HANDLED;
}
/* See tui-layout.h. */
width = width_;
height = height_;
- struct height_info
+ struct size_info
{
- int height;
- int min_height;
- int max_height;
+ int size;
+ int min_size;
+ int max_size;
/* True if this window will share a box border with the previous
window in the list. */
bool share_box;
};
- std::vector<height_info> info (m_splits.size ());
+ std::vector<size_info> info (m_splits.size ());
- /* Step 1: Find the min and max height of each sub-layout.
- Fixed-sized layouts are given their desired height, and then the
+ /* 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_height = height;
+ int available_size = m_vertical ? height : width;
int last_index = -1;
int total_weight = 0;
for (int i = 0; i < m_splits.size (); ++i)
/* 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 (&info[i].min_height, &info[i].max_height);
+ m_splits[i].layout->get_sizes (m_vertical, &info[i].min_size,
+ &info[i].max_size);
if (!m_applied
&& cmd_win_already_exists
/* 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 heights this way ensures
+ 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_height = TUI_CMD_WIN->height;
- info[i].max_height = TUI_CMD_WIN->height;
+ 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_height == info[i].max_height)
- available_height -= info[i].min_height;
+ if (info[i].min_size == info[i].max_size)
+ available_size -= info[i].min_size;
else
{
last_index = i;
}
/* Two adjacent boxed windows will share a border, making a bit
- more height available. */
+ more size available. */
if (i > 0
&& m_splits[i - 1].layout->bottom_boxed_p ()
&& m_splits[i].layout->top_boxed_p ())
info[i].share_box = true;
}
- /* Step 2: Compute the height of each sub-layout. Fixed-sized items
+ /* 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_height = 0;
+ int used_size = 0;
for (int i = 0; i < m_splits.size (); ++i)
{
/* Compute the height and clamp to the allowable range. */
- info[i].height = available_height * m_splits[i].weight / total_weight;
- if (info[i].height > info[i].max_height)
- info[i].height = info[i].max_height;
- if (info[i].height < info[i].min_height)
- info[i].height = info[i].min_height;
- /* If there is any leftover height, just redistribute it to the
+ 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;
+ /* If there is any leftover size, just redistribute it to the
last resizeable window, by dropping it from the allocated
- height. We could try to be fancier here perhaps, by
- redistributing this height among all windows, not just the
+ size. We could try to be fancier here perhaps, by
+ redistributing this size among all windows, not just the
last window. */
- if (info[i].min_height != info[i].max_height)
+ if (info[i].min_size != info[i].max_size)
{
- used_height += info[i].height;
+ used_size += info[i].size;
if (info[i].share_box)
- --used_height;
+ --used_size;
}
}
- /* Allocate any leftover height. */
- if (available_height >= used_height && last_index != -1)
- info[last_index].height += available_height - used_height;
+ /* Allocate any leftover size. */
+ if (available_size >= used_size && last_index != -1)
+ info[last_index].size += available_size - used_size;
/* Step 3: Resize. */
- int height_accum = 0;
+ 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 (height_accum + info[i].height > height)
- height_accum = height - info[i].height;
+ if (size_accum + info[i].size > maximum)
+ size_accum = maximum - info[i].size;
else if (info[i].share_box)
- --height_accum;
- m_splits[i].layout->apply (x, y + height_accum, width, info[i].height);
- height_accum += info[i].height;
+ --size_accum;
+ if (m_vertical)
+ m_splits[i].layout->apply (x, y + size_accum, width, info[i].size);
+ else
+ m_splits[i].layout->apply (x + size_accum, y, info[i].size, height);
+ size_accum += info[i].size;
}
m_applied = true;
if (depth > 0)
fputs_unfiltered ("{", output);
+ if (!m_vertical)
+ fputs_unfiltered ("-horizontal ", output);
+
bool first = true;
for (auto &item : m_splits)
{
static bool
validate_window_name (const std::string &name)
{
- return (name == "src" || name == "cmd"
- || name == "regs" || name == "asm"
- || name == "status");
+ auto iter = known_window_types->find (name);
+ return iter != known_window_types->end ();
}
/* Implementation of the "tui new-layout" command. */
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);
+ splits.emplace_back (new tui_layout_split (is_vertical));
std::unordered_set<std::string> seen_windows;
while (true)
{
if (spec[0] == '{')
{
- splits.emplace_back (new tui_layout_split);
- ++spec;
+ 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;
}
new_layout.release ();
}
-/* Base command for "layout". */
-
-static void
-tui_layout_command (const char *layout_name, int from_tty)
-{
- help_list (layout_list, "layout ", all_commands, gdb_stdout);
-}
-
/* Function to initialize gdb commands, for tui window layout
manipulation. */
void
_initialize_tui_layout ()
{
- add_prefix_cmd ("layout", class_tui, tui_layout_command, _("\
+ add_basic_prefix_cmd ("layout", class_tui, _("\
Change the layout of windows.\n\
Usage: layout prev | next | LAYOUT-NAME"),
- &layout_list, "layout ", 0, &cmdlist);
+ &layout_list, "layout ", 0, &cmdlist);
add_cmd ("next", class_tui, tui_next_layout_command,
- _("Apply the next TUI layout"),
+ _("Apply the next TUI layout."),
&layout_list);
add_cmd ("prev", class_tui, tui_prev_layout_command,
- _("Apply the previous TUI layout"),
+ _("Apply the previous TUI layout."),
&layout_list);
add_cmd ("regs", class_tui, tui_regs_layout_command,
- _("Apply the TUI register layout"),
+ _("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 NAME WINDOW WEIGHT [WINDOW WEIGHT]...\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\
- { NAME WEIGHT [NAME WEIGHT]... }\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 ();
}