From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id EctsIU7R0mjseg8AWB0awg (envelope-from ) for ; Tue, 23 Sep 2025 12:56:46 -0400 Authentication-Results: simark.ca; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=ROEbUb2y; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id 833001E04C; Tue, 23 Sep 2025 12:56:46 -0400 (EDT) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on simark.ca X-Spam-Level: X-Spam-Status: No, score=-2.1 required=5.0 tests=ARC_SIGNED,ARC_VALID,BAYES_00, DKIM_INVALID,DKIM_SIGNED,MAILING_LIST_MULTI,RCVD_IN_DNSWL_MED, RCVD_IN_VALIDITY_CERTIFIED_BLOCKED,RCVD_IN_VALIDITY_RPBL_BLOCKED, RCVD_IN_VALIDITY_SAFE_BLOCKED autolearn=ham autolearn_force=no version=4.0.1 Received: from server2.sourceware.org (server2.sourceware.org [8.43.85.97]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by simark.ca (Postfix) with ESMTPS id 81AFC1E04C for ; Tue, 23 Sep 2025 12:56:45 -0400 (EDT) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 34AD93858419 for ; Tue, 23 Sep 2025 16:56:45 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 34AD93858419 Authentication-Results: sourceware.org; dkim=fail reason="signature verification failed" (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=ROEbUb2y Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTP id 7C07E3858C42 for ; Tue, 23 Sep 2025 16:54:41 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 7C07E3858C42 Authentication-Results: sourceware.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 7C07E3858C42 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1758646481; cv=none; b=XcBGhNRpk8iJWf33KpZSI11qUo17uorzG7CCc+qCjlZb3Mk26JdRQ3pFnexzjrxSlXbJfSK8CaX7yB1m10mr5G4KvXAlarOVnkoKhtO1Ie1j+Z5yfvCXS8YSDCGI6gk7h3aouImE2ShTLx2JwToI7pxRYLQL6VWsM0vaz2J0UWU= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1758646481; c=relaxed/simple; bh=0S6I8S51nS7/h6iBf5gd0I0G0w0WzZQXLcN9x69vt40=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=JoDtMLYp90SJjDw2k8zfYAe8Gd0Twy6BTXuvcBwd+AQJv8AdTcOOn9AZ+OKVDBdKnRTOE59l2qdLdlesn5CnddRWwm2oGkJ4trbnPsomzx7g7I/Sl45Xe2c/Lwwu1oTy6Vv4UKbbuX57S3/NfGWmJ8Mx2NDRcCsqqyhb+D3hdC4= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 7C07E3858C42 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1758646481; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=J0Kx3L9IBiaSJagfG9PTOFt7m7x8UvkSC8txKluZ3iQ=; b=ROEbUb2yadmGdCdmZpg+4o2vgUcVEjdset6lRixUJoW4LRrTzJncp6DIYYPzpk/kj5K4M9 3tOiZ1EiL6aCKkfPAhTNTmOE1F1EJy3Fji6bGn3ji4T5K8Zju5yg4++8GczE+grxHUUTQb DfiKVR95KeDlYDgTSzG5FN5rAY9ORy4= Received: from mail-wr1-f69.google.com (mail-wr1-f69.google.com [209.85.221.69]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-198-cI_AXG4ROXG7lXhlXFOGBw-1; Tue, 23 Sep 2025 12:54:40 -0400 X-MC-Unique: cI_AXG4ROXG7lXhlXFOGBw-1 X-Mimecast-MFC-AGG-ID: cI_AXG4ROXG7lXhlXFOGBw_1758646479 Received: by mail-wr1-f69.google.com with SMTP id ffacd0b85a97d-3f924ae2a89so2398935f8f.3 for ; Tue, 23 Sep 2025 09:54:39 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1758646478; x=1759251278; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=j2YYb/V2FTLmN0jk7uxJS5NuDKIKedenERoqfZckaiM=; b=FnxOOsD2GSGeWM2cp2zEz3vulpuSzcd4PA/7TD7OfsDvn0GD1BiIVTP0khZLnuPaTp 9FKJf2OFuhamgwqBYtS3KFixcWn6jtnrtsDtBp5F71kw7pibe3QDjuLoChLM6S0+wTAo hiZ+cpkuSCUnlTONGUyfwLglF6PsSDL/Q8+moZFzTQHROHVAq10GFskL6toXVbQT6oyy UucPYNDO8aAd7oI//PevkfAxXzvRuDKvZ7p/G4kyZF+Dz5FWWDXXoyfWK3J1A6vN1hpo C59aXsMLoWR7geP4ktdm6rZx31ALCuVlCTLbKa+BxhCV3ZR4DppjeRhrThwspRzbi2nH G/YQ== X-Gm-Message-State: AOJu0Yz34oiZcdFn1Nm1I/aUs0sgxZ+4yafa46E4UTNw2yysFN+HlVe/ dJHV7X/fP7hSqROV3sH3fscu94dYmm6/3XSrPfqiDawzUcbD2R3CpNZZjew8uONMeiRpPyHORqh XBkVm0LkwecNStUJmQBYgNSeSH3jQ0xvon3LiXGt+RDDrRrmobhfGaJEBS3rh+iwF7kWD2zBx02 z+pkeyywaBtpCykSs9MvHax2OdYe7fBGZvX6fROt/9hKxHB8o= X-Gm-Gg: ASbGncsm2QQ8zVM281XjVHNj1o1EcJ+/IkN4zBL5LOG4jCYFGu5G+K5UJRGzO7i/Xs4 9SlrNrMdR1LHUAmMSD05bIZLzPezv1uKHKmPLwyMxbIKg0Deuzkzy1+lzU3CDnR3cJZ039nugZi bxgF2vIZ0Xe1O3UUD4H5mglaAymmOfIP6XtNDpAgv631drKcBWBHP2G0y+0/AgV0YYIN7p+prbs XJMQ5oevUvou4VsxxgC22LW/rKtpNCgS4u3Od+56WrBT9M3wAwwP3jyAQYIQcbHEtJ+NU+OOCC4 V1VnzWHFbxMC7FWiRODXk52mFh6Hk0/pfyM= X-Received: by 2002:a05:6000:40c7:b0:3ff:17ac:a347 with SMTP id ffacd0b85a97d-405c6d10242mr3697755f8f.27.1758646478100; Tue, 23 Sep 2025 09:54:38 -0700 (PDT) X-Google-Smtp-Source: AGHT+IEXSY8QdTkhYXpgJFevVxEoaXWeZS2kI1hnz7e3yPZ/ZdphSL8d9wQzXNC73GzKuV6VbmvZOw== X-Received: by 2002:a05:6000:40c7:b0:3ff:17ac:a347 with SMTP id ffacd0b85a97d-405c6d10242mr3697719f8f.27.1758646477562; Tue, 23 Sep 2025 09:54:37 -0700 (PDT) Received: from localhost ([31.111.84.207]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-3ee0fbf1d35sm26431222f8f.55.2025.09.23.09.54.37 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 23 Sep 2025 09:54:37 -0700 (PDT) From: Andrew Burgess To: gdb-patches@sourceware.org Cc: Andrew Burgess , Eli Zaretskii Subject: [PATCHv9 3/3] gdb/python: extend gdb.write to support styled output Date: Tue, 23 Sep 2025 17:54:31 +0100 Message-ID: X-Mailer: git-send-email 2.47.1 In-Reply-To: References: MIME-Version: 1.0 X-Mimecast-Spam-Score: 1 X-Mimecast-MFC-PROC-ID: LFpbGTX999PlRZauoR6pnFZb3DrWKt4N3NZN9o9YXt4_1758646479 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: 8bit content-type: text/plain; charset="US-ASCII"; x-default=true X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gdb-patches-bounces~public-inbox=simark.ca@sourceware.org It is already possible to produce styled output from Python by converting the gdb.Style to its escape code sequence, and writing that to the output stream. But this commit adds an alternative option to the mix by extending the existing gdb.write() function to accept a 'style' argument. The value of this argument can be 'None' to indicate no style change should be performed, this is the default, and matches the existing behaviour. Or the new 'style' argument can be a gdb.Style object, in which case the specified style is applied only for the string passed to gdb.write, after which the default style is re-applied. Using gdb.write with a style object more closely matches how GDB handles styling internally, and has the benefit that the user doesn't need to remember to restore the default style when they are done. Reviewed-By: Eli Zaretskii --- gdb/NEWS | 3 + gdb/doc/python.texi | 9 ++- gdb/python/py-style.c | 21 +++++++ gdb/python/python-internal.h | 14 +++++ gdb/python/python.c | 30 ++++++++-- .../gdb.python/py-color-pagination.exp | 56 +++++++++++++++++++ .../gdb.python/py-color-pagination.py | 16 ++++++ gdb/testsuite/gdb.python/py-style.exp | 29 ++++++++++ 8 files changed, 172 insertions(+), 6 deletions(-) diff --git a/gdb/NEWS b/gdb/NEWS index 6d83083f600..3c6f8e9759e 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -190,6 +190,9 @@ info threads [-gid] [-stopped] [-running] [ID]... Use gdb.StyleParameterSet(NAME) to create 'set style NAME ...' and 'show style NAME ...' parameters. + ** The gdb.write() function now takes an additional, optional, + 'style' argument, which can be used to style the output. + ** The memory_source argument (the second argument) has been removed from gdb.disassembler.builtin_disassemble. This argument was never used by GDB, and was added by mistake. The unused argument diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi index 45e53845c6e..79f15a67287 100644 --- a/gdb/doc/python.texi +++ b/gdb/doc/python.texi @@ -452,7 +452,7 @@ Basic Python historical compatibility. @end defun -@defun gdb.write (string @r{[}, stream@r{]}) +@defun gdb.write (string @r{[}, stream@r{]} @r{[}, style@r{]}) Print a string to @value{GDBN}'s paginated output stream. The optional @var{stream} determines the stream to print to. The default stream is @value{GDBN}'s standard output stream. Possible stream @@ -475,6 +475,13 @@ Basic Python @value{GDBN}'s log stream (@pxref{Logging Output}). @end table +The @var{style} should be a @code{gdb.Style} object (@pxref{Styles In +Python}), or @code{None} (the default). If @var{style} is @code{None} +then the current style for @var{stream} will be applied to @var{text}. +If @var{style} is a @code{gdb.Style} object, then this style is +applied to @var{text}, after which the default output style is +restored. + Writing to @code{sys.stdout} or @code{sys.stderr} will automatically call this function and will automatically direct the output to the relevant stream. diff --git a/gdb/python/py-style.c b/gdb/python/py-style.c index 51b35f2fa7e..cf65e3115b2 100644 --- a/gdb/python/py-style.c +++ b/gdb/python/py-style.c @@ -353,6 +353,15 @@ stylepy_init (PyObject *self, PyObject *args, PyObject *kwargs) +/* See python-internal.h. */ + +bool +gdbpy_is_style (PyObject *obj) +{ + gdb_assert (obj != nullptr); + return PyObject_TypeCheck (obj, &style_object_type); +} + /* Return the ui_file_style for STYLEPY. If the style cannot be found, then return an empty optional, and set a Python error. */ @@ -369,6 +378,18 @@ stylepy_to_style (style_object *stylepy) return style; } +/* See python-internal.h. */ + +std::optional +gdbpy_style_object_to_ui_file_style (PyObject *obj) +{ + gdb_assert (obj != nullptr); + gdb_assert (gdbpy_is_style (obj)); + + style_object *style_obj = (style_object *) obj; + return stylepy_to_style (style_obj); +} + /* Implementation of gdb.Style.escape_sequence(). Return the escape sequence to apply Style. If styling is turned off, then this returns the empty string. Can raise an exception if a named style can no longer diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h index f61a1753ac4..dbb2d7eb7e0 100644 --- a/gdb/python/python-internal.h +++ b/gdb/python/python-internal.h @@ -570,6 +570,20 @@ struct symtab_and_line *sal_object_to_symtab_and_line (PyObject *obj); frame_info_ptr frame_object_to_frame_info (PyObject *frame_obj); struct gdbarch *arch_object_to_gdbarch (PyObject *obj); +/* Return true if OBJ is a gdb.Style object. OBJ must not be NULL. */ + +extern bool gdbpy_is_style (PyObject *obj); + +/* Return the ui_file_style from OBJ, a gdb.Style object. OBJ must not be + NULL. + + It is possible that OBJ is a gdb.Style object, but the underlying style + cannot be fetched for some reason. If this happens then a Python error + is set and an empty optional is returned. */ + +extern std::optional + gdbpy_style_object_to_ui_file_style (PyObject *obj); + extern PyObject *gdbpy_execute_mi_command (PyObject *self, PyObject *args, PyObject *kw); diff --git a/gdb/python/python.c b/gdb/python/python.c index 740b1968742..51e7a0aa165 100644 --- a/gdb/python/python.c +++ b/gdb/python/python.c @@ -1567,12 +1567,22 @@ static PyObject * gdbpy_write (PyObject *self, PyObject *args, PyObject *kw) { const char *arg; - static const char *keywords[] = { "text", "stream", NULL }; + static const char *keywords[] = { "text", "stream", "style", nullptr }; int stream_type = 0; + PyObject *style_obj = Py_None; - if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "s|i", keywords, &arg, - &stream_type)) - return NULL; + if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "s|iO", keywords, &arg, + &stream_type, &style_obj)) + return nullptr; + + if (style_obj != Py_None && !gdbpy_is_style (style_obj)) + { + PyErr_Format + (PyExc_TypeError, + _("'style' argument must be gdb.Style or None, not %s."), + Py_TYPE (style_obj)->tp_name); + return nullptr; + } try { @@ -1590,7 +1600,17 @@ gdbpy_write (PyObject *self, PyObject *args, PyObject *kw) break; } - gdb_puts (arg, stream); + if (style_obj == Py_None) + gdb_puts (arg, stream); + else + { + std::optional style + = gdbpy_style_object_to_ui_file_style (style_obj); + if (!style.has_value ()) + return nullptr; + + fputs_styled (arg, style.value (), stream); + } } catch (const gdb_exception &except) { diff --git a/gdb/testsuite/gdb.python/py-color-pagination.exp b/gdb/testsuite/gdb.python/py-color-pagination.exp index 1974dbb4186..6b10f9ca793 100644 --- a/gdb/testsuite/gdb.python/py-color-pagination.exp +++ b/gdb/testsuite/gdb.python/py-color-pagination.exp @@ -131,8 +131,64 @@ proc test_pagination { type mode } { } } +# Run the command 'style-fill-v2' which fills the screen with output and +# triggers the pagination prompt. Check that styling is applied correctly +# to the output. This v2 command is exercising passing a style to +# gdb.write() rather than passing the escape sequence for the style. +proc test_pagination_v2 { } { + set saw_bad_color_handling false + set expected_restore_color "" + set last_color "" + gdb_test_multiple "style-fill-v2" "" { + -re "^style-fill-v2\r\n" { + exp_continue + } + + -re "^(${::any_color}\033\\\[m)(${::any_color})$::str\033\\\[m" { + # After a continuation prompt GDB will restore the previous + # color, and then we immediately switch to a new color. + set restored_color $expect_out(1,string) + if { $restored_color ne $expected_restore_color } { + set saw_bad_color_handling true + } + set last_color $expect_out(2,string) + exp_continue + } + + -re "^(${::any_color})$::str\033\\\[m" { + # This pattern matches printing STR in all cases that are not + # immediately after a pagination prompt. In this case there is + # a single escape sequence to set the color. + set last_color $expect_out(1,string) + exp_continue + } + + -re "^$::pagination_prompt$" { + # After a pagination prompt we expect GDB to restore the last + # color, but this will then be disabled due to a styled + # gdb.write emitting a return to default style escape sequence. + set expected_restore_color "$last_color\033\[m" + + # Send '\n' to view more output. + send_gdb "\n" + exp_continue + } + + -re "^\r\n" { + # The matches the newline sent to the continuation prompt. + exp_continue + } + + -re "^$::gdb_prompt $" { + gdb_assert { !$saw_bad_color_handling } $gdb_test_name + } + } +} + foreach_with_prefix type { color style } { foreach_with_prefix mode { write print } { test_pagination $type $mode } } + +test_pagination_v2 diff --git a/gdb/testsuite/gdb.python/py-color-pagination.py b/gdb/testsuite/gdb.python/py-color-pagination.py index f0252e5bf86..9cdc76cb0ce 100644 --- a/gdb/testsuite/gdb.python/py-color-pagination.py +++ b/gdb/testsuite/gdb.python/py-color-pagination.py @@ -62,5 +62,21 @@ class StyleTester(gdb.Command): write(mode, "\n") +class StyleTester2(gdb.Command): + def __init__(self): + super().__init__("style-fill-v2", gdb.COMMAND_USER) + + def invoke(self, args, from_tty): + str = "<" + "-" * 78 + ">" + for i in range(0, 20): + for color_name in basic_colors: + c = gdb.Color(color_name) + s = gdb.Style(foreground=c) + gdb.write(str, style=s) + + gdb.write("\n") + + ColorTester() StyleTester() +StyleTester2() diff --git a/gdb/testsuite/gdb.python/py-style.exp b/gdb/testsuite/gdb.python/py-style.exp index b2efe9a97e3..491e189774e 100644 --- a/gdb/testsuite/gdb.python/py-style.exp +++ b/gdb/testsuite/gdb.python/py-style.exp @@ -340,3 +340,32 @@ gdb_test_no_output "python input_text = \"a totally different string that is als gdb_test "python print(output_text)" \ "^this is a unique string that is unlikely to appear elsewhere 12345" \ "check the output_text is still valid" + +# Test gdb.write passing in a style. Define a helper function to +# ensure all output is flushed before we return to the prompt. +gdb_test_multiline "create function to call gdb.write then flush" \ + "python" "" \ + "def write_and_flush(*args, **kwargs):" "" \ + " gdb.write(*args, **kwargs)" "" \ + " gdb.write(\"\\n\")" "" \ + " gdb.flush(gdb.STDOUT)" "" \ + "end" "" + +gdb_test "python write_and_flush(\"some text\")" \ + "^some text" "unstyled text, no style passed" + +gdb_test "python write_and_flush(\"some text\", style=None)" \ + "^some text" "unstyled text, pass style as None" + +gdb_test "python write_and_flush(\"some text\", style=filename_style)" \ + "^\033\\\[34;41;2;23;24;27msome text\033\\\[m" \ + "styled output, pass style by keyword" + +gdb_test "python write_and_flush(\"some text\", gdb.STDOUT, filename_style)" \ + "^\033\\\[34;41;2;23;24;27msome text\033\\\[m" \ + "styled output, pass style by position" + +gdb_test "python write_and_flush(\"some text\", style='filename')" \ + [multi_line \ + "Python Exception : 'style' argument must be gdb\\.Style or None, not str\\." \ + "Error occurred in Python: 'style' argument must be gdb\\.Style or None, not str\\."] -- 2.47.1