binutils-gdb/gdb/ui-file.c
Tom Tromey 23ac625889 Apply styling to error messages
This patch changes gdb to apply styling to error messages.  The
approach taken is that styling is always applied when forming an
exception's string value; and then if styling is not desired, the
styling is stripped before printing.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=32289
2026-03-23 12:51:57 -06:00

507 lines
10 KiB
C
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* UI_FILE - a generic STDIO like output stream.
Copyright (C) 1999-2026 Free Software Foundation, Inc.
This file is part of GDB.
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/>. */
/* Implement the ``struct ui_file'' object. */
#include "ui-file.h"
#include "gdbsupport/gdb_obstack.h"
#include "gdbsupport/gdb_select.h"
#include "gdbsupport/filestuff.h"
#include "cli-out.h"
#include "cli/cli-style.h"
#include <chrono>
null_file null_stream;
ui_file::ui_file ()
{}
ui_file::~ui_file ()
{}
void
ui_file::printf (const char *format, ...)
{
va_list args;
va_start (args, format);
vprintf (format, args);
va_end (args);
}
void
ui_file::putstr (const char *str, int quoter)
{
while (*str)
printchar (*str++, quoter, false);
}
void
ui_file::putstrn (const char *str, int n, int quoter, bool async_safe)
{
for (int i = 0; i < n; i++)
printchar (str[i], quoter, async_safe);
}
void
ui_file::putc (int c)
{
char copy = (char) c;
write (&copy, 1);
}
void
ui_file::vprintf (const char *format, va_list args)
{
ui_out_flags flags = disallow_ui_out_field;
cli_ui_out (this, flags).vmessage ({}, format, args);
}
/* See ui-file.h. */
void
ui_file::emit_style_escape (const ui_file_style &style)
{
if (can_emit_style_escape ())
this->puts (style.to_ansi ().c_str ());
}
/* See ui-file.h. */
void
ui_file::printchar (int c, int quoter, bool async_safe)
{
char buf[4];
int out = 0;
c &= 0xFF; /* Avoid sign bit follies */
if (c < 0x20 /* Low control chars */
|| (c >= 0x7F && c < 0xA0) /* DEL, High controls */
|| (sevenbit_strings && c >= 0x80))
{ /* high order bit set */
buf[out++] = '\\';
switch (c)
{
case '\n':
buf[out++] = 'n';
break;
case '\b':
buf[out++] = 'b';
break;
case '\t':
buf[out++] = 't';
break;
case '\f':
buf[out++] = 'f';
break;
case '\r':
buf[out++] = 'r';
break;
case '\033':
buf[out++] = 'e';
break;
case '\007':
buf[out++] = 'a';
break;
default:
{
buf[out++] = '0' + ((c >> 6) & 0x7);
buf[out++] = '0' + ((c >> 3) & 0x7);
buf[out++] = '0' + ((c >> 0) & 0x7);
break;
}
}
}
else
{
if (quoter != 0 && (c == '\\' || c == quoter))
buf[out++] = '\\';
buf[out++] = c;
}
if (async_safe)
this->write_async_safe (buf, out);
else
this->write (buf, out);
}
void
null_file::write (const char *buf, long sizeof_buf)
{
/* Discard the request. */
}
void
null_file::puts (const char *)
{
/* Discard the request. */
}
void
null_file::write_async_safe (const char *buf, long sizeof_buf)
{
/* Discard the request. */
}
string_file::~string_file ()
{}
void
string_file::write (const char *buf, long length_buf)
{
m_string.append (buf, length_buf);
}
/* See ui-file.h. */
bool
string_file::term_out ()
{
return m_term_out;
}
/* See ui-file.h. */
bool
string_file::can_emit_style_escape ()
{
return m_term_out && term_cli_styling ();
}
stdio_file::stdio_file (FILE *file, bool close_p)
{
set_stream (file);
m_close_p = close_p;
}
stdio_file::stdio_file ()
: m_file (NULL),
m_fd (-1),
m_close_p (false)
{}
stdio_file::~stdio_file ()
{
if (m_close_p)
fclose (m_file);
}
void
stdio_file::set_stream (FILE *file)
{
m_file = file;
m_fd = fileno (file);
}
bool
stdio_file::open (const char *name, const char *mode)
{
/* Close the previous stream, if we own it. */
if (m_close_p)
{
fclose (m_file);
m_close_p = false;
}
gdb_file_up f = gdb_fopen_cloexec (name, mode);
if (f == NULL)
return false;
set_stream (f.release ());
m_close_p = true;
return true;
}
void
stdio_file::flush ()
{
fflush (m_file);
}
long
stdio_file::read (char *buf, long length_buf)
{
/* Wait until at least one byte of data is available, or we get
interrupted with Control-C. */
{
fd_set readfds;
FD_ZERO (&readfds);
FD_SET (m_fd, &readfds);
if (interruptible_select (m_fd + 1, &readfds, NULL, NULL, NULL) == -1)
return -1;
}
return ::read (m_fd, buf, length_buf);
}
void
stdio_file::write (const char *buf, long length_buf)
{
/* Calling error crashes when we are called from the exception framework. */
if (fwrite (buf, length_buf, 1, m_file))
{
/* Nothing. */
}
}
void
stdio_file::write_async_safe (const char *buf, long length_buf)
{
/* This is written the way it is to avoid a warning from gcc about not using the
result of write (since it can be declared with attribute warn_unused_result).
Alas casting to void doesn't work for this. */
if (::write (m_fd, buf, length_buf))
{
/* Nothing. */
}
}
void
stdio_file::puts (const char *linebuffer)
{
/* This host-dependent function (with implementations in
posix-hdep.c and mingw-hdep.c) is given the opportunity to
process the output first in host-dependent way. If it does, it
should return non-zero, to avoid calling fputs below. */
if (gdb_console_fputs (linebuffer, m_file))
return;
/* Calling error crashes when we are called from the exception framework. */
if (fputs (linebuffer, m_file))
{
/* Nothing. */
}
}
bool
stdio_file::isatty ()
{
return ::isatty (m_fd);
}
/* See ui-file.h. */
bool
stdio_file::can_emit_style_escape ()
{
return (this->isatty ()
&& term_cli_styling ());
}
/* This is the implementation of ui_file method 'write' for stderr.
gdb_stdout is flushed before writing to gdb_stderr. */
void
stderr_file::write (const char *buf, long length_buf)
{
gdb_stdout->flush ();
stdio_file::write (buf, length_buf);
}
/* This is the implementation of ui_file method 'puts' for stderr.
gdb_stdout is flushed before writing to gdb_stderr. */
void
stderr_file::puts (const char *linebuffer)
{
gdb_stdout->flush ();
stdio_file::puts (linebuffer);
}
stderr_file::stderr_file (FILE *stream)
: stdio_file (stream)
{}
/* See ui-file.h. */
template<typename T>
void
escape_buffering_file<T>::write (const char *buf, long length_buf)
{
std::string copy (buf, length_buf);
this->puts (copy.c_str ());
}
/* See ui-file.h. */
template<typename T>
void
escape_buffering_file<T>::puts (const char *buf)
{
std::string local_buffer;
if (!m_buffer.empty ())
{
gdb_assert (m_buffer[0] == '\033');
m_buffer += buf;
/* If we need to keep buffering, we'll handle that below. */
local_buffer = std::move (m_buffer);
buf = local_buffer.c_str ();
}
while (*buf != '\0')
{
const char *esc = strchr (buf, '\033');
if (esc == nullptr)
break;
/* First, write out any prefix. */
if (esc > buf)
{
do_write (buf, esc - buf);
buf = esc;
}
int n_read = 0;
ansi_escape_result seen = examine_ansi_escape (esc, &n_read);
if (seen == ansi_escape_result::INCOMPLETE)
{
/* Start buffering. */
m_buffer = buf;
return;
}
else if (seen == ansi_escape_result::NO_MATCH)
{
/* Just emit the ESC . */
n_read = 1;
}
else
gdb_assert (seen == ansi_escape_result::MATCHED);
do_write (esc, n_read);
buf += n_read;
}
/* If there is any data remaining in BUF, we can flush it now. */
if (*buf != '\0')
do_puts (buf);
}
/* See ui-file.h. */
template<typename T>
void
no_terminal_escape_file<T>::do_puts (const char *buf)
{
while (*buf != '\0')
{
const char *esc = strchr (buf, '\033');
if (esc == nullptr)
break;
int n_read = 0;
if (!skip_ansi_escape (esc, &n_read))
++esc;
/* The immediate superclass is escape_buffering_file, and
calling its "write" would just end up in this function again.
So perform the actual write using the "grandparent"
class. */
T::write (buf, esc - buf);
buf = esc + n_read;
}
if (*buf != '\0')
{
/* See comment above to understand which 'write' is being
called. */
T::write (buf, strlen (buf));
}
}
template<typename T>
void
no_terminal_escape_file<T>::do_write (const char *buf, long len)
{
std::string copy (buf, len);
do_puts (copy.c_str ());
}
void
timestamped_file::write (const char *buf, long len)
{
if (debug_timestamp)
{
/* Print timestamp if previous print ended with a \n. */
if (m_needs_timestamp)
{
using namespace std::chrono;
steady_clock::time_point now = steady_clock::now ();
seconds s = duration_cast<seconds> (now.time_since_epoch ());
microseconds us = duration_cast<microseconds> (now.time_since_epoch () - s);
std::string timestamp = string_printf ("%ld.%06ld ",
(long) s.count (),
(long) us.count ());
m_stream->puts (timestamp.c_str ());
}
/* Print the message. */
m_stream->write (buf, len);
m_needs_timestamp = (len > 0 && buf[len - 1] == '\n');
}
else
m_stream->write (buf, len);
}
void
tab_expansion_file::write (const char *buf, long length_buf)
{
for (long i = 0; i < length_buf; ++i)
{
if (buf[i] == '\t')
{
do
{
m_stream->write (" ", 1);
++m_column;
}
while ((m_column % 8) != 0);
}
else
{
m_stream->write (&buf[i], 1);
if (buf[i] == '\n')
m_column = 0;
else
++m_column;
}
}
}
/* Any necessary instantiations. This is done here to avoid putting
all the logic in a header file, which seems fine in this case
because these classes aren't instantiated in very many ways. */
template class escape_buffering_file<stdio_file>;
template class no_terminal_escape_file<stdio_file>;
template class no_terminal_escape_file<wrapped_file<ui_file *>>;