Patchwork [11/24] Add horizontal splitting to TUI layout

login
register
mail settings
Submitter Tom Tromey
Date Jan. 4, 2020, 6:33 p.m.
Message ID <20200104183410.17114-12-tom@tromey.com>
Download mbox | patch
Permalink /patch/37187/
State New
Headers show

Comments

Tom Tromey - Jan. 4, 2020, 6:33 p.m.
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-01-04  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-01-04  Tom Tromey  <tom@tromey.com>

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

gdb/testsuite/ChangeLog
2020-01-04  Tom Tromey  <tom@tromey.com>

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

Change-Id: I38b35e504f34698578af86686be03c0fefd954ae
---
 gdb/ChangeLog                        |  22 ++++
 gdb/NEWS                             |   2 +
 gdb/doc/ChangeLog                    |   5 +
 gdb/doc/gdb.texinfo                  |  31 +++++-
 gdb/testsuite/ChangeLog              |   6 ++
 gdb/testsuite/gdb.tui/new-layout.exp |  21 +++-
 gdb/tui/tui-data.h                   |   9 ++
 gdb/tui/tui-layout.c                 | 147 +++++++++++++++++----------
 gdb/tui/tui-layout.h                 |  19 +++-
 gdb/tui/tui-win.c                    |   8 ++
 10 files changed, 207 insertions(+), 63 deletions(-)
Eli Zaretskii - Jan. 4, 2020, 6:47 p.m.
> From: Tom Tromey <tom@tromey.com>
> Cc: Tom Tromey <tom@tromey.com>
> Date: Sat,  4 Jan 2020 11:33:57 -0700
> 
> gdb/ChangeLog
> 2020-01-04  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-01-04  Tom Tromey  <tom@tromey.com>
> 
> 	PR tui/17850:
> 	* gdb.texinfo (TUI Commands): Document horizontal layouts.
> 
> gdb/testsuite/ChangeLog
> 2020-01-04  Tom Tromey  <tom@tromey.com>
> 
> 	PR tui/17850:
> 	* gdb.tui/new-layout.exp: Add horizontal layout and winheight
> 	tests.

Thanks, the documentation parts are OK.

Patch

diff --git a/gdb/NEWS b/gdb/NEWS
index b8939693f4e..a936620c0a8 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -7,6 +7,8 @@ 
   that support it (see entry for GDB 9, below), providing faster
   performance for programs with many symbols.
 
+* TUI windows can now be arranged horizontally.
+
 * New commands
 
 tui new-layout NAME WINDOW WEIGHT [WINDOW WEIGHT]...
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 147d0c28c92..e011e992ba7 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -27909,11 +27909,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{locator}
-window can be specified.
+window can be specified.  Note that, because it is of fixed height,
+the weight assigned to the locator 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
@@ -27930,6 +27942,17 @@  and register windows, followed by the locator, and then finally the
 command window.  The non-locator 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 locator 0 cmd 1
+@end example
+
+This will result in side-by-side source and assembly windows; with the
+locator 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
diff --git a/gdb/testsuite/gdb.tui/new-layout.exp b/gdb/testsuite/gdb.tui/new-layout.exp
index 406d9b25f08..61f878532e6 100644
--- a/gdb/testsuite/gdb.tui/new-layout.exp
+++ b/gdb/testsuite/gdb.tui/new-layout.exp
@@ -52,6 +52,11 @@  gdb_test_no_output "tui new-layout example2 {asm 1 locator 0} 1 cmd 1"
 gdb_test "help layout example2" \
     "Apply the \"example2\" layout.*tui new-layout example2 {asm 1 locator 0} 1 cmd 1"
 
+gdb_test_no_output "tui new-layout h {-horizontal asm 1 src 1} 1 locator 0 cmd 1"
+
+gdb_test "help layout h" \
+    "Apply the \"h\" layout.*tui new-layout h {-horizontal asm 1 src 1} 1 locator 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
diff --git a/gdb/tui/tui-data.h b/gdb/tui/tui-data.h
index 66866dbc23f..570b55b1962 100644
--- a/gdb/tui/tui-data.h
+++ b/gdb/tui/tui-data.h
@@ -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
   {
diff --git a/gdb/tui/tui-layout.c b/gdb/tui/tui-layout.c
index be6c754d022..f33317beee8 100644
--- a/gdb/tui/tui-layout.c
+++ b/gdb/tui/tui-layout.c
@@ -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;
 	}
 
@@ -939,12 +980,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."),
diff --git a/gdb/tui/tui-layout.h b/gdb/tui/tui-layout.h
index 969e4dfd231..6607e8d40d8 100644
--- a/gdb/tui/tui-layout.h
+++ b/gdb/tui/tui-layout.h
@@ -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;
 };
diff --git a/gdb/tui/tui-win.c b/gdb/tui/tui-win.c
index 4f90c765b53..8206f3e6965 100644
--- a/gdb/tui/tui-win.c
+++ b/gdb/tui/tui-win.c
@@ -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,