Factor color and terminal sequence outputting into outputter_t

Removes some static variables and simplifies the behavior of the tputs
singletone receiver.
This commit is contained in:
ridiculousfish 2018-10-06 13:32:08 -07:00
parent 9715db9434
commit 5134949a14
8 changed files with 268 additions and 276 deletions

View file

@ -42,14 +42,6 @@ static void print_colors(io_streams_t &streams) {
}
}
static std::string builtin_set_color_output;
/// Function we set as the output writer.
static int set_color_builtin_outputter(char c) {
ASSERT_IS_MAIN_THREAD();
builtin_set_color_output.push_back(c);
return 0;
}
static const wchar_t *const short_options = L":b:hvoidrcu";
static const struct woption long_options[] = {{L"background", required_argument, NULL, 'b'},
{L"help", no_argument, NULL, 'h'},
@ -185,62 +177,53 @@ int builtin_set_color(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
if (cur_term == NULL || !exit_attribute_mode) {
return STATUS_CMD_ERROR;
}
// Save old output function so we can restore it.
int (*const saved_writer_func)(char) = output_get_writer();
// Set our output function, which writes to a std::string.
builtin_set_color_output.clear();
output_set_writer(set_color_builtin_outputter);
outputter_t outp;
if (bold && enter_bold_mode) {
writembs_nofail(tparm((char *)enter_bold_mode));
writembs_nofail(outp, tparm((char *)enter_bold_mode));
}
if (underline && enter_underline_mode) {
writembs_nofail(enter_underline_mode);
writembs_nofail(outp, enter_underline_mode);
}
if (italics && enter_italics_mode) {
writembs_nofail(enter_italics_mode);
writembs_nofail(outp, enter_italics_mode);
}
if (dim && enter_dim_mode) {
writembs_nofail(enter_dim_mode);
writembs_nofail(outp, enter_dim_mode);
}
if (reverse && enter_reverse_mode) {
writembs_nofail(enter_reverse_mode);
writembs_nofail(outp, enter_reverse_mode);
} else if (reverse && enter_standout_mode) {
writembs_nofail(enter_standout_mode);
writembs_nofail(outp, enter_standout_mode);
}
if (bgcolor != NULL && bg.is_normal()) {
writembs_nofail(tparm((char *)exit_attribute_mode));
writembs_nofail(outp, tparm((char *)exit_attribute_mode));
}
if (!fg.is_none()) {
if (fg.is_normal() || fg.is_reset()) {
writembs_nofail(tparm((char *)exit_attribute_mode));
} else if (!write_color(fg, true /* is_fg */)) {
// We need to do *something* or the lack of any output
// with a cartesian product here would make "foo" disappear
// on lame terminals:
// $ env TERM=vt100 fish -c 'echo (set_color red)"foo"'
set_color(rgb_color_t::reset(), rgb_color_t::none());
writembs_nofail(outp, tparm((char *)exit_attribute_mode));
} else {
if (!outp.write_color(fg, true /* is_fg */)) {
// We need to do *something* or the lack of any output messes up
// when the cartesian product here would make "foo" disappear:
// $ echo (set_color foo)bar
outp.set_color(rgb_color_t::reset(), rgb_color_t::none());
}
}
}
if (bgcolor != NULL && !bg.is_normal() && !bg.is_reset()) {
write_color(bg, false /* not is_fg */);
outp.write_color(bg, false /* not is_fg */);
}
// Restore saved writer function.
output_set_writer(saved_writer_func);
// Output the collected string.
streams.out.append(str2wcstring(builtin_set_color_output));
builtin_set_color_output.clear();
streams.out.append(str2wcstring(outp.contents()));
return STATUS_CMD_OK;
}

View file

@ -252,37 +252,25 @@ static wcstring prettify(const wcstring &src, bool do_indent) {
return std::move(prettifier.output);
}
// Helper for output_set_writer
static std::string output_receiver;
static int write_to_output_receiver(char c) {
output_receiver.push_back(c);
return 0;
}
/// Given a string and list of colors of the same size, return the string with ANSI escape sequences
/// representing the colors.
static std::string ansi_colorize(const wcstring &text,
const std::vector<highlight_spec_t> &colors) {
assert(colors.size() == text.size());
assert(output_receiver.empty());
int (*saved)(char) = output_get_writer();
output_set_writer(write_to_output_receiver);
outputter_t outp;
const auto &vars = env_stack_t::globals();
highlight_spec_t last_color = highlight_spec_normal;
for (size_t i = 0; i < text.size(); i++) {
highlight_spec_t color = colors.at(i);
if (color != last_color) {
set_color(highlight_get_color(color, false), rgb_color_t::normal());
outp.set_color(highlight_get_color(color, false), rgb_color_t::normal());
last_color = color;
}
writech(text.at(i));
outp.writech(text.at(i));
}
set_color(rgb_color_t::normal(), rgb_color_t::normal());
output_set_writer(saved);
std::string result;
result.swap(output_receiver);
return result;
outp.set_color(rgb_color_t::normal(), rgb_color_t::normal());
return outp.contents();
}
/// Given a string and list of colors of the same size, return the string with HTML span elements

View file

@ -30,25 +30,9 @@
#include "output.h"
#include "wutil.h" // IWYU pragma: keep
static int writeb_internal(char c);
/// The function used for output.
static int (*out)(char c) = writeb_internal; //!OCLINT(unused param)
/// Whether term256 and term24bit are supported.
static color_support_t color_support = 0;
/// Set the function used for writing in move_cursor, writespace and set_color and all other output
/// functions in this library. By default, the write call is used to give completely unbuffered
/// output to stdout.
void output_set_writer(int (*writer)(char)) {
CHECK(writer, );
out = writer;
}
/// Return the current output writer.
int (*output_get_writer())(char) { return out; }
/// Returns true if we think tparm can handle outputting a color index
static bool term_supports_color_natively(unsigned int c) { return (unsigned)max_colors >= c + 1; }
@ -63,10 +47,10 @@ unsigned char index_for_color(rgb_color_t c) {
return c.to_term256_index();
}
static bool write_color_escape(const char *todo, unsigned char idx, bool is_fg) {
static bool write_color_escape(outputter_t &outp, const char *todo, unsigned char idx, bool is_fg) {
if (term_supports_color_natively(idx)) {
// Use tparm to emit color escape.
writembs(tparm((char *)todo, idx));
writembs(outp, tparm((char *)todo, idx));
return true;
}
@ -85,45 +69,46 @@ static bool write_color_escape(const char *todo, unsigned char idx, bool is_fg)
snprintf(buff, sizeof buff, "\x1B[%d;5;%dm", is_fg ? 38 : 48, idx);
}
int (*writer)(char) = output_get_writer();
if (writer) {
for (size_t i = 0; buff[i]; i++) {
writer(buff[i]);
}
}
outp.writestr(buff);
return true;
}
static bool write_foreground_color(unsigned char idx) {
static bool write_foreground_color(outputter_t &outp, unsigned char idx) {
if (!cur_term) return false;
if (set_a_foreground && set_a_foreground[0]) {
return write_color_escape(set_a_foreground, idx, true);
return write_color_escape(outp, set_a_foreground, idx, true);
} else if (set_foreground && set_foreground[0]) {
return write_color_escape(set_foreground, idx, true);
return write_color_escape(outp, set_foreground, idx, true);
}
return false;
}
static bool write_background_color(unsigned char idx) {
static bool write_background_color(outputter_t &outp, unsigned char idx) {
if (!cur_term) return false;
if (set_a_background && set_a_background[0]) {
return write_color_escape(set_a_background, idx, false);
return write_color_escape(outp, set_a_background, idx, false);
} else if (set_background && set_background[0]) {
return write_color_escape(set_background, idx, false);
return write_color_escape(outp, set_background, idx, false);
}
return false;
}
void outputter_t::flush_to(int fd) {
if (fd >= 0 && !contents_.empty()) {
write_loop(fd, contents_.data(), contents_.size());
contents_.clear();
}
}
// Exported for builtin_set_color's usage only.
bool write_color(rgb_color_t color, bool is_fg) {
bool outputter_t::write_color(rgb_color_t color, bool is_fg) {
if (!cur_term) return false;
bool supports_term24bit =
static_cast<bool>(output_get_color_support() & color_support_term24bit);
if (!supports_term24bit || !color.is_rgb()) {
// Indexed or non-24 bit color.
unsigned char idx = index_for_color(color);
return (is_fg ? write_foreground_color : write_background_color)(idx);
return (is_fg ? write_foreground_color : write_background_color)(*this, idx);
}
// 24 bit! No tparm here, just ANSI escape sequences.
@ -133,13 +118,7 @@ bool write_color(rgb_color_t color, bool is_fg) {
char buff[128];
snprintf(buff, sizeof buff, "\x1B[%d;2;%u;%u;%um", is_fg ? 38 : 48, rgb.rgb[0], rgb.rgb[1],
rgb.rgb[2]);
int (*writer)(char) = output_get_writer();
if (writer) {
for (size_t i = 0; buff[i]; i++) {
writer(buff[i]);
}
}
writestr(buff);
return true;
}
@ -162,23 +141,10 @@ bool write_color(rgb_color_t color, bool is_fg) {
///
/// \param c Foreground color.
/// \param c2 Background color.
void set_color(rgb_color_t c, rgb_color_t c2) {
#if 0
wcstring tmp = c.description();
wcstring tmp2 = c2.description();
debug(3, "set_color %ls : %ls", tmp.c_str(), tmp2.c_str());
#endif
ASSERT_IS_MAIN_THREAD();
void outputter_t::set_color(rgb_color_t c, rgb_color_t c2) {
if (!cur_term) return;
const rgb_color_t normal = rgb_color_t::normal();
static rgb_color_t last_color = rgb_color_t::normal();
static rgb_color_t last_color2 = rgb_color_t::normal();
static bool was_bold = false;
static bool was_underline = false;
static bool was_italics = false;
static bool was_dim = false;
static bool was_reverse = false;
bool bg_set = false, last_bg_set = false;
bool is_bold = false;
bool is_underline = false;
@ -216,14 +182,14 @@ void set_color(rgb_color_t c, rgb_color_t c2) {
was_reverse = false;
// If we exit attibute mode, we must first set a color, or previously coloured text might
// lose it's color. Terminals are weird...
write_foreground_color(0);
writembs(exit_attribute_mode);
write_foreground_color(*this, 0);
writembs(*this, exit_attribute_mode);
return;
}
if (was_bold && !is_bold) {
// Only way to exit bold mode is a reset of all attributes.
writembs(exit_attribute_mode);
writembs(*this, exit_attribute_mode);
last_color = normal;
last_color2 = normal;
was_bold = false;
@ -235,7 +201,7 @@ void set_color(rgb_color_t c, rgb_color_t c2) {
if (was_dim && !is_dim) {
// Only way to exit dim mode is a reset of all attributes.
writembs(exit_attribute_mode);
writembs(*this, exit_attribute_mode);
last_color = normal;
last_color2 = normal;
was_bold = false;
@ -247,7 +213,7 @@ void set_color(rgb_color_t c, rgb_color_t c2) {
if (was_reverse && !is_reverse) {
// Only way to exit reverse mode is a reset of all attributes.
writembs(exit_attribute_mode);
writembs(*this, exit_attribute_mode);
last_color = normal;
last_color2 = normal;
was_bold = false;
@ -272,18 +238,18 @@ void set_color(rgb_color_t c, rgb_color_t c2) {
if (bg_set && !last_bg_set) {
// Background color changed and is set, so we enter bold mode to make reading easier.
// This means bold mode is _always_ on when the background color is set.
writembs_nofail(enter_bold_mode);
writembs_nofail(*this, enter_bold_mode);
}
if (!bg_set && last_bg_set) {
// Background color changed and is no longer set, so we exit bold mode.
writembs(exit_attribute_mode);
writembs(*this, exit_attribute_mode);
was_bold = false;
was_underline = false;
was_italics = false;
was_dim = false;
was_reverse = false;
// We don't know if exit_attribute_mode resets colors, so we set it to something known.
if (write_foreground_color(0)) {
if (write_foreground_color(*this, 0)) {
last_color = rgb_color_t::black();
}
}
@ -291,8 +257,8 @@ void set_color(rgb_color_t c, rgb_color_t c2) {
if (last_color != c) {
if (c.is_normal()) {
write_foreground_color(0);
writembs(exit_attribute_mode);
write_foreground_color(*this, 0);
writembs(*this, exit_attribute_mode);
last_color2 = rgb_color_t::normal();
was_bold = false;
@ -309,9 +275,9 @@ void set_color(rgb_color_t c, rgb_color_t c2) {
if (last_color2 != c2) {
if (c2.is_normal()) {
write_background_color(0);
write_background_color(*this, 0);
writembs(exit_attribute_mode);
writembs(*this, exit_attribute_mode);
if (!last_color.is_normal()) {
write_color(last_color, true /* foreground */);
}
@ -330,66 +296,75 @@ void set_color(rgb_color_t c, rgb_color_t c2) {
// Lastly, we set bold, underline, italics, dim, and reverse modes correctly.
if (is_bold && !was_bold && enter_bold_mode && strlen(enter_bold_mode) > 0 && !bg_set) {
writembs_nofail(tparm((char *)enter_bold_mode));
writembs_nofail(*this, tparm(enter_bold_mode));
was_bold = is_bold;
}
if (was_underline && !is_underline) {
writembs_nofail(exit_underline_mode);
writembs_nofail(*this, exit_underline_mode);
}
if (!was_underline && is_underline) {
writembs_nofail(enter_underline_mode);
writembs_nofail(*this, enter_underline_mode);
}
was_underline = is_underline;
if (was_italics && !is_italics && enter_italics_mode && strlen(enter_italics_mode) > 0) {
writembs_nofail(exit_italics_mode);
writembs_nofail(*this, exit_italics_mode);
was_italics = is_italics;
}
if (!was_italics && is_italics && enter_italics_mode && strlen(enter_italics_mode) > 0) {
writembs_nofail(enter_italics_mode);
writembs_nofail(*this, enter_italics_mode);
was_italics = is_italics;
}
if (is_dim && !was_dim && enter_dim_mode && strlen(enter_dim_mode) > 0) {
writembs_nofail(enter_dim_mode);
writembs_nofail(*this, enter_dim_mode);
was_dim = is_dim;
}
if (is_reverse && !was_reverse) {
// Some terms do not have a reverse mode set, so standout mode is a fallback.
if (enter_reverse_mode && strlen(enter_reverse_mode) > 0) {
writembs_nofail(enter_reverse_mode);
writembs_nofail(*this, enter_reverse_mode);
was_reverse = is_reverse;
} else if (enter_standout_mode && strlen(enter_standout_mode) > 0) {
writembs_nofail(enter_standout_mode);
writembs_nofail(*this, enter_standout_mode);
was_reverse = is_reverse;
}
}
}
/// Default output method, simply calls write() on stdout.
static int writeb_internal(char c) { // cppcheck
write_loop(1, &c, 1);
// tputs accepts a function pointer that receives an int only.
// Use the following lock to redirect it to the proper outputter.
// Note we can't use owning_lock because the tputs_writer must access it and owning_lock is not
// recursive.
static std::mutex s_tputs_receiver_lock;
static outputter_t *s_tputs_receiver{nullptr};
static int tputs_writer(tputs_arg_t b) {
ASSERT_IS_LOCKED(s_tputs_receiver_lock);
assert(s_tputs_receiver && "null s_tputs_receiver");
char c = static_cast<char>(b);
s_tputs_receiver->writestr(&c, 1);
return 0;
}
/// This is for writing process notification messages. Has to write to stdout, so clr_eol and such
/// functions will work correctly. Not an issue since this function is only used in interactive mode
/// anyway.
int writeb(tputs_arg_t b) {
out(b);
return 0;
int outputter_t::term_puts(const char *str, int affcnt) {
// Acquire the lock, use a scoped_push to substitute in our receiver, then call tputs. The
// scoped_push will restore it.
scoped_lock locker{s_tputs_receiver_lock};
scoped_push<outputter_t *> push(&s_tputs_receiver, this);
return tputs(str, affcnt, tputs_writer);
}
/// Write a wide character using the output method specified using output_set_writer(). This should
/// only be used when writing characters from user supplied strings. This is needed due to our use
/// of the ENCODE_DIRECT_BASE mechanism to allow the user to specify arbitrary byte values to be
/// output. Such as in a `printf` invocation that includes literal byte values such as `\x1B`.
/// This should not be used for writing non-user supplied characters.
int writech(wint_t ch) {
/// Write a wide character to the outputter. This should only be used when writing characters from
/// user supplied strings. This is needed due to our use of the ENCODE_DIRECT_BASE mechanism to
/// allow the user to specify arbitrary byte values to be output. Such as in a `printf` invocation
/// that includes literal byte values such as `\x1B`. This should not be used for writing non-user
/// supplied characters.
int outputter_t::writech(wint_t ch) {
char buff[MB_LEN_MAX + 1];
size_t len;
@ -408,10 +383,7 @@ int writech(wint_t ch) {
return 1;
}
}
for (size_t i = 0; i < len; i++) {
out(buff[i]);
}
this->writestr(buff, len);
return 0;
}
@ -419,12 +391,12 @@ int writech(wint_t ch) {
/// messages; just use debug() or fwprintf() for that. It should only be used to output user
/// supplied strings that might contain literal bytes; e.g., "\342\224\214" from issue #1894. This
/// is needed because those strings may contain chars specially encoded using ENCODE_DIRECT_BASE.
void writestr(const wchar_t *str) {
CHECK(str, );
void outputter_t::writestr(const wchar_t *str) {
assert(str && "Empty input string");
if (MB_CUR_MAX == 1) {
// Single-byte locale (C/POSIX/ISO-8859).
while (*str) writech(*str++);
this->writestr(str);
return;
}
@ -443,15 +415,16 @@ void writestr(const wchar_t *str) {
buffer = new char[len];
}
wcstombs(buffer, str, len);
// Write the string.
for (char *pos = buffer; *pos; pos++) {
out(*pos);
}
this->writestr(buffer);
if (buffer != static_buffer) delete[] buffer;
}
outputter_t &outputter_t::stdoutput() {
ASSERT_IS_MAIN_THREAD();
static outputter_t res(STDOUT_FILENO);
return res;
}
/// Given a list of rgb_color_t, pick the "best" one, as determined by the color support. Returns
/// rgb_color_t::none() if empty.
rgb_color_t best_color(const std::vector<rgb_color_t> &candidates, color_support_t support) {
@ -548,9 +521,10 @@ rgb_color_t parse_color(const env_var_t &var, bool is_background) {
}
/// Write specified multibyte string.
void writembs_check(const char *mbs, const char *mbs_name, bool critical, const char *file, long line) {
void writembs_check(outputter_t &outp, const char *mbs, const char *mbs_name, bool critical,
const char *file, long line) {
if (mbs != NULL) {
tputs((char *)mbs, 1, &writeb);
outp.term_puts(mbs, 1);
} else if (critical) {
auto term = env_stack_t::globals().get(L"TERM");
const wchar_t *fmt =

View file

@ -14,24 +14,102 @@
class env_var_t;
void set_color(rgb_color_t c, rgb_color_t c2);
class outputter_t {
/// Storage for buffered contents.
std::string contents_;
void writembs_check(const char *mbs, const char *mbs_name, bool critical, const char *file, long line);
#define writembs(mbs) writembs_check((mbs), #mbs, true, __FILE__, __LINE__)
#define writembs_nofail(mbs) writembs_check((mbs), #mbs, false, __FILE__, __LINE__)
/// Count of how many outstanding beginBuffering() calls there are.
uint32_t bufferCount_{0};
int writech(wint_t ch);
/// fd to output to.
int fd_{-1};
void writestr(const wchar_t *str);
rgb_color_t last_color = rgb_color_t::normal();
rgb_color_t last_color2 = rgb_color_t::normal();
bool was_bold = false;
bool was_underline = false;
bool was_italics = false;
bool was_dim = false;
bool was_reverse = false;
/// Construct an outputter which outputs to a given fd.
explicit outputter_t(int fd) : fd_(fd) {}
/// Flush output, if we have a set fd and our buffering count is 0.
void maybe_flush() {
if (fd_ >= 0 && bufferCount_ == 0) flush_to(fd_);
}
public:
/// Construct an outputter which outputs to its string.
outputter_t() = default;
/// Unconditionally write the color string to the output.
bool write_color(rgb_color_t color, bool is_fg);
/// Set the foreground and background color.
void set_color(rgb_color_t c, rgb_color_t c2);
/// Write a wide character to the receiver.
int writech(wint_t ch);
/// Write a NUL-terminated wide character string to the receiver.
void writestr(const wchar_t *str);
/// Write a wide character string to the receiver.
void writestr(const wcstring &str) { writestr(str.c_str()); }
/// Write the given terminfo string to the receiver, like tputs().
int term_puts(const char *str, int affcnt);
/// Write a narrow string of the given length.
void writestr(const char *str, size_t len) {
contents_.append(str, len);
maybe_flush();
}
/// Write a narrow NUL-terminated string.
void writestr(const char *str) { writestr(str, strlen(str)); }
/// Write a narrow character.
void push_back(char c) {
contents_.push_back(c);
maybe_flush();
}
/// \return the "output" contents.
const std::string &contents() const { return contents_; }
/// Output any buffered data to the given \p fd.
void flush_to(int fd);
/// Begins buffering. Output will not be automatically flushed until a corresponding
/// endBuffering() call.
void beginBuffering() {
bufferCount_++;
assert(bufferCount_ > 0 && "bufferCount_ overflow");
}
/// Balance a beginBuffering() call.
void endBuffering() {
assert(bufferCount_ > 0 && "bufferCount_ underflow");
bufferCount_--;
maybe_flush();
}
/// Accesses the singleton stdout outputter.
/// This can only be used from the main thread.
/// This outputter flushes its buffer after every write operation.
static outputter_t &stdoutput();
};
void writembs_check(outputter_t &outp, const char *mbs, const char *mbs_name, bool critical,
const char *file, long line);
#define writembs(outp, mbs) writembs_check((outp), (mbs), #mbs, true, __FILE__, __LINE__)
#define writembs_nofail(outp, mbs) writembs_check((outp), (mbs), #mbs, false, __FILE__, __LINE__)
rgb_color_t parse_color(const env_var_t &val, bool is_background);
int writeb(tputs_arg_t b);
void output_set_writer(int (*writer)(char));
int (*output_get_writer())(char);
/// Sets what colors are supported.
enum { color_support_term256 = 1 << 0, color_support_term24bit = 1 << 1 };
typedef unsigned int color_support_t;
@ -40,7 +118,6 @@ void output_set_color_support(color_support_t support);
rgb_color_t best_color(const std::vector<rgb_color_t> &colors, color_support_t support);
bool write_color(rgb_color_t color, bool is_fg);
unsigned char index_for_color(rgb_color_t c);
#endif

View file

@ -475,12 +475,13 @@ typedef enum { JOB_STOPPED, JOB_ENDED } job_status_t;
static void print_job_status(const job_t *j, job_status_t status) {
const wchar_t *msg = L"Job %d, '%ls' has ended"; // this is the most common status msg
if (status == JOB_STOPPED) msg = L"Job %d, '%ls' has stopped";
fwprintf(stdout, L"\r");
fwprintf(stdout, _(msg), j->job_id, truncate_command(j->command()).c_str());
outputter_t outp;
outp.writestr("\r");
outp.writestr(format_string(_(msg), j->job_id, truncate_command(j->command()).c_str()));
if (clr_eol) outp.term_puts(clr_eol, 1);
outp.writestr(L"\n");
fflush(stdout);
if (clr_eol) tputs(clr_eol, 1, &writeb);
fwprintf(stdout, L"\n");
outp.flush_to(STDOUT_FILENO);
}
void proc_fire_event(const wchar_t *msg, event_type_t type, pid_t pid, int status) {
@ -578,7 +579,7 @@ static bool process_clean_after_marking(bool allow_interactive) {
signal_get_desc(WTERMSIG(p->status)));
}
if (clr_eol) tputs(clr_eol, 1, &writeb);
if (clr_eol) outputter_t::stdoutput().term_puts(clr_eol, 1);
fwprintf(stdout, L"\n");
}
found = false;

View file

@ -458,8 +458,8 @@ static void reader_super_highlight_me_plenty(int highlight_pos_adjust = 0, bool
static bool exit_forced;
/// Give up control of terminal.
static void term_donate() {
set_color(rgb_color_t::normal(), rgb_color_t::normal());
static void term_donate(outputter_t &outp) {
outp.set_color(rgb_color_t::normal(), rgb_color_t::normal());
while (1) {
if (tcsetattr(STDIN_FILENO, TCSANOW, &tty_modes_for_external_cmds) == -1) {
@ -844,7 +844,7 @@ void reader_write_title(const wcstring &cmd, bool reset_cursor_position) {
}
proc_pop_interactive();
set_color(rgb_color_t::reset(), rgb_color_t::reset());
outputter_t::stdoutput().set_color(rgb_color_t::reset(), rgb_color_t::reset());
if (reset_cursor_position && !lst.empty()) {
// Put the cursor back at the beginning of the line (issue #2453).
ignore_result(write(STDOUT_FILENO, "\r", 1));
@ -1844,7 +1844,7 @@ static void reader_interactive_init() {
/// Destroy data for interactive use.
static void reader_interactive_destroy() {
kill_destroy();
set_color(rgb_color_t::reset(), rgb_color_t::reset());
outputter_t::stdoutput().set_color(rgb_color_t::reset(), rgb_color_t::reset());
input_destroy();
}
@ -2031,9 +2031,9 @@ void reader_run_command(parser_t &parser, const wcstring &cmd) {
// For compatibility with fish 2.0's $_, now replaced with `status current-command`
if (!ft.empty()) parser.vars().set_one(L"_", ENV_GLOBAL, ft);
outputter_t &outp = outputter_t::stdoutput();
reader_write_title(cmd);
term_donate();
term_donate(outp);
gettimeofday(&time_before, NULL);
@ -3358,7 +3358,7 @@ const wchar_t *reader_readline(int nchars) {
if (errno == EIO) redirect_tty_output();
wperror(L"tcsetattr"); // return to previous mode
}
set_color(rgb_color_t::reset(), rgb_color_t::reset());
outputter_t::stdoutput().set_color(rgb_color_t::reset(), rgb_color_t::reset());
}
return finished ? data->command_line.text.c_str() : NULL;

View file

@ -52,29 +52,14 @@
static void invalidate_soft_wrap(screen_t *scr);
/// Ugly kludge. The internal buffer used to store output of tputs. Since tputs external function
/// can only take an integer and not a pointer as parameter we need a static storage buffer.
typedef std::vector<char> data_buffer_t;
static data_buffer_t *s_writeb_buffer = 0;
static int s_writeb(char character);
/// Class to temporarily set s_writeb_buffer and the writer function in a scoped way.
/// RAII class to begin and end buffering around stdoutput().
class scoped_buffer_t {
data_buffer_t *const old_buff;
int (*const old_writer)(char);
screen_t &screen_;
public:
explicit scoped_buffer_t(data_buffer_t *buff)
: old_buff(s_writeb_buffer), old_writer(output_get_writer()) {
s_writeb_buffer = buff;
output_set_writer(s_writeb);
}
scoped_buffer_t(screen_t &s) : screen_(s) { screen_.outp().beginBuffering(); }
~scoped_buffer_t() {
s_writeb_buffer = old_buff;
output_set_writer(old_writer);
}
~scoped_buffer_t() { screen_.outp().endBuffering(); }
};
// Singleton of the cached escape sequences seen in prompts and similar strings.
@ -439,12 +424,6 @@ static void s_desired_append_char(screen_t *s, wchar_t b, int c, int indent, siz
}
}
/// The writeb function offered to tputs.
static int s_writeb(char c) {
s_writeb_buffer->push_back(c);
return 0;
}
/// Write the bytes needed to move screen cursor to the specified position to the specified buffer.
/// The actual_cursor field of the specified screen_t will be updated.
///
@ -452,18 +431,20 @@ static int s_writeb(char c) {
/// \param b the buffer to send the output escape codes to
/// \param new_x the new x position
/// \param new_y the new y position
static void s_move(screen_t *s, data_buffer_t *b, int new_x, int new_y) {
static void s_move(screen_t *s, int new_x, int new_y) {
if (s->actual.cursor.x == new_x && s->actual.cursor.y == new_y) return;
const scoped_buffer_t buffering(*s);
// If we are at the end of our window, then either the cursor stuck to the edge or it didn't. We
// don't know! We can fix it up though.
if (s->actual.cursor.x == common_get_width()) {
// Either issue a cr to go back to the beginning of this line, or a nl to go to the
// beginning of the next one, depending on what we think is more efficient.
if (new_y <= s->actual.cursor.y) {
b->push_back('\r');
s->outp().push_back('\r');
} else {
b->push_back('\n');
s->outp().push_back('\n');
s->actual.cursor.y++;
}
// Either way we're not in the first column.
@ -474,7 +455,7 @@ static void s_move(screen_t *s, data_buffer_t *b, int new_x, int new_y) {
int x_steps, y_steps;
const char *str;
scoped_buffer_t scoped_buffer(b);
auto &outp = s->outp();
y_steps = new_y - s->actual.cursor.y;
@ -492,13 +473,13 @@ static void s_move(screen_t *s, data_buffer_t *b, int new_x, int new_y) {
}
for (i = 0; i < abs(y_steps); i++) {
writembs(str);
writembs(outp, str);
}
x_steps = new_x - s->actual.cursor.x;
if (x_steps && new_x == 0) {
b->push_back('\r');
outp.push_back('\r');
x_steps = 0;
}
@ -517,10 +498,10 @@ static void s_move(screen_t *s, data_buffer_t *b, int new_x, int new_y) {
multi_str != NULL && multi_str[0] != '\0' && abs(x_steps) * strlen(str) > strlen(multi_str);
if (use_multi && cur_term) {
char *multi_param = tparm((char *)multi_str, abs(x_steps));
writembs(multi_param);
writembs(outp, multi_param);
} else {
for (i = 0; i < abs(x_steps); i++) {
writembs(str);
writembs(outp, str);
}
}
@ -529,20 +510,19 @@ static void s_move(screen_t *s, data_buffer_t *b, int new_x, int new_y) {
}
/// Set the pen color for the terminal.
static void s_set_color(screen_t *s, data_buffer_t *b, highlight_spec_t c) {
static void s_set_color(screen_t *s, const environment_t &vars, highlight_spec_t c) {
UNUSED(s);
scoped_buffer_t scoped_buffer(b);
unsigned int uc = (unsigned int)c;
set_color(highlight_get_color(uc & 0xffff, false),
highlight_get_color((uc >> 16) & 0xffff, true));
s->outp().set_color(highlight_get_color(uc & 0xfff, false),
highlight_get_color((uc >> 16) & 0xffff, true));
}
/// Convert a wide character to a multibyte string and append it to the buffer.
static void s_write_char(screen_t *s, data_buffer_t *b, wchar_t c) {
scoped_buffer_t scoped_buffer(b);
static void s_write_char(screen_t *s, wchar_t c) {
scoped_buffer_t outp(*s);
s->actual.cursor.x += fish_wcwidth_min_0(c);
writech(c);
s->outp().writech(c);
if (s->actual.cursor.x == s->actual_width && allow_soft_wrap()) {
s->soft_wrap_location.x = 0;
s->soft_wrap_location.y = s->actual.cursor.y + 1;
@ -555,17 +535,11 @@ static void s_write_char(screen_t *s, data_buffer_t *b, wchar_t c) {
}
}
/// Send the specified string through tputs and append the output to the specified buffer.
static void s_write_mbs(data_buffer_t *b, const char *s) {
scoped_buffer_t scoped_buffer(b);
writembs(s);
}
/// Send the specified string through tputs and append the output to the screen's outputter.
static void s_write_mbs(screen_t *screen, const char *s) { writembs(screen->outp(), s); }
/// Convert a wide string to a multibyte string and append it to the buffer.
static void s_write_str(data_buffer_t *b, const wchar_t *s) {
scoped_buffer_t scoped_buffer(b);
writestr(s);
}
static void s_write_str(screen_t *screen, const wchar_t *s) { screen->outp().writestr(s); }
/// Returns the length of the "shared prefix" of the two lines, which is the run of matching text
/// and colors. If the prefix ends on a combining character, do not include the previous character
@ -608,7 +582,8 @@ static void invalidate_soft_wrap(screen_t *scr) { scr->soft_wrap_location = INVA
/// Update the screen to match the desired output.
static void s_update(screen_t *scr, const wcstring &left_prompt, const wcstring &right_prompt) {
// if (test_stuff(scr)) return;
const environment_t &vars = env_stack_t::principal();
const scoped_buffer_t buffering(*scr);
const size_t left_prompt_width =
calc_prompt_layout(left_prompt, cached_layouts).last_line_width;
const size_t right_prompt_width =
@ -620,8 +595,6 @@ static void s_update(screen_t *scr, const wcstring &left_prompt, const wcstring
size_t actual_lines_before_reset = scr->actual_lines_before_reset;
scr->actual_lines_before_reset = 0;
data_buffer_t output;
bool need_clear_lines = scr->need_clear_lines;
bool need_clear_screen = scr->need_clear_screen;
bool has_cleared_screen = false;
@ -630,7 +603,7 @@ static void s_update(screen_t *scr, const wcstring &left_prompt, const wcstring
// Ensure we don't issue a clear screen for the very first output, to avoid issue #402.
if (scr->actual_width != SCREEN_WIDTH_UNINITIALIZED) {
need_clear_screen = true;
s_move(scr, &output, 0, 0);
s_move(scr, 0, 0);
s_reset(scr, screen_reset_current_line_contents);
need_clear_lines = need_clear_lines || scr->need_clear_lines;
@ -647,8 +620,8 @@ static void s_update(screen_t *scr, const wcstring &left_prompt, const wcstring
const size_t lines_with_stuff = maxi(actual_lines_before_reset, scr->actual.line_count());
if (left_prompt != scr->actual_left_prompt) {
s_move(scr, &output, 0, 0);
s_write_str(&output, left_prompt.c_str());
s_move(scr, 0, 0);
s_write_str(scr, left_prompt.c_str());
scr->actual_left_prompt = left_prompt;
scr->actual.cursor.x = (int)left_prompt_width;
}
@ -714,22 +687,22 @@ static void s_update(screen_t *scr, const wcstring &left_prompt, const wcstring
// wrapping.
if (j + 1 == (size_t)screen_width && should_clear_screen_this_line &&
!has_cleared_screen) {
s_move(scr, &output, current_width, (int)i);
s_write_mbs(&output, clr_eos);
s_move(scr, current_width, (int)i);
s_write_mbs(scr, clr_eos);
has_cleared_screen = true;
}
perform_any_impending_soft_wrap(scr, current_width, (int)i);
s_move(scr, &output, current_width, (int)i);
s_set_color(scr, &output, o_line.color_at(j));
s_write_char(scr, &output, o_line.char_at(j));
s_move(scr, current_width, (int)i);
s_set_color(scr, vars, o_line.color_at(j));
s_write_char(scr, o_line.char_at(j));
current_width += fish_wcwidth_min_0(o_line.char_at(j));
}
// Clear the screen if we have not done so yet.
if (should_clear_screen_this_line && !has_cleared_screen) {
s_move(scr, &output, current_width, (int)i);
s_write_mbs(&output, clr_eos);
s_move(scr, current_width, (int)i);
s_write_mbs(scr, clr_eos);
has_cleared_screen = true;
}
@ -750,16 +723,16 @@ static void s_update(screen_t *scr, const wcstring &left_prompt, const wcstring
clear_remainder = prev_width > current_width;
}
if (clear_remainder && clr_eol) {
s_set_color(scr, &output, 0xffffffff);
s_move(scr, &output, current_width, (int)i);
s_write_mbs(&output, clr_eol);
s_set_color(scr, vars, 0xffffffff);
s_move(scr, current_width, (int)i);
s_write_mbs(scr, clr_eol);
}
// Output any rprompt if this is the first line.
if (i == 0 && right_prompt_width > 0) { //!OCLINT(Use early exit/continue)
s_move(scr, &output, (int)(screen_width - right_prompt_width), (int)i);
s_set_color(scr, &output, 0xffffffff);
s_write_str(&output, right_prompt.c_str());
s_move(scr, (int)(screen_width - right_prompt_width), (int)i);
s_set_color(scr, vars, 0xffffffff);
s_write_str(scr, right_prompt.c_str());
scr->actual.cursor.x += right_prompt_width;
// We output in the last column. Some terms (Linux) push the cursor further right, past
@ -770,28 +743,23 @@ static void s_update(screen_t *scr, const wcstring &left_prompt, const wcstring
// wrapped. If so, then a cr will go to the beginning of the following line! So instead
// issue a bunch of "move left" commands to get back onto the line, and then jump to the
// front of it.
s_move(scr, &output, scr->actual.cursor.x - (int)right_prompt_width,
scr->actual.cursor.y);
s_write_str(&output, L"\r");
s_move(scr, scr->actual.cursor.x - (int)right_prompt_width, scr->actual.cursor.y);
s_write_str(scr, L"\r");
scr->actual.cursor.x = 0;
}
}
// Clear remaining lines (if any) if we haven't cleared the screen.
if (!has_cleared_screen && scr->desired.line_count() < lines_with_stuff && clr_eol) {
s_set_color(scr, &output, 0xffffffff);
s_set_color(scr, vars, 0xffffffff);
for (size_t i = scr->desired.line_count(); i < lines_with_stuff; i++) {
s_move(scr, &output, 0, (int)i);
s_write_mbs(&output, clr_eol);
s_move(scr, 0, (int)i);
s_write_mbs(scr, clr_eol);
}
}
s_move(scr, &output, scr->desired.cursor.x, scr->desired.cursor.y);
s_set_color(scr, &output, 0xffffffff);
if (!output.empty()) {
write_loop(STDOUT_FILENO, &output.at(0), output.size());
}
s_move(scr, scr->desired.cursor.x, scr->desired.cursor.y);
s_set_color(scr, vars, 0xffffffff);
// We have now synced our actual screen against our desired screen. Note that this is a big
// assignment!
@ -1206,21 +1174,15 @@ void s_reset(screen_t *s, screen_reset_mode_t mode) {
fstat(2, &s->prev_buff_2);
}
bool screen_force_clear_to_end() {
bool result = false;
void screen_force_clear_to_end() {
if (clr_eos) {
data_buffer_t output;
s_write_mbs(&output, clr_eos);
if (!output.empty()) {
write_loop(STDOUT_FILENO, &output.at(0), output.size());
result = true;
}
writembs(outputter_t::stdoutput(), clr_eos);
}
return result;
}
screen_t::screen_t()
: desired(),
: outp_(outputter_t::stdoutput()),
desired(),
actual(),
actual_left_prompt(),
last_right_prompt_width(),

View file

@ -110,10 +110,14 @@ class screen_data_t {
bool empty() const { return line_datas.empty(); }
};
class outputter_t;
/// The class representing the current and desired screen contents.
class screen_t {
outputter_t &outp_;
public:
/// Constructor
/// Constructor.
screen_t();
/// The internal representation of the desired screen contents.
@ -144,6 +148,9 @@ class screen_t {
/// These status buffers are used to check if any output has occurred other than from fish's
/// main loop, in which case we need to redraw.
struct stat prev_buff_1, prev_buff_2, post_buff_1, post_buff_2;
/// \return the outputter for this screen.
outputter_t &outp() { return outp_; }
};
/// This is the main function for the screen putput library. It is used to define the desired
@ -197,8 +204,8 @@ enum screen_reset_mode_t {
void s_reset(screen_t *s, screen_reset_mode_t mode);
/// Issues an immediate clr_eos, returning if it existed.
bool screen_force_clear_to_end();
/// Issues an immediate clr_eos.
void screen_force_clear_to_end();
/// Returns the length of an escape code. Exposed for testing purposes only.
size_t escape_code_length(const wchar_t *code);