From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id mppaBoEoamjv5ywAWB0awg (envelope-from ) for ; Sun, 06 Jul 2025 03:40:49 -0400 Authentication-Results: simark.ca; dkim=pass (1024-bit key; unprotected) header.d=suse.de header.i=@suse.de header.a=rsa-sha256 header.s=susede2_rsa header.b=ISvUMjrV; dkim=pass header.d=suse.de header.i=@suse.de header.a=ed25519-sha256 header.s=susede2_ed25519 header.b=+u5FGkXM; dkim=pass (1024-bit key) header.d=suse.de header.i=@suse.de header.a=rsa-sha256 header.s=susede2_rsa header.b=xCBSSA0t; dkim=neutral header.d=suse.de header.i=@suse.de header.a=ed25519-sha256 header.s=susede2_ed25519 header.b=YDVBbLmO; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id 06A661E11C; Sun, 6 Jul 2025 03:40:49 -0400 (EDT) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on simark.ca X-Spam-Level: X-Spam-Status: No, score=-9.1 required=5.0 tests=ARC_SIGNED,ARC_VALID,BAYES_00, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,MAILING_LIST_MULTI, RCVD_IN_DNSWL_MED,RCVD_IN_VALIDITY_CERTIFIED,RCVD_IN_VALIDITY_RPBL, RCVD_IN_VALIDITY_SAFE 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 10B3A1E089 for ; Sun, 6 Jul 2025 03:40:47 -0400 (EDT) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 711573858D26 for ; Sun, 6 Jul 2025 07:40:46 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 711573858D26 Authentication-Results: sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=suse.de header.i=@suse.de header.a=rsa-sha256 header.s=susede2_rsa header.b=ISvUMjrV; dkim=pass header.d=suse.de header.i=@suse.de header.a=ed25519-sha256 header.s=susede2_ed25519 header.b=+u5FGkXM; dkim=pass (1024-bit key) header.d=suse.de header.i=@suse.de header.a=rsa-sha256 header.s=susede2_rsa header.b=xCBSSA0t; dkim=neutral header.d=suse.de header.i=@suse.de header.a=ed25519-sha256 header.s=susede2_ed25519 header.b=YDVBbLmO Received: from smtp-out2.suse.de (smtp-out2.suse.de [195.135.223.131]) by sourceware.org (Postfix) with ESMTPS id DC85C3858D1E for ; Sun, 6 Jul 2025 07:40:08 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org DC85C3858D1E Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=suse.de Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=suse.de ARC-Filter: OpenARC Filter v1.0.0 sourceware.org DC85C3858D1E Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=195.135.223.131 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1751787609; cv=none; b=CgKv6oeg7wC6wiqbvjt8OyjE1YAwWHaHPT5jfmhqLV1Rivxkce1dCS+7snVcl4+U6MSTYqTO0hkZBm0qaxdBI3nROe0FfYxFiJrp1eb6Ti8maSXMnmJr7ug55VvCFuLT3nK4+kFZWxtt8pceXAXLz5ppBq2IhZr5Y5QqlUlG60U= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1751787609; c=relaxed/simple; bh=Xc+ruCpIVXmjib5ojAZk+cnC+qjaLqDGwen60bNsnnY=; h=DKIM-Signature:DKIM-Signature:DKIM-Signature:DKIM-Signature:From: To:Subject:Date:Message-ID:MIME-Version; b=kDimHLa5EIqy59kqE/owG7RPKzFzgg+ypywS7tB4H6qTmAys7ogqoMxUnjMgLw6DmsZD0ToyZ044eRaUopnBnsKJgd+LsZsF8PLcWBFLIedGJG647vAyuBxiyPGVm8S5jC8J1sBoDNT7tWn1nrsKI7aC49lQIPlnyOSHhODFcgM= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org DC85C3858D1E Received: from imap1.dmz-prg2.suse.org (unknown [10.150.64.97]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by smtp-out2.suse.de (Postfix) with ESMTPS id DD9011F38A for ; Sun, 6 Jul 2025 07:40:04 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_rsa; t=1751787605; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc: mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=mQtpYeTCJcoBsve5AjWQtc8VRRSeM47bQPnI33P3T7g=; b=ISvUMjrVqXrL1MIOdbY+2UppHaKKekLSfx+yvGtJzYINu3cW3r/zVo/i7Y7/dVXxN3pffS FME9f7WUU3RTY7Y7UFAIggMoM/7ZDMXPW9W5gbBJzxFA2uvjiI1ri2ueffNPvyjICrp4If FZVg7G2Pj2YprFaHvMkqPDR7FTRmo2I= DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_ed25519; t=1751787605; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc: mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=mQtpYeTCJcoBsve5AjWQtc8VRRSeM47bQPnI33P3T7g=; b=+u5FGkXMRC/rZfN3SgLVUjctKH7hEDVwtFBHKgurBewpdA4RZ0p8JXBj33qHOcnpLAXiSu lWMUjT5+YyANiyBw== Authentication-Results: smtp-out2.suse.de; none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_rsa; t=1751787604; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc: mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=mQtpYeTCJcoBsve5AjWQtc8VRRSeM47bQPnI33P3T7g=; b=xCBSSA0tcggaSqdgtBPVWFzueUNqpgsuHkK2ErxDqP1b1bMz5LFn5VitAlINFa+YbEYzCJ ORGNPi3jRVK6LJ4nXQei8VU2UUDnc0hkqWBMJVHaCuym/sMZHkIOY4zfp3dsgrogWChHsy MjWTuN/DMuf4K/Bi7+krRQduPEjw7Sw= DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_ed25519; t=1751787604; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc: mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=mQtpYeTCJcoBsve5AjWQtc8VRRSeM47bQPnI33P3T7g=; b=YDVBbLmOdD3rvSingyl6mcclmnAKCae7MyzBAGfF+WBZny0bjNiLNXj9/LkfCzwfvTdD06 a8T6uyyQ6lZoPcCQ== Received: from imap1.dmz-prg2.suse.org (localhost [127.0.0.1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by imap1.dmz-prg2.suse.org (Postfix) with ESMTPS id C321013A7D for ; Sun, 6 Jul 2025 07:40:04 +0000 (UTC) Received: from dovecot-director2.suse.de ([2a07:de40:b281:106:10:150:64:167]) by imap1.dmz-prg2.suse.org with ESMTPSA id +J31LVQoamiDUQAAD6G6ig (envelope-from ) for ; Sun, 06 Jul 2025 07:40:04 +0000 From: Tom de Vries To: gdb-patches@sourceware.org Subject: [PATCH v3] [gdb/tui] Handle unicode chars in prompt Date: Sun, 6 Jul 2025 09:40:03 +0200 Message-ID: <20250706074003.6476-1-tdevries@suse.de> X-Mailer: git-send-email 2.43.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Spamd-Result: default: False [-3.30 / 50.00]; BAYES_HAM(-3.00)[100.00%]; MID_CONTAINS_FROM(1.00)[]; NEURAL_HAM_LONG(-1.00)[-1.000]; NEURAL_HAM_SHORT(-0.20)[-1.000]; MIME_GOOD(-0.10)[text/plain]; FUZZY_RATELIMITED(0.00)[rspamd.com]; ARC_NA(0.00)[]; RCPT_COUNT_ONE(0.00)[1]; RCVD_VIA_SMTP_AUTH(0.00)[]; MIME_TRACE(0.00)[0:+]; RCVD_TLS_ALL(0.00)[]; PREVIOUSLY_DELIVERED(0.00)[gdb-patches@sourceware.org]; FROM_EQ_ENVFROM(0.00)[]; FROM_HAS_DN(0.00)[]; DKIM_SIGNED(0.00)[suse.de:s=susede2_rsa,suse.de:s=susede2_ed25519]; RCVD_COUNT_TWO(0.00)[2]; TO_MATCH_ENVRCPT_ALL(0.00)[]; TO_DN_NONE(0.00)[]; DBL_BLOCKED_OPENRESOLVER(0.00)[gnu.org:url,suse.de:mid] 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 Let's try to set the prompt using a unicode character, say '❯', aka U+276F (heavy right-pointing angle quotation mark ornament). This works fine on an xterm with CLI (with X marking the position of the blinking cursor): ... $ gdb -q -ex "set prompt GDB❯ " GDB❯ X ... but with TUI: ... $ gdb -q -tui -ex "set prompt GDB❯ " ... we get instead: ... GDB GDB X ... We can use the test-case gdb.tui/unicode-prompt.exp to get more details, using tuiterm. With Term::dump_screen we have: ... 16 (gdb) set prompt GDB❯ 17 GDB❯ GDB❯ GDB❯ set prompt (gdb) 18 (gdb) ... and with Term::dump_screen_with_attrs (summarizing using attribute sets and ): ... 16 (gdb) set prompt GDB❯ 17 GDB GDB GDB set prompt (gdb) 18 (gdb) ... where: ... == == ... This explains why we didn't see the unicode char on xterm: it's hidden because the invisible attribute is set. So, there seem to be two problems: - the attributes are incorrect, and - the prompt is repeated a couple of times. In TUI, the prompt is written out by tui_puts_internal, which outputs one byte at a time using waddch, which apparently breaks multi-byte char support. Fix this by detecting multi-byte chars in tui_puts_internal, and printing them using waddnstr. Tested on x86_64-linux and x86_64-freebsd. Reported-By: wuzy01@qq.com PR tui/28800 Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=28800 --- gdb/charset.c | 20 +++++ gdb/charset.h | 7 ++ gdb/testsuite/gdb.tui/unicode-prompt.exp | 71 ++++++++++++++++ gdb/tui/tui-io.c | 100 +++++++++++++++++++---- 4 files changed, 183 insertions(+), 15 deletions(-) create mode 100644 gdb/testsuite/gdb.tui/unicode-prompt.exp diff --git a/gdb/charset.c b/gdb/charset.c index 259362563b2..9c0df83c0d3 100644 --- a/gdb/charset.c +++ b/gdb/charset.c @@ -690,6 +690,26 @@ wchar_iterator::iterate (enum wchar_iterate_result *out_result, return -1; } +/* See charset.h. */ + +void +wchar_iterator::skip (size_t len) +{ + m_input += len; + + gdb_assert (len <= m_bytes); + m_bytes -= len; +} + +/* See charset.h. */ + +void +wchar_iterator::reset (const gdb_byte *input, size_t bytes) +{ + m_input = input; + m_bytes = bytes; +} + struct charset_vector { ~charset_vector () diff --git a/gdb/charset.h b/gdb/charset.h index a0f109da5ee..4d68e61f35a 100644 --- a/gdb/charset.h +++ b/gdb/charset.h @@ -126,6 +126,13 @@ class wchar_iterator int iterate (enum wchar_iterate_result *out_result, gdb_wchar_t **out_chars, const gdb_byte **ptr, size_t *len); + /* Increase the input buffer pointer by LEN bytes. */ + void skip (size_t len); + + /* Reset the input buffer pointer to INPUT and the number of bytes in the + input buffer to BYTES. */ + void reset (const gdb_byte *input, size_t bytes); + private: /* The underlying iconv descriptor. */ diff --git a/gdb/testsuite/gdb.tui/unicode-prompt.exp b/gdb/testsuite/gdb.tui/unicode-prompt.exp new file mode 100644 index 00000000000..ac2b6202c04 --- /dev/null +++ b/gdb/testsuite/gdb.tui/unicode-prompt.exp @@ -0,0 +1,71 @@ +# Copyright 2025 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 . + +require allow_tui_tests + +tuiterm_env + +save_vars { env(LC_ALL) } { + # Override "C" settings from default_gdb_init. + setenv LC_ALL "C.UTF-8" + + Term::clean_restart 24 80 +} + +if {![Term::enter_tui]} { + unsupported "TUI not supported" + return +} + +set unicode_char "\u276F" +set unicode_char_unsupported "❯" + +set color_on [string cat {\033} "\[" "31m"] +set color_off [string cat {\033} "\[" "0m"] + +set prompt "GDB$color_on$unicode_char$color_off " +set prompt_no_color "GDB$unicode_char " +set prompt_no_color_re [string_to_regexp $prompt_no_color] + +if { [ishost *-*-*bsd*] } { + set issue_eol "\r\n" +} else { + set issue_eol "\n" +} + +# Set new prompt. +send_gdb "set prompt $prompt$issue_eol" +# Set old prompt back. +send_gdb "set prompt (gdb) $issue_eol" + +gdb_assert \ + { [Term::wait_for "^${prompt_no_color_re}set prompt $gdb_prompt "] } \ + "prompt with unicode char" + +set line [Term::get_line_with_attrs [expr $Term::_cur_row - 1]] +verbose -log "line with attrs: '$line'" + +set prompt_with_attrs_re "GDB$unicode_char " +set prompt_unsupported_re "GDB$unicode_char_unsupported " + +set test "colored unicode char" +if { [regexp "^${prompt_with_attrs_re}set prompt .*$" $line] } { + pass $test +} elseif { [regexp "^${prompt_unsupported_re}set prompt .*$" $line] } { + # I get this on freebsd: no color, and unicode char not recognized. + unsupported $test +} else { + fail $test +} diff --git a/gdb/tui/tui-io.c b/gdb/tui/tui-io.c index 1b4cc82cce8..5c39e42362a 100644 --- a/gdb/tui/tui-io.c +++ b/gdb/tui/tui-io.c @@ -45,6 +45,7 @@ #include "gdbsupport/unordered_map.h" #include "pager.h" #include "gdbsupport/gdb-checked-static-cast.h" +#include "charset.h" /* This redefines CTRL if it is not already defined, so it must come after terminal state related include files like and @@ -539,30 +540,99 @@ tui_puts_internal (WINDOW *w, const char *string, int *height) char c; int prev_col = 0; bool saw_nl = false; + size_t skip = 0; + wchar_iterator it ((gdb_byte *)string, strlen (string), host_charset (), 1); - while ((c = *string++) != 0) + while (true) { - if (c == '\1' || c == '\2') - { - /* Ignore these, they are readline escape-marking - sequences. */ - continue; - } + bool handled = false; + + /* Get iterator in sync with string. */ + it.skip (skip); + skip = 0; + + /* Detect and handle multibyte chars. */ + { + enum wchar_iterate_result res2; + gdb_wchar_t *dummy1; + const gdb_byte *dummy2; + size_t len; + int res = it.iterate (&res2, &dummy1, &dummy2, &len); + if (res < 0) + { + /* End of string. */ + gdb_assert (res2 == wchar_iterate_eof); + break; + } + + if (res == 0) + { + if (res2 == wchar_iterate_invalid) + { + /* Let single-byte char code handle it. */ + gdb_assert (len == 1); + } + else if (res2 == wchar_iterate_incomplete) + { + /* Iterator has been setup to return end-of-string on next + call to iterate. Make that an advance-by-one instead, and + let single-byte char code handle it. */ + it.reset ((gdb_byte *)(string + 1), strlen (string + 1)); + } + else + gdb_assert_not_reached (""); + } + else + { + /* res > 0. */ + gdb_assert (res2 == wchar_iterate_ok); + if (len > 1) + { + /* Multi-byte char. Handle it. */ + waddnstr (w, string, len); + string += len; + handled = true; + } + else + { + /* Single-byte char. Let single-byte char code handle it. */ + gdb_assert (len == 1); + } + } + } - if (c == '\033') + if (!handled) { - size_t bytes_read = apply_ansi_escape (w, string - 1); - if (bytes_read > 0) + c = *string++; + if (c == '\0') + { + /* End of string. */ + break; + } + + if (c == '\1' || c == '\2') { - string = string + bytes_read - 1; + /* Ignore these, they are readline escape-marking + sequences. */ continue; } - } - if (c == '\n') - saw_nl = true; + if (c == '\033') + { + size_t bytes_read = apply_ansi_escape (w, string - 1); + if (bytes_read > 0) + { + skip = bytes_read - 1; + string += skip; + continue; + } + } + + if (c == '\n') + saw_nl = true; - do_tui_putc (w, c); + do_tui_putc (w, c); + } if (height != nullptr) { base-commit: 87f5e2edca1412326ae40489e2780821093481cb -- 2.43.0