[RFC,gdb/tui] Add set tui border-kind acs-or-space/acs-or-ascii

Message ID 20230419133743.3288-1-tdevries@suse.de
State Dropped
Headers
Series [RFC,gdb/tui] Add set tui border-kind acs-or-space/acs-or-ascii |

Commit Message

Tom de Vries April 19, 2023, 1:37 p.m. UTC
  The gdb TUI testsuite uses a custom ansi terminal emulator (tuiterm.exp) in
order to be able to inspect the state of the terminal, and consequently runs
all test-cases using TERM=ansi.

Once in a while I manually replay a test-case on an actual terminal emulator,
using TERM=ansi to make sure I trigger the same behaviour, and run into funny
looking borders, like this:
...
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³                             ³
³ [ No Source Available ]     ³
³                             ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
...
This is PR tui/30370.

AFAICT, otherwise everything works as expected.  Negative effects though may
include:
- a user assessing that "something" went wrong, and not knowing the scope of
  the problem abandoning the TUI session, and
- a GDB developer spending time debugging TUI to understand what went wrong.

The problem can be worked around in GDB, by using a different
"tui border-kind" than acs, say ascii, such that we have:
...
+-----------------------------+
|                             |
| [ No Source Available ]     |
|                             |
+-----------------------------+
...

The problem is known in the context of ncurses [1], and a workaround can be
applied by setting the environment variable NCURSES_NO_UTF8_ACS to 1, which
gives us the desired:
...
┌─────────────────────────────┐
│                             │
│ [ No Source Available ]     │
│                             │
└─────────────────────────────┘
...
[ Note that the workaround is applied automatically by ncurses for TERM=screen
and TERM=linux. ]

But, you have to know about these workarounds in order to be able to apply
them.

It would be nice if gdb could detect when a workaround is required, and
apply the ncurses one without the user even having to be aware of it.  But
AFAIU that's not possible (and arguably, if that were the case, the logic
would have been applied in ncurses).

Another option is to detect if a workaround may be required, inform the
user about the workaround(s), and ask the user if gdb should apply
one of the workarounds if possible, but that's a bit intrusive.

Instead, introduce two new tui border-kind values acs-or-space and
acs-or-ascii, and change the default from acs to acs-or-ascii.

For value acs-or-ascii, if gdb detects a workaround may be required, it uses
ascii, and otherwise acs.

More concretely, this means the border kind is now ascii by default (for my
default environment with TERM=xterm and NCURSES_NO_UTF8_ACS unset) while
"show tui border-kind" mentions the reason the fallback is chosen:
...
$ gdb -q -batch -ex "show tui border-kind"
The kind of border for TUI windows is "acs-or-ascii" \
  (ascii, NCURSES_NO_UTF8_ACS unset).
...

[ Note that we could extend the reason to:
...
  (ascii, TERM != screen && TERM != linux && NCURSES_NO_UTF8_ACS unset)
...
or
...
  (ascii, TERM = xterm && NCURSES_NO_UTF8_ACS unset)
...
but I'm not sure if this would make things more clear. ]

And if we tell GDB that the workaround is or isn't necessary by setting
NCURSES_NO_UTF8_ACS, we get acs:
...
$ NCURSES_NO_UTF8_ACS=1 gdb -q -batch -ex "show tui border-kind"
The kind of border for TUI windows is "acs-or-ascii" (acs).
$ NCURSES_NO_UTF8_ACS=0 gdb -q -batch -ex "show tui border-kind"
The kind of border for TUI windows is "acs-or-ascii" (acs).
...

Or likewise, if we use TERM=screen or TERM=linux:
...
$ TERM=screen gdb -q -batch -ex "show tui border-kind"
The kind of border for TUI windows is "acs-or-ascii" (acs).
$ TERM=linux gdb -q -batch -ex "show tui border-kind"
The kind of border for TUI windows is "acs-or-ascii" (acs).
...

RFC for now, so:
- no docs,
- no attempt to make things efficient.

Is this approach a good idea?

Any other comments?

Tested on x86_64-linux.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=30370

[1] https://invisible-island.net/ncurses/man/ncurses.3x.html#h3-NCURSES_NO_UTF8_ACS
---
 gdb/testsuite/gdb.tui/tui-border-kind.exp | 83 +++++++++++++++++++++
 gdb/testsuite/lib/tuiterm.exp             |  4 +
 gdb/tui/tui-win.c                         | 91 ++++++++++++++++++++---
 3 files changed, 167 insertions(+), 11 deletions(-)
 create mode 100644 gdb/testsuite/gdb.tui/tui-border-kind.exp


base-commit: 28ab94f51dcdee056d96e57ad04c27c22cf854ea
  

Comments

Tom Tromey Dec. 15, 2023, 7:43 p.m. UTC | #1
>>>>> "Tom" == Tom de Vries via Gdb-patches <gdb-patches@sourceware.org> writes:

I don't recall if we discussed or finished discussing this...

Tom> The gdb TUI testsuite uses a custom ansi terminal emulator (tuiterm.exp) in
Tom> order to be able to inspect the state of the terminal, and consequently runs
Tom> all test-cases using TERM=ansi.

Tom> Once in a while I manually replay a test-case on an actual terminal emulator,
Tom> using TERM=ansi to make sure I trigger the same behaviour, and run into funny
Tom> looking borders, like this:
Tom> ...
Tom> ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
Tom> ³                             ³
Tom> ³ [ No Source Available ]     ³
Tom> ³                             ³
Tom> ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
Tom> ...
Tom> This is PR tui/30370.

IIUC, you are not using an actual ANSI terminal, but some other thing,
and then setting TERM=ansi?

I think in this case you should just expect that things will go wrong.
TERM has to reflect the capabilities of the actual terminal.  I tend to
think gdb shouldn't try to work around this kind of thing, if a user
does this it will probably affect any terminal-aware program.

Tom
  
Tom de Vries Dec. 16, 2023, 9:50 a.m. UTC | #2
On 12/15/23 20:43, Tom Tromey wrote:
>>>>>> "Tom" == Tom de Vries via Gdb-patches <gdb-patches@sourceware.org> writes:
> 
> I don't recall if we discussed or finished discussing this...
> 
> Tom> The gdb TUI testsuite uses a custom ansi terminal emulator (tuiterm.exp) in
> Tom> order to be able to inspect the state of the terminal, and consequently runs
> Tom> all test-cases using TERM=ansi.
> 
> Tom> Once in a while I manually replay a test-case on an actual terminal emulator,
> Tom> using TERM=ansi to make sure I trigger the same behaviour, and run into funny
> Tom> looking borders, like this:
> Tom> ...
> Tom> ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
> Tom> ³                             ³
> Tom> ³ [ No Source Available ]     ³
> Tom> ³                             ³
> Tom> ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
> Tom> ...
> Tom> This is PR tui/30370.
> 
> IIUC, you are not using an actual ANSI terminal, but some other thing,
> and then setting TERM=ansi?
> 

Yes (and some other thing meaning xterm, gnome-terminal or konsole).

> I think in this case you should just expect that things will go wrong.
> TERM has to reflect the capabilities of the actual terminal.  I tend to
> think gdb shouldn't try to work around this kind of thing, if a user
> does this it will probably affect any terminal-aware program.

Agreed.

FWIW, my meta concern here was difference in behaviour between 
test-cases run in the testsuite, and replaying those scenarios in an xterm.

I think this concern is better addressed by the ansi-for-tui proposal (
https://sourceware.org/pipermail/gdb-patches/2023-May/199761.html ).

Thanks,
- Tom
  

Patch

diff --git a/gdb/testsuite/gdb.tui/tui-border-kind.exp b/gdb/testsuite/gdb.tui/tui-border-kind.exp
new file mode 100644
index 00000000000..cd6bf0899f0
--- /dev/null
+++ b/gdb/testsuite/gdb.tui/tui-border-kind.exp
@@ -0,0 +1,83 @@ 
+# Copyright 2023 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# Check that tui border-kind acs-or-ascii results in the right tui
+# border-kind.
+
+# We set variables in env on build that we need on host, so we require
+# host == build.
+require {!is_remote host}
+
+# Don't use tuiterm_env, it just supports tui border-kind ascii.
+# So we do a more basic test, and don't enter TUI.
+
+with_test_prefix TERM=screen {
+    save_vars { env(TERM) } {
+	setenv TERM screen
+	clean_restart
+
+	# Check that TERM=screen selects acs.
+	gdb_test "show tui border-kind" \
+	    {The kind of border for TUI windows is "acs-or-ascii" \(acs\).}
+
+    }
+}
+
+with_test_prefix TERM=linux {
+    save_vars { env(TERM) } {
+	setenv TERM linux
+	clean_restart
+
+	# Check that TERM=linux selects acs.	
+	gdb_test "show tui border-kind" \
+	    {The kind of border for TUI windows is "acs-or-ascii" \(acs\).}
+    }
+}
+
+
+with_test_prefix TERM=ansi {
+    save_vars { env(TERM) } {
+	setenv TERM ansi
+
+	with_test_prefix NCURSES_NO_UTF8_ACS=1 {
+	    setenv NCURSES_NO_UTF8_ACS 1
+	    clean_restart
+
+	    # Check that TERM=linux and NCURSES_NO_UTF8_ACS=1 selects acs.
+	    gdb_test "show tui border-kind" \
+		{The kind of border for TUI windows is "acs-or-ascii" \(acs\).}
+	}
+
+	with_test_prefix NCURSES_NO_UTF8_ACS=0 {
+	    setenv NCURSES_NO_UTF8_ACS 0
+	    clean_restart
+
+	    # Check that TERM=linux and NCURSES_NO_UTF8_ACS=0 selects acs.
+	    gdb_test "show tui border-kind" \
+		{The kind of border for TUI windows is "acs-or-ascii" \(acs\).}
+	}
+
+	with_test_prefix NCURSES_NO_UTF8_ACS=unset {
+	    unset -nocomplain env(NCURSES_NO_UTF8_ACS)
+	    clean_restart
+
+	    # Check that TERM=ansi with NCURSES_NO_UTF8_ACS unset selects ascii.
+	    gdb_test "show tui border-kind" \
+		[concat \
+		     {The kind of border for TUI windows is "acs-or-ascii"} \
+		     {\(ascii, NCURSES_NO_UTF8_ACS unset\).}]
+	}
+    }
+}
diff --git a/gdb/testsuite/lib/tuiterm.exp b/gdb/testsuite/lib/tuiterm.exp
index 05edfe9a5b1..3d7a9df52de 100644
--- a/gdb/testsuite/lib/tuiterm.exp
+++ b/gdb/testsuite/lib/tuiterm.exp
@@ -804,7 +804,11 @@  namespace eval Term {
 	    return 0
 	}
 
+	# We could drop this, and the default tui border-kind acs-or-ascii
+	# would normally pick ascii.  But we'd run into if NCURSES_NO_UTF8_ACS
+	# would be set, which would pick acs instead.
 	gdb_test_no_output "set tui border-kind ascii"
+
 	gdb_test_no_output "maint set tui-resize-message on"
 	return 1
     }
diff --git a/gdb/tui/tui-win.c b/gdb/tui/tui-win.c
index 3b17cb8dd29..1339fc0c8e2 100644
--- a/gdb/tui/tui-win.c
+++ b/gdb/tui/tui-win.c
@@ -93,6 +93,8 @@  static const char *const tui_border_kind_enums[] = {
   "space",
   "ascii",
   "acs",
+  "acs-or-space",
+  "acs-or-ascii",
   NULL
 };
 
@@ -207,15 +209,80 @@  The attribute mode to use for the TUI window borders is \"%s\".\n"),
 	      value);
 }
 
-static const char *tui_border_kind = "acs";
+/* Actual value of "tui border-kind" setting.  X-or-y values allowed.  */
+static const char *tui_border_kind_setting = "acs-or-ascii";
+
+/* Actual value of "tui border-kind".  X-or-y values not allowed.  */
+static const char *tui_border_kind_value = nullptr;
+
+/* Reason why y was used in a X-or-y value for "tui border-kind".  */
+static const char *tui_border_kind_value_reason = nullptr;
+
+static void
+determine_tui_border_kind_value (void)
+{
+  const char *fallback = nullptr;
+
+  tui_border_kind_value_reason = "";
+
+  if (strcmp (tui_border_kind_setting, "acs-or-ascii") == 0)
+    {
+      /* Tentatively set to acs, and define fallback.  */
+      tui_border_kind_value = "acs";
+      fallback = "ascii";
+    }
+  else if (strcmp (tui_border_kind_setting, "acs-or-space") == 0)
+    {
+      /* Tentatively set to acs, and define fallback.  */
+      tui_border_kind_value = "acs";
+      fallback = "space";
+    }
+  else
+    {
+      tui_border_kind_value = tui_border_kind_setting;
+      return;
+    }
+
+  char *env_term = getenv ("TERM");
+  if (strcmp (env_term, "linux") == 0 || strcmp (env_term, "screen") == 0)
+    {
+      /* Ncurses knows how to handle these TERM values.  */
+      return;
+    }
+
+  char *env_ncurses_no_utf8_acs = getenv ("NCURSES_NO_UTF8_ACS");
+  if (env_ncurses_no_utf8_acs != nullptr)
+    {
+      /* The user knowns if the workaround is required or not.  */
+      return;
+    }
+
+  /* There may be problems with acs, so apply the fall back.  */
+  tui_border_kind_value = fallback;
+  tui_border_kind_value_reason = ", NCURSES_NO_UTF8_ACS unset";
+}
+
+static const char *
+tui_border_kind (void)
+{
+  determine_tui_border_kind_value ();
+  return tui_border_kind_value;
+}
+
 static void
 show_tui_border_kind (struct ui_file *file, 
 		      int from_tty,
 		      struct cmd_list_element *c, 
 		      const char *value)
 {
-  gdb_printf (file, _("The kind of border for TUI windows is \"%s\".\n"),
-	      value);
+  if (strcmp (tui_border_kind (), value) != 0)
+    {
+      gdb_printf (file, _("The kind of border for TUI windows is \"%s\" (%s%s).\n"),
+		  value, tui_border_kind (), tui_border_kind_value_reason);
+    }
+  else
+    gdb_printf (file, _("The kind of border for TUI windows is \"%s\".\n"),
+		value);
 }
 
 /* Implementation of the "set/show style tui-current-position" commands.  */
@@ -298,25 +365,25 @@  tui_update_variables ()
   /* If one corner changes, all characters are changed.
      Only check the first one.  The ACS characters are determined at
      run time by curses terminal management.  */
-  entry = translate (tui_border_kind, tui_border_kind_translate_lrcorner);
+  entry = translate (tui_border_kind (), tui_border_kind_translate_lrcorner);
   if (tui_border_lrcorner != (chtype) entry->value)
     {
       tui_border_lrcorner = (entry->value < 0) ? ACS_LRCORNER : entry->value;
       need_redraw = true;
     }
-  entry = translate (tui_border_kind, tui_border_kind_translate_llcorner);
+  entry = translate (tui_border_kind (), tui_border_kind_translate_llcorner);
   tui_border_llcorner = (entry->value < 0) ? ACS_LLCORNER : entry->value;
 
-  entry = translate (tui_border_kind, tui_border_kind_translate_ulcorner);
+  entry = translate (tui_border_kind (), tui_border_kind_translate_ulcorner);
   tui_border_ulcorner = (entry->value < 0) ? ACS_ULCORNER : entry->value;
 
-  entry = translate (tui_border_kind, tui_border_kind_translate_urcorner);
+  entry = translate (tui_border_kind (), tui_border_kind_translate_urcorner);
   tui_border_urcorner = (entry->value < 0) ? ACS_URCORNER : entry->value;
 
-  entry = translate (tui_border_kind, tui_border_kind_translate_hline);
+  entry = translate (tui_border_kind (), tui_border_kind_translate_hline);
   tui_border_hline = (entry->value < 0) ? ACS_HLINE : entry->value;
 
-  entry = translate (tui_border_kind, tui_border_kind_translate_vline);
+  entry = translate (tui_border_kind (), tui_border_kind_translate_vline);
   tui_border_vline = (entry->value < 0) ? ACS_VLINE : entry->value;
 
   return need_redraw;
@@ -1204,13 +1271,15 @@  defaults to 1, and WIN defaults to the currently focused window."));
 
   /* Define the tui control variables.  */
   add_setshow_enum_cmd ("border-kind", no_class, tui_border_kind_enums,
-			&tui_border_kind, _("\
+			&tui_border_kind_setting, _("\
 Set the kind of border for TUI windows."), _("\
 Show the kind of border for TUI windows."), _("\
 This variable controls the border of TUI windows:\n\
    space           use a white space\n\
    ascii           use ascii characters + - | for the border\n\
-   acs             use the Alternate Character Set"),
+   acs             use the Alternate Character Set\n\
+   acs-or-space    detect if acs is possible, otherwise use space\n\
+   acs-or-ascii    detect if acs is possible, otherwise use ascii"),
 			tui_set_var_cmd,
 			show_tui_border_kind,
 			&tui_setlist, &tui_showlist);