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> 2020-02-22 Tom Tromey <tom@tromey.com>
* tui/tui-layout.h (enum tui_adjust_result): New. * 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, * The $_siginfo convenience variable now also works on Windows targets,
and will display the EXCEPTION_RECORD of the last handled exception. and will display the EXCEPTION_RECORD of the last handled exception.
* TUI windows can now be arranged horizontally.
* New commands * New commands
set exec-file-mismatch -- Set exec-file-mismatch handling (ask|warn|off). 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> 2020-02-22 Tom Tromey <tom@tromey.com>
* gdb.texinfo (TUI Overview): Mention user layouts. * 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 Create a new TUI layout. The new layout will be named @var{name}, and
can be accessed using the @code{layout} command (see below). can be accessed using the @code{layout} command (see below).
Each @var{window} parameter is the name of a window to display. The Each @var{window} parameter is either the name of a window to display,
windows will be displayed from top to bottom in the order listed. The or a window description. The windows will be displayed from top to
names of the windows are the same as the ones given to the 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} @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 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 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, the command window. The non-status windows all have the same weight,
so the terminal will be split into three roughly equal sections. 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} @item layout @var{name}
@kindex layout @kindex layout
Changes which TUI windows are displayed. The @var{name} parameter 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> 2020-02-22 Tom Tromey <tom@tromey.com>
* gdb.tui/new-layout.exp: Add sub-layout tests. * 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" \ gdb_test "help layout example2" \
"Apply the \"example2\" layout.*tui new-layout example2 {asm 1 status 0} 1 cmd 1" "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]} { if {![Term::enter_tui]} {
unsupported "TUI not supported" unsupported "TUI not supported"
} }
@ -62,4 +67,18 @@ gdb_assert {![string match "No Source Available" $text]} \
Term::command "layout example" Term::command "layout example"
Term::check_contents "example layout shows assembly" \ 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. */ /* Compute the minimum height of this window. */
virtual int min_height () const = 0; 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. */ /* Return true if this window can be boxed. */
virtual bool can_box () const 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. */ /* See tui-layout.h. */
void 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) if (m_window == nullptr)
m_window = tui_get_window_by_name (m_contents); m_window = tui_get_window_by_name (m_contents);
*min_height = m_window->min_height (); if (height)
*max_height = m_window->max_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. */ /* See tui-layout.h. */
@ -430,7 +438,7 @@ tui_layout_split::add_window (const char *name, int weight)
std::unique_ptr<tui_layout_base> std::unique_ptr<tui_layout_base>
tui_layout_split::clone () const 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) for (const split &item : m_splits)
{ {
std::unique_ptr<tui_layout_base> next = item.layout->clone (); std::unique_ptr<tui_layout_base> next = item.layout->clone ();
@ -443,16 +451,29 @@ tui_layout_split::clone () const
/* See tui-layout.h. */ /* See tui-layout.h. */
void 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; *min_value = 0;
*max_height = 0; *max_value = 0;
bool first_time = true;
for (const split &item : m_splits) for (const split &item : m_splits)
{ {
int new_min, new_max; int new_min, new_max;
item.layout->get_sizes (&new_min, &new_max); item.layout->get_sizes (height, &new_min, &new_max);
*min_height += new_min; /* For the mismatch case, the first time through we want to set
*max_height += new_max; 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; return HANDLED;
if (adjusted == FOUND) if (adjusted == FOUND)
{ {
if (!m_vertical)
return FOUND;
found_index = i; found_index = i;
break; 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 index = (found_index + 1 + i) % m_splits.size ();
int new_min, new_max; 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) if (delta < 0)
{ {
@ -571,23 +594,23 @@ tui_layout_split::apply (int x_, int y_, int width_, int height_)
width = width_; width = width_;
height = height_; height = height_;
struct height_info struct size_info
{ {
int height; int size;
int min_height; int min_size;
int max_height; int max_size;
/* True if this window will share a box border with the previous /* True if this window will share a box border with the previous
window in the list. */ window in the list. */
bool share_box; 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. /* Step 1: Find the min and max size of each sub-layout.
Fixed-sized layouts are given their desired height, and then the Fixed-sized layouts are given their desired size, and then the
remaining space is distributed among the remaining windows remaining space is distributed among the remaining windows
according to the weights given. */ according to the weights given. */
int available_height = height; int available_size = m_vertical ? height : width;
int last_index = -1; int last_index = -1;
int total_weight = 0; int total_weight = 0;
for (int i = 0; i < m_splits.size (); ++i) 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 /* Always call get_sizes, to ensure that the window is
instantiated. This is a bit gross but less gross than adding instantiated. This is a bit gross but less gross than adding
special cases for this in other places. */ 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 if (!m_applied
&& cmd_win_already_exists && 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 /* If this layout has never been applied, then it means the
user just changed the layout. In this situation, it's user just changed the layout. In this situation, it's
desirable to keep the size of the command window the 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 that the resizing step, below, does the right thing with
this window. */ this window. */
info[i].min_height = TUI_CMD_WIN->height; info[i].min_size = (m_vertical
info[i].max_height = TUI_CMD_WIN->height; ? 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) if (info[i].min_size == info[i].max_size)
available_height -= info[i].min_height; available_size -= info[i].min_size;
else else
{ {
last_index = i; 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 /* Two adjacent boxed windows will share a border, making a bit
more height available. */ more size available. */
if (i > 0 if (i > 0
&& m_splits[i - 1].layout->bottom_boxed_p () && m_splits[i - 1].layout->bottom_boxed_p ()
&& m_splits[i].layout->top_boxed_p ()) && m_splits[i].layout->top_boxed_p ())
info[i].share_box = true; 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 are given their fixed size, while others are resized according to
their weight. */ their weight. */
int used_height = 0; int used_size = 0;
for (int i = 0; i < m_splits.size (); ++i) for (int i = 0; i < m_splits.size (); ++i)
{ {
/* Compute the height and clamp to the allowable range. */ /* Compute the height and clamp to the allowable range. */
info[i].height = available_height * m_splits[i].weight / total_weight; info[i].size = available_size * m_splits[i].weight / total_weight;
if (info[i].height > info[i].max_height) if (info[i].size > info[i].max_size)
info[i].height = info[i].max_height; info[i].size = info[i].max_size;
if (info[i].height < info[i].min_height) if (info[i].size < info[i].min_size)
info[i].height = info[i].min_height; info[i].size = info[i].min_size;
/* If there is any leftover height, just redistribute it to the /* If there is any leftover size, just redistribute it to the
last resizeable window, by dropping it from the allocated last resizeable window, by dropping it from the allocated
height. We could try to be fancier here perhaps, by size. We could try to be fancier here perhaps, by
redistributing this height among all windows, not just the redistributing this size among all windows, not just the
last window. */ 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) if (info[i].share_box)
--used_height; --used_size;
} }
} }
/* Allocate any leftover height. */ /* Allocate any leftover size. */
if (available_height >= used_height && last_index != -1) if (available_size >= used_size && last_index != -1)
info[last_index].height += available_height - used_height; info[last_index].size += available_size - used_size;
/* Step 3: Resize. */ /* 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) for (int i = 0; i < m_splits.size (); ++i)
{ {
/* If we fall off the bottom, just make allocations overlap. /* If we fall off the bottom, just make allocations overlap.
GIGO. */ GIGO. */
if (height_accum + info[i].height > height) if (size_accum + info[i].size > maximum)
height_accum = height - info[i].height; size_accum = maximum - info[i].size;
else if (info[i].share_box) else if (info[i].share_box)
--height_accum; --size_accum;
m_splits[i].layout->apply (x, y + height_accum, width, info[i].height); if (m_vertical)
height_accum += info[i].height; 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; m_applied = true;
@ -716,6 +746,9 @@ tui_layout_split::specification (ui_file *output, int depth)
if (depth > 0) if (depth > 0)
fputs_unfiltered ("{", output); fputs_unfiltered ("{", output);
if (!m_vertical)
fputs_unfiltered ("-horizontal ", output);
bool first = true; bool first = true;
for (auto &item : m_splits) for (auto &item : m_splits)
{ {
@ -839,8 +872,13 @@ tui_new_layout_command (const char *spec, int from_tty)
if (new_name[0] == '-') if (new_name[0] == '-')
error (_("Layout name cannot start with '-'")); 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; 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; std::unordered_set<std::string> seen_windows;
while (true) while (true)
{ {
@ -850,8 +888,11 @@ tui_new_layout_command (const char *spec, int from_tty)
if (spec[0] == '{') if (spec[0] == '{')
{ {
splits.emplace_back (new tui_layout_split); is_vertical = true;
++spec; spec = skip_spaces (spec + 1);
if (check_for_argument (&spec, "-horizontal"))
is_vertical = false;
splits.emplace_back (new tui_layout_split (is_vertical));
continue; continue;
} }
@ -940,12 +981,12 @@ Usage: layout prev | next | LAYOUT-NAME"),
add_cmd ("new-layout", class_tui, tui_new_layout_command, add_cmd ("new-layout", class_tui, tui_new_layout_command,
_("Create a new TUI layout.\n\ _("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\ Create a new TUI layout. The new layout will be named NAME,\n\
and can be accessed using \"layout NAME\".\n\ and can be accessed using \"layout NAME\".\n\
The windows will be displayed in the specified order.\n\ The windows will be displayed in the specified order.\n\
A WINDOW can also be of the form:\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\ This form indicates a sub-frame.\n\
Each WEIGHT is an integer, which holds the relative size\n\ Each WEIGHT is an integer, which holds the relative size\n\
to be allocated to the window."), to be allocated to the window."),

View File

@ -58,8 +58,9 @@ public:
/* Change the size and location of this layout. */ /* Change the size and location of this layout. */
virtual void apply (int x, int y, int width, int height) = 0; virtual void apply (int x, int y, int width, int height) = 0;
/* Return the minimum and maximum height of this layout. */ /* Return the minimum and maximum height or width of this layout.
virtual void get_sizes (int *min_height, int *max_height) = 0; 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. */ /* True if the topmost item in this layout is boxed. */
virtual bool top_boxed_p () const = 0; virtual bool top_boxed_p () const = 0;
@ -142,7 +143,7 @@ public:
protected: protected:
void get_sizes (int *min_height, int *max_height) override; void get_sizes (bool height, int *min_value, int *max_value) override;
private: private:
@ -159,7 +160,12 @@ class tui_layout_split : public tui_layout_base
{ {
public: 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); DISABLE_COPY_AND_ASSIGN (tui_layout_split);
@ -191,7 +197,7 @@ public:
protected: protected:
void get_sizes (int *min_height, int *max_height) override; void get_sizes (bool height, int *min_value, int *max_value) override;
private: private:
@ -209,6 +215,9 @@ private:
/* The splits. */ /* The splits. */
std::vector<split> m_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. */ /* True if this layout has already been applied at least once. */
bool m_applied = false; bool m_applied = false;
}; };

View File

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