From 7c043ba695a3cee067554b1e871e60f7934512b4 Mon Sep 17 00:00:00 2001 From: Tom Tromey Date: Sat, 22 Feb 2020 11:48:26 -0700 Subject: [PATCH] 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 PR tui/17850: * tui/tui-win.c (tui_gen_win_info::max_width): New method. * tui/tui-layout.h (class tui_layout_base) : Add "height" argument. (class tui_layout_window) : Likewise. (class tui_layout_split) : Add "vertical" argument. : Add "height" argument. : 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) : New methods. gdb/doc/ChangeLog 2020-02-22 Tom Tromey PR tui/17850: * gdb.texinfo (TUI Commands): Document horizontal layouts. gdb/testsuite/ChangeLog 2020-02-22 Tom Tromey 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(-) diff --git a/gdb/ChangeLog b/gdb/ChangeLog index cb7160622e2..83293187444 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,25 @@ +2020-02-22 Tom Tromey + + PR tui/17850: + * tui/tui-win.c (tui_gen_win_info::max_width): New method. + * tui/tui-layout.h (class tui_layout_base) : Add + "height" argument. + (class tui_layout_window) : Likewise. + (class tui_layout_split) : Add "vertical" + argument. + : Add "height" argument. + : 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) : + New methods. + 2020-02-22 Tom Tromey * tui/tui-layout.h (enum tui_adjust_result): New. diff --git a/gdb/NEWS b/gdb/NEWS index 830653f555c..18703247fa6 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -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). diff --git a/gdb/doc/ChangeLog b/gdb/doc/ChangeLog index 99f2d939760..2b6bbc87693 100644 --- a/gdb/doc/ChangeLog +++ b/gdb/doc/ChangeLog @@ -1,3 +1,8 @@ +2020-02-22 Tom Tromey + + PR tui/17850: + * gdb.texinfo (TUI Commands): Document horizontal layouts. + 2020-02-22 Tom Tromey * gdb.texinfo (TUI Overview): Mention user layouts. diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index c2617386869..0645fb05946 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -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 diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog index 9e825fbd53c..8f2fbdf2660 100644 --- a/gdb/testsuite/ChangeLog +++ b/gdb/testsuite/ChangeLog @@ -1,3 +1,9 @@ +2020-02-22 Tom Tromey + + PR tui/17850: + * gdb.tui/new-layout.exp: Add horizontal layout and winheight + tests. + 2020-02-22 Tom Tromey * gdb.tui/new-layout.exp: Add sub-layout tests. diff --git a/gdb/testsuite/gdb.tui/new-layout.exp b/gdb/testsuite/gdb.tui/new-layout.exp index 83823229bd4..b71de7de5fa 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 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
" + +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
.*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 2badcd4f1a6..e15ac585c98 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 e1833cd44e5..c10378617c2 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_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 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 info (m_splits.size ()); + std::vector 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> splits; - splits.emplace_back (new tui_layout_split); + splits.emplace_back (new tui_layout_split (is_vertical)); std::unordered_set 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."), 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 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 8cc002169e8..ef78e91b6fe 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, -- 2.30.2