Add horizontal splitting to TUI layout

This changes the TUI layout engine to add horizontal splitting.  Now,
windows can be side-by-side.

A horizontal split is defined using the "-horizontal" parameter to
"tui new-layout".

This also adds the first "winheight" test to the test suite.  One open
question is whether we want a new "winwidth" command, now that
horizontal layouts are possible.  This is easily done using the
generic layout code.

gdb/ChangeLog
2020-02-22  Tom Tromey  <tom@tromey.com>

	PR tui/17850:
	* tui/tui-win.c (tui_gen_win_info::max_width): New method.
	* tui/tui-layout.h (class tui_layout_base) <get_sizes>: Add
	"height" argument.
	(class tui_layout_window) <get_sizes>: Likewise.
	(class tui_layout_split) <tui_layout_split>: Add "vertical"
	argument.
	<get_sizes>: Add "height" argument.
	<m_vertical>: New field.
	* tui/tui-layout.c (tui_layout_split::clone): Update.
	(tui_layout_split::get_sizes): Add "height" argument.
	(tui_layout_split::adjust_size, tui_layout_split::apply): Update.
	(tui_new_layout_command): Parse "-horizontal".
	(_initialize_tui_layout): Update help string.
	(tui_layout_split::specification): Add "-horizontal" when needed.
	* tui/tui-layout.c (tui_layout_window::get_sizes): Add "height"
	argument.
	* tui/tui-data.h (struct tui_gen_win_info) <max_width, min_width>:
	New methods.

gdb/doc/ChangeLog
2020-02-22  Tom Tromey  <tom@tromey.com>

	PR tui/17850:
	* gdb.texinfo (TUI Commands): Document horizontal layouts.

gdb/testsuite/ChangeLog
2020-02-22  Tom Tromey  <tom@tromey.com>

	PR tui/17850:
	* gdb.tui/new-layout.exp: Add horizontal layout and winheight
	tests.

Change-Id: I38b35e504f34698578af86686be03c0fefd954ae
This commit is contained in:
Tom Tromey 2020-02-22 11:48:26 -07:00
parent 6bc5664858
commit 7c043ba695
10 changed files with 207 additions and 63 deletions

View File

@ -1,3 +1,25 @@
2020-02-22 Tom Tromey <tom@tromey.com>
PR tui/17850:
* tui/tui-win.c (tui_gen_win_info::max_width): New method.
* tui/tui-layout.h (class tui_layout_base) <get_sizes>: Add
"height" argument.
(class tui_layout_window) <get_sizes>: Likewise.
(class tui_layout_split) <tui_layout_split>: Add "vertical"
argument.
<get_sizes>: Add "height" argument.
<m_vertical>: New field.
* tui/tui-layout.c (tui_layout_split::clone): Update.
(tui_layout_split::get_sizes): Add "height" argument.
(tui_layout_split::adjust_size, tui_layout_split::apply): Update.
(tui_new_layout_command): Parse "-horizontal".
(_initialize_tui_layout): Update help string.
(tui_layout_split::specification): Add "-horizontal" when needed.
* tui/tui-layout.c (tui_layout_window::get_sizes): Add "height"
argument.
* tui/tui-data.h (struct tui_gen_win_info) <max_width, min_width>:
New methods.
2020-02-22 Tom Tromey <tom@tromey.com>
* tui/tui-layout.h (enum tui_adjust_result): New.

View File

@ -17,6 +17,8 @@
* The $_siginfo convenience variable now also works on Windows targets,
and will display the EXCEPTION_RECORD of the last handled exception.
* TUI windows can now be arranged horizontally.
* New commands
set exec-file-mismatch -- Set exec-file-mismatch handling (ask|warn|off).

View File

@ -1,3 +1,8 @@
2020-02-22 Tom Tromey <tom@tromey.com>
PR tui/17850:
* gdb.texinfo (TUI Commands): Document horizontal layouts.
2020-02-22 Tom Tromey <tom@tromey.com>
* gdb.texinfo (TUI Overview): Mention user layouts.

View File

@ -27998,11 +27998,23 @@ List and give the size of all displayed windows.
Create a new TUI layout. The new layout will be named @var{name}, and
can be accessed using the @code{layout} command (see below).
Each @var{window} parameter is the name of a window to display. The
windows will be displayed from top to bottom in the order listed. The
names of the windows are the same as the ones given to the
Each @var{window} parameter is either the name of a window to display,
or a window description. The windows will be displayed from top to
bottom in the order listed.
The names of the windows are the same as the ones given to the
@code{focus} command (see below); additional, the @code{status}
window can be specified.
window can be specified. Note that, because it is of fixed height,
the weight assigned to the status window is of no importance. It is
conventional to use @samp{0} here.
A window description looks a bit like an invocation of @code{tui
new-layout}, and is of the form
@{@r{[}@code{-horizontal}@r{]}@var{window} @var{weight} @r{[}@var{window} @var{weight}@dots{}@r{]}@}.
This specifies a sub-layout. If @code{-horizontal} is given, the
windows in this description will be arranged side-by-side, rather than
top-to-bottom.
Each @var{weight} is an integer. It is the weight of this window
relative to all the other windows in the layout. These numbers are
@ -28019,6 +28031,17 @@ and register windows, followed by the status window, and then finally
the command window. The non-status windows all have the same weight,
so the terminal will be split into three roughly equal sections.
Here is a more complex example, showing a horizontal layout:
@example
(gdb) tui new-layout example @{-horizontal src 1 asm 1@} 2 status 0 cmd 1
@end example
This will result in side-by-side source and assembly windows; with the
status and command window being beneath these, filling the entire
width of the terminal. Because they have weight 2, the source and
assembly windows will be twice the height of the command window.
@item layout @var{name}
@kindex layout
Changes which TUI windows are displayed. The @var{name} parameter

View File

@ -1,3 +1,9 @@
2020-02-22 Tom Tromey <tom@tromey.com>
PR tui/17850:
* gdb.tui/new-layout.exp: Add horizontal layout and winheight
tests.
2020-02-22 Tom Tromey <tom@tromey.com>
* gdb.tui/new-layout.exp: Add sub-layout tests.

View File

@ -52,6 +52,11 @@ gdb_test_no_output "tui new-layout example2 {asm 1 status 0} 1 cmd 1"
gdb_test "help layout example2" \
"Apply the \"example2\" layout.*tui new-layout example2 {asm 1 status 0} 1 cmd 1"
gdb_test_no_output "tui new-layout h {-horizontal asm 1 src 1} 1 status 0 cmd 1"
gdb_test "help layout h" \
"Apply the \"h\" layout.*tui new-layout h {-horizontal asm 1 src 1} 1 status 0 cmd 1"
if {![Term::enter_tui]} {
unsupported "TUI not supported"
}
@ -62,4 +67,18 @@ gdb_assert {![string match "No Source Available" $text]} \
Term::command "layout example"
Term::check_contents "example layout shows assembly" \
"No Assembly Available"
"$hex <main>"
Term::command "layout h"
Term::check_box "left window box" 0 0 40 15
Term::check_box "right window box" 39 0 41 15
Term::check_contents "horizontal display" \
"$hex <main>.*21.*return 0"
Term::command "winheight src - 5"
Term::check_box "left window box after shrink" 0 0 40 10
Term::check_box "right window box after shrink" 39 0 41 10
Term::command "winheight src + 5"
Term::check_box "left window box after grow" 0 0 40 15
Term::check_box "right window box after grow" 39 0 41 15

View File

@ -82,6 +82,15 @@ public:
/* Compute the minimum height of this window. */
virtual int min_height () const = 0;
/* Compute the maximum width of this window. */
int max_width () const;
/* Compute the minimum width of this window. */
int min_width () const
{
return 3;
}
/* Return true if this window can be boxed. */
virtual bool can_box () const
{

View File

@ -355,12 +355,20 @@ tui_layout_window::apply (int x_, int y_, int width_, int height_)
/* 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. */
@ -430,7 +438,7 @@ tui_layout_split::add_window (const char *name, int weight)
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 ();
@ -443,16 +451,29 @@ tui_layout_split::clone () const
/* 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;
}
}
@ -502,6 +523,8 @@ tui_layout_split::adjust_size (const char *name, int new_height)
return HANDLED;
if (adjusted == FOUND)
{
if (!m_vertical)
return FOUND;
found_index = i;
break;
}
@ -524,7 +547,7 @@ tui_layout_split::adjust_size (const char *name, int 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)
{
@ -571,23 +594,23 @@ tui_layout_split::apply (int x_, int y_, int width_, int height_)
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)
@ -597,7 +620,8 @@ tui_layout_split::apply (int x_, int y_, int width_, int height_)
/* 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
@ -607,15 +631,17 @@ tui_layout_split::apply (int x_, int y_, int width_, int height_)
/* 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;
@ -623,54 +649,58 @@ tui_layout_split::apply (int x_, int y_, int width_, int height_)
}
/* 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;
@ -716,6 +746,9 @@ tui_layout_split::specification (ui_file *output, int depth)
if (depth > 0)
fputs_unfiltered ("{", output);
if (!m_vertical)
fputs_unfiltered ("-horizontal ", output);
bool first = true;
for (auto &item : m_splits)
{
@ -839,8 +872,13 @@ tui_new_layout_command (const char *spec, int from_tty)
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)
{
@ -850,8 +888,11 @@ tui_new_layout_command (const char *spec, int from_tty)
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;
}
@ -940,12 +981,12 @@ Usage: layout prev | next | LAYOUT-NAME"),
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."),

View File

@ -58,8 +58,9 @@ public:
/* Change the size and location of this layout. */
virtual void apply (int x, int y, int width, int height) = 0;
/* Return the minimum and maximum height of this layout. */
virtual void get_sizes (int *min_height, int *max_height) = 0;
/* Return the minimum and maximum height or width of this layout.
HEIGHT is true to fetch height, false to fetch width. */
virtual void get_sizes (bool height, int *min_value, int *max_value) = 0;
/* True if the topmost item in this layout is boxed. */
virtual bool top_boxed_p () const = 0;
@ -142,7 +143,7 @@ public:
protected:
void get_sizes (int *min_height, int *max_height) override;
void get_sizes (bool height, int *min_value, int *max_value) override;
private:
@ -159,7 +160,12 @@ class tui_layout_split : public tui_layout_base
{
public:
tui_layout_split () = default;
/* Create a new layout. If VERTICAL is true, then windows in this
layout will be arranged vertically. */
explicit tui_layout_split (bool vertical = true)
: m_vertical (vertical)
{
}
DISABLE_COPY_AND_ASSIGN (tui_layout_split);
@ -191,7 +197,7 @@ public:
protected:
void get_sizes (int *min_height, int *max_height) override;
void get_sizes (bool height, int *min_value, int *max_value) override;
private:
@ -209,6 +215,9 @@ private:
/* The splits. */
std::vector<split> m_splits;
/* True if the windows in this split are arranged vertically. */
bool m_vertical;
/* True if this layout has already been applied at least once. */
bool m_applied = false;
};

View File

@ -952,6 +952,14 @@ tui_win_info::max_height () const
return tui_term_height () - 2;
}
/* See tui-data.h. */
int
tui_gen_win_info::max_width () const
{
return tui_term_width () - 2;
}
static void
parse_scrolling_args (const char *arg,
struct tui_win_info **win_to_scroll,