blob: 62598e70ca0095ff7edb8b61d808a3dc4cb40fbe [file] [log] [blame]
/* Copyright (C) 2025, 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/>. */
#ifndef GDB_BUFFERED_STREAMS_H
#define GDB_BUFFERED_STREAMS_H
#include <optional>
#include "ui-file.h"
struct buffered_streams;
class ui_out;
/* Organizes writes to a collection of buffered output streams
so that when flushed, output is written to all streams in
chronological order. */
struct buffer_group
{
buffer_group (ui_out *uiout);
/* Flush all buffered writes to the underlying output streams. */
void flush () const;
/* Record contents of BUF and associate it with STREAM. */
void write (const char *buf, long length_buf, ui_file *stream);
/* Record a wrap_here and associate it with STREAM. */
void wrap_here (int indent, ui_file *stream);
/* Record a call to flush and associate it with STREAM. */
void flush_here (ui_file *stream);
private:
struct output_unit
{
output_unit (ui_file *stream, std::string msg, int wrap_hint = -1,
bool flush = false)
: m_stream (stream), m_msg (msg), m_wrap_hint (wrap_hint),
m_flush (flush)
{}
/* Write contents of this output_unit to the underlying stream. */
void flush () const;
/* Underlying stream for which this output unit will be written to. */
ui_file *m_stream;
/* String to be written to underlying buffer. */
std::string m_msg;
/* Argument to wrap_here. -1 indicates no wrap. Used to call wrap_here
during buffer flush. */
int m_wrap_hint;
/* Indicate that the underlying output stream's flush should be called. */
bool m_flush;
};
/* Output_units to be written to buffered output streams. */
std::vector<output_unit> m_buffered_output;
/* Buffered output streams. */
std::unique_ptr<buffered_streams> m_buffered_streams;
};
/* If FILE is a buffering_file, return its underlying stream. */
extern ui_file *get_unbuffered (ui_file *file);
/* Buffer output to gdb_stdout and gdb_stderr for the duration of FUNC. */
template<typename F, typename... Arg>
void
do_with_buffered_output (F func, ui_out *uiout, Arg... args)
{
buffer_group g (uiout);
try
{
func (uiout, std::forward<Arg> (args)...);
}
catch (gdb_exception &ex)
{
/* Ideally flush would be called in the destructor of buffer_group,
however flushing might cause an exception to be thrown. Catch it
and ensure the first exception propagates. */
try
{
g.flush ();
}
catch (const gdb_exception &)
{
}
throw_exception (std::move (ex));
}
/* Try was successful. Let any further exceptions propagate. */
g.flush ();
}
/* Accumulate writes to an underlying ui_file. Output to the
underlying file is deferred until required. */
struct buffering_file : public ui_file
{
buffering_file (buffer_group *group, ui_file *stream)
: m_group (group),
m_stream (stream)
{ /* Nothing. */ }
/* Return the underlying output stream. */
ui_file *stream () const
{
return m_stream;
}
/* Record the contents of BUF. */
void write (const char *buf, long length_buf) override
{
m_group->write (buf, length_buf, m_stream);
}
/* Record a wrap_here call with argument INDENT. */
void wrap_here (int indent) override
{
m_group->wrap_here (indent, m_stream);
}
/* Return true if the underlying stream is a tty. */
bool isatty () override
{
return m_stream->isatty ();
}
/* Return true if ANSI escapes can be used on the underlying stream. */
bool can_emit_style_escape () override
{
return m_stream->can_emit_style_escape ();
}
void emit_style_escape (const ui_file_style &style) override
{
if (can_emit_style_escape () && style != m_applied_style)
{
m_applied_style = style;
ui_file::emit_style_escape (style);
}
}
/* Flush the underlying output stream. */
void flush () override
{
return m_group->flush_here (m_stream);
}
private:
/* Coordinates buffering across multiple buffering_files. */
buffer_group *m_group;
/* The underlying output stream. */
ui_file *m_stream;
/* The currently applied style. */
ui_file_style m_applied_style;
};
/* Attaches and detaches buffers for each of the gdb_std* streams. */
struct buffered_streams
{
buffered_streams (buffer_group *group, ui_out *uiout);
~buffered_streams ()
{
this->remove_buffers ();
}
/* Remove buffering_files from all underlying streams. */
void remove_buffers ();
private:
/* True if buffers are still attached to each underlying output stream. */
bool m_buffers_in_place;
/* Buffers for each gdb_std* output stream. */
buffering_file m_buffered_stdout;
buffering_file m_buffered_stderr;
buffering_file m_buffered_stdlog;
buffering_file m_buffered_stdtarg;
/* Buffer for current_uiout's output stream. */
std::optional<buffering_file> m_buffered_current_uiout;
/* Additional ui_out being buffered. */
ui_out *m_uiout;
/* Buffer for m_uiout's output stream. */
std::optional<buffering_file> m_buffered_uiout;
};
#endif /* GDB_BUFFERED_STREAMS_H */