Files
binutils-gdb/gdb/testsuite/lib/gdb-utils.exp
Jannik Hartung 64917bab3c [UI/TUI] Add support for italic and underline ANSI escape sequences
The ANSI escape sequence translation layer in TUI mode strips italic
or underlined text modes silently. You cannot output text formatted
like that using `TuiWindow.write` in Python at the moment.

Parse the ANSI escape sequences for italic and underlined text into
the `ui_file_style` structure and apply it to the TUI window when
applying styles, similar to preserving the bold/dim state already.

A script like this shows italic and underlined text correctly now.
```python
import gdb

class TestTUIWindow:
    _tui_window: gdb.TuiWindow

    def __init__(self, tui_window: gdb.TuiWindow) -> None:
        self._tui_window = tui_window
        self._tui_window.title = "colors test"

    def render(self) -> None:
        self._tui_window.write("""
\x1b[4mThis is underlined.\x1b[24m And normal text.
\x1b[3mThis is italic.\x1b[23m And normal text.
""", True)

gdb.register_window_type("colortest", TestTUIWindow)
```

And launching it with
```
source the_above_script.py
tui new-layout test colortest 1 cmd 1
layout test
```

Approved-By: Tom Tromey <tom@tromey.com>
2025-08-05 13:52:15 +02:00

248 lines
6.2 KiB
Plaintext

# Copyright 2014-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 <http://www.gnu.org/licenses/>.
# Utility procedures, shared between test suite domains.
# A helper procedure to retrieve commands to send to GDB before a program
# is started.
proc gdb_init_commands {} {
set commands ""
if [target_info exists gdb_init_command] {
lappend commands [target_info gdb_init_command]
}
if [target_info exists gdb_init_commands] {
set commands [concat $commands [target_info gdb_init_commands]]
}
return $commands
}
# Given an input string, adds backslashes as needed to create a
# regexp that will match the string.
proc string_to_regexp {str} {
set result $str
regsub -all {[]?*+.|(){}^$\[\\]} $str {\\&} result
return $result
}
# Convenience function that calls string_to_regexp for each arg, and
# joins the results using "\r\n".
proc multi_line_string_to_regexp { args } {
set res [lmap arg $args {string_to_regexp $arg}]
return [multi_line {*}$res]
}
# Given a list of strings, adds backslashes as needed to each string to
# create a regexp that will match the string, and join the result.
proc string_list_to_regexp { args } {
set result ""
foreach arg $args {
set arg [string_to_regexp $arg]
append result $arg
}
return $result
}
# Wrap STR in an ANSI terminal escape sequences -- one to set the
# style to STYLE, and one to reset the style to the default. The
# return value is suitable for use as a regular expression.
# STYLE can either be the payload part of an ANSI terminal sequence,
# or a shorthand for one of the gdb standard styles: "file",
# "function", "variable", "address", etc.
proc style {str style} {
set fg 39
set bg 49
set intensity 22
set italic 23
set underline 24
set reverse 27
switch -exact -- $style {
title { set intensity 1 }
command { set intensity 1 }
file { set fg 32 }
function { set fg 33 }
highlight { set fg 31 }
variable { set fg 36 }
address { set fg 34 }
metadata { set intensity 2 }
version { set fg 35; set intensity 1 }
line-number { set intensity 2 }
none { return $str }
}
return "\033\\\[${fg};${bg};${intensity};${italic};${underline};${reverse}m${str}\033\\\[m"
}
# gdb_get_bp_addr num
#
# Purpose:
# Get address of a particular breakpoint.
#
# Parameter:
# The parameter "num" indicates the number of the breakpoint to get.
# Note that *currently* this parameter must be an integer value.
# E.g., -1 means that we're gonna get the first internal breakpoint;
# 2 means to get the second user-defined breakpoint.
#
# Return:
# First address for a particular breakpoint.
#
# TODO:
# It would be nice if this procedure could accept floating point value.
# E.g., 'gdb_get_bp_addr 1.2' means to get the address of the second
# location of breakpoint #1.
#
proc gdb_get_bp_addr { num } {
gdb_test_multiple "maint info break $num" "find address of specified bp $num" {
-re -wrap ".*(0x\[0-9a-f\]+).*" {
return $expect_out(1,string)
}
}
return ""
}
# Compare the version numbers in L1 to those in L2 using OP, and
# return 1 if the comparison is true. OP can be "<", "<=", ">", ">=",
# or "==".
# It is ok if the lengths of the lists differ, but note that we have
# "{1} < {1 0}" instead of "{1} == {1 0}". See also
# gdb.testsuite/version-compare.exp.
proc version_compare { l1 op l2 } {
switch -exact $op {
"==" -
"<=" -
"<" {}
">=" {
# a >= b => b <= a
set x $l2
set l2 $l1
set l1 $x
set op "<="
}
">" {
# a > b => b < a
set x $l2
set l2 $l1
set l1 $x
set op "<"
}
default { error "unsupported op: $op" }
}
# Handle ops < and ==.
foreach v1 $l1 v2 $l2 {
if {$v1 == ""} {
# This is: "1.2 OP 1.2.1".
if {$op != "=="} {
return 1
}
return 0
}
if {$v2 == ""} {
# This is: "1.2.1 OP 1.2".
return 0
}
if {$v1 == $v2} {
continue
}
return [expr $v1 $op $v2]
}
if {$op == "<"} {
# They are equal.
return 0
}
return 1
}
# Acquire lock file LOCKFILE. Tries forever until the lock file is
# successfully created.
proc lock_file_acquire {lockfile} {
verbose -log "acquiring lock file: $::subdir/${::gdb_test_file_name}.exp"
while {true} {
if {![catch {open $lockfile {WRONLY CREAT EXCL}} rc]} {
set msg "locked by $::subdir/${::gdb_test_file_name}.exp"
verbose -log "lock file: $msg"
# For debugging, put info in the lockfile about who owns
# it.
puts $rc $msg
flush $rc
return [list $rc $lockfile]
}
after 10
}
}
# Release a lock file.
proc lock_file_release {info} {
verbose -log "releasing lock file: $::subdir/${::gdb_test_file_name}.exp"
if {![catch {fconfigure [lindex $info 0]}]} {
if {![catch {
close [lindex $info 0]
file delete -force [lindex $info 1]
} rc]} {
return ""
} else {
return -code error "Error releasing lockfile: '$rc'"
}
} else {
error "invalid lock"
}
}
# Return directory where we keep lock files.
proc lock_dir {} {
if { [info exists ::GDB_LOCK_DIR] } {
# When using check//.
return $::GDB_LOCK_DIR
}
return [make_gdb_parallel_path cache]
}
# Run body under lock LOCK_FILE.
proc with_lock { lock_file body } {
if {[info exists ::GDB_PARALLEL]} {
set lock_file [file join [lock_dir] $lock_file]
set lock_rc [lock_file_acquire $lock_file]
}
set code [catch {uplevel 1 $body} result]
if {[info exists ::GDB_PARALLEL]} {
lock_file_release $lock_rc
}
if {$code == 1} {
global errorInfo errorCode
return -code $code -errorinfo $errorInfo -errorcode $errorCode $result
} else {
return -code $code $result
}
}