mirror of
https://github.com/fish-shell/fish-shell
synced 2025-01-14 14:03:58 +00:00
Adopt the new output.rs
This switches output.cpp from C++ to Rust.
This commit is contained in:
parent
8f38e175ce
commit
84b24d5615
10 changed files with 152 additions and 456 deletions
|
@ -56,6 +56,7 @@ fn main() {
|
|||
"src/job_group.rs",
|
||||
"src/kill.rs",
|
||||
"src/null_terminated_array.rs",
|
||||
"src/output.rs",
|
||||
"src/parse_constants.rs",
|
||||
"src/parse_tree.rs",
|
||||
"src/parse_util.rs",
|
||||
|
|
|
@ -492,7 +492,7 @@ fn update_fish_color_support(vars: &EnvStack) {
|
|||
let mut color_support = ColorSupport::NONE;
|
||||
color_support.set(ColorSupport::TERM_256COLOR, supports_256color);
|
||||
color_support.set(ColorSupport::TERM_24BIT, supports_24bit);
|
||||
crate::output::output_set_color_support(color_support);
|
||||
crate::output::set_color_support(color_support);
|
||||
}
|
||||
|
||||
/// Try to initialize the terminfo/curses subsystem using our fallback terminal name. Do not set
|
||||
|
|
|
@ -24,20 +24,20 @@ bitflags! {
|
|||
}
|
||||
}
|
||||
|
||||
/// FFI bits.
|
||||
pub fn output_set_color_support(value: ColorSupport) {
|
||||
extern "C" {
|
||||
pub fn output_set_color_support(value: libc::c_int);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
output_set_color_support(value.bits() as i32);
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether term256 and term24bit are supported.
|
||||
static COLOR_SUPPORT: AtomicU8 = AtomicU8::new(0);
|
||||
|
||||
/// FFI bits.
|
||||
#[no_mangle]
|
||||
extern "C" fn output_get_color_support() -> u8 {
|
||||
COLOR_SUPPORT.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn output_set_color_support(val: u8) {
|
||||
COLOR_SUPPORT.store(val, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Returns true if we think tparm can handle outputting a color index.
|
||||
fn term_supports_color_natively(term: &Term, c: i32) -> bool {
|
||||
#[allow(clippy::int_plus_one)]
|
||||
|
@ -646,3 +646,91 @@ pub fn writembs_check<T: Borrow<Option<CString>>>(
|
|||
FLOG!(error, text);
|
||||
}
|
||||
}
|
||||
|
||||
/// FFI junk.
|
||||
fn stdoutput_ffi() -> &'static mut Outputter {
|
||||
// TODO: this is bogus because it avoids RefCell's check, but is temporary for FFI purposes.
|
||||
unsafe { &mut *Outputter::stdoutput().as_ptr() }
|
||||
}
|
||||
|
||||
/// Make an outputter which outputs to its string.
|
||||
fn make_buffering_outputter_ffi() -> Box<Outputter> {
|
||||
Box::new(Outputter::new_buffering())
|
||||
}
|
||||
|
||||
type RgbColorFFI = crate::ffi::rgb_color_t;
|
||||
use crate::wchar_ffi::AsWstr;
|
||||
impl Outputter {
|
||||
fn set_color_ffi(&mut self, fg: &RgbColorFFI, bg: &RgbColorFFI) {
|
||||
self.set_color(fg.from_ffi(), bg.from_ffi());
|
||||
}
|
||||
|
||||
fn writech_ffi(&mut self, ch: crate::ffi::wchar_t) {
|
||||
self.writech(char::from_u32(ch).expect("Invalid wchar"));
|
||||
}
|
||||
|
||||
// Write a nul-terminated string.
|
||||
// We accept CxxString because it prevents needing to do typecasts at the call site,
|
||||
// as it's unclear what Cxx type corresponds to const char *.
|
||||
// We are unconcerned with interior nul-bytes: none of the termcap sequences contain them
|
||||
// for obvious reasons.
|
||||
fn writembs_ffi(&mut self, mbs: &cxx::CxxString) {
|
||||
let mbs = unsafe { CStr::from_ptr(mbs.as_ptr() as *const std::ffi::c_char) };
|
||||
writembs!(self, Some(mbs.to_owned()));
|
||||
}
|
||||
|
||||
fn writestr_ffi(&mut self, str: crate::ffi::wcharz_t) {
|
||||
self.write_wstr(str.as_wstr());
|
||||
}
|
||||
|
||||
fn write_color_ffi(&mut self, color: &RgbColorFFI, is_fg: bool) -> bool {
|
||||
self.write_color(color.from_ffi(), is_fg)
|
||||
}
|
||||
}
|
||||
|
||||
#[cxx::bridge]
|
||||
mod ffi {
|
||||
extern "C++" {
|
||||
include!("color.h");
|
||||
include!("wutil.h");
|
||||
|
||||
#[cxx_name = "rgb_color_t"]
|
||||
type RgbColorFFI = super::RgbColorFFI;
|
||||
|
||||
type wcharz_t = crate::ffi::wcharz_t;
|
||||
}
|
||||
|
||||
extern "Rust" {
|
||||
#[cxx_name = "outputter_t"]
|
||||
type Outputter;
|
||||
|
||||
#[cxx_name = "make_buffering_outputter"]
|
||||
fn make_buffering_outputter_ffi() -> Box<Outputter>;
|
||||
|
||||
#[cxx_name = "stdoutput"]
|
||||
fn stdoutput_ffi() -> &'static mut Outputter;
|
||||
|
||||
#[cxx_name = "set_color"]
|
||||
fn set_color_ffi(&mut self, fg: &RgbColorFFI, bg: &RgbColorFFI);
|
||||
|
||||
#[cxx_name = "writech"]
|
||||
fn writech_ffi(&mut self, ch: wchar_t);
|
||||
|
||||
#[cxx_name = "writestr"]
|
||||
fn writestr_ffi(&mut self, str: wcharz_t);
|
||||
|
||||
#[cxx_name = "writembs"]
|
||||
fn writembs_ffi(&mut self, mbs: &CxxString);
|
||||
|
||||
#[cxx_name = "write_color"]
|
||||
fn write_color_ffi(&mut self, color: &RgbColorFFI, is_fg: bool) -> bool;
|
||||
|
||||
// These do not need separate FFI variants.
|
||||
fn contents(&self) -> &[u8];
|
||||
fn begin_buffering(&mut self);
|
||||
fn end_buffering(&mut self);
|
||||
|
||||
#[cxx_name = "push_back"]
|
||||
fn push(&mut self, ch: u8);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,7 +67,8 @@ static void print_modifiers(outputter_t &outp, bool bold, bool underline, bool i
|
|||
|
||||
static void print_colors(io_streams_t &streams, std::vector<wcstring> args, bool bold,
|
||||
bool underline, bool italics, bool dim, bool reverse, rgb_color_t bg) {
|
||||
outputter_t outp;
|
||||
rust::Box<outputter_t> outputter = make_buffering_outputter();
|
||||
outputter_t &outp = *outputter;
|
||||
if (args.empty()) args = rgb_color_t::named_color_names();
|
||||
for (const auto &color_name : args) {
|
||||
if (!streams.out_is_redirected && isatty(STDOUT_FILENO)) {
|
||||
|
@ -78,7 +79,7 @@ static void print_colors(io_streams_t &streams, std::vector<wcstring> args, bool
|
|||
outp.write_color(bg, false /* not is_fg */);
|
||||
}
|
||||
}
|
||||
outp.writestr(color_name);
|
||||
outp.writestr(color_name.c_str());
|
||||
if (!bg.is_none()) {
|
||||
// If we have a background, stop it after the color
|
||||
// or it goes to the end of the line and looks ugly.
|
||||
|
@ -87,7 +88,9 @@ static void print_colors(io_streams_t &streams, std::vector<wcstring> args, bool
|
|||
outp.writech(L'\n');
|
||||
} // conveniently, 'normal' is always the last color so we don't need to reset here
|
||||
|
||||
streams.out.append(str2wcstring(outp.contents()));
|
||||
auto contents = outp.contents();
|
||||
streams.out.append(
|
||||
str2wcstring(reinterpret_cast<const char *>(contents.data()), contents.size()));
|
||||
}
|
||||
|
||||
static const wchar_t *const short_options = L":b:hoidrcu";
|
||||
|
@ -210,7 +213,8 @@ maybe_t<int> builtin_set_color(parser_t &parser, io_streams_t &streams, const wc
|
|||
if (cur_term == nullptr || !exit_attribute_mode) {
|
||||
return STATUS_CMD_ERROR;
|
||||
}
|
||||
outputter_t outp;
|
||||
rust::Box<outputter_t> outputter = make_buffering_outputter();
|
||||
outputter_t &outp = *outputter;
|
||||
|
||||
print_modifiers(outp, bold, underline, italics, dim, reverse, bg);
|
||||
|
||||
|
@ -236,7 +240,9 @@ maybe_t<int> builtin_set_color(parser_t &parser, io_streams_t &streams, const wc
|
|||
}
|
||||
|
||||
// Output the collected string.
|
||||
streams.out.append(str2wcstring(outp.contents()));
|
||||
auto contents = outp.contents();
|
||||
streams.out.append(
|
||||
str2wcstring(reinterpret_cast<const char *>(contents.data()), contents.size()));
|
||||
|
||||
return STATUS_CMD_OK;
|
||||
}
|
||||
|
|
|
@ -1296,19 +1296,20 @@ std::string colorize(const wcstring &text, const std::vector<highlight_spec_t> &
|
|||
const environment_t &vars) {
|
||||
assert(colors.size() == text.size());
|
||||
highlight_color_resolver_t rv;
|
||||
outputter_t outp;
|
||||
rust::Box<outputter_t> outp = make_buffering_outputter();
|
||||
|
||||
highlight_spec_t last_color = highlight_role_t::normal;
|
||||
for (size_t i = 0; i < text.size(); i++) {
|
||||
highlight_spec_t color = colors.at(i);
|
||||
if (color != last_color) {
|
||||
outp.set_color(rv.resolve_spec(color, false, vars), rgb_color_t::normal());
|
||||
outp->set_color(rv.resolve_spec(color, false, vars), rgb_color_t::normal());
|
||||
last_color = color;
|
||||
}
|
||||
outp.writech(text.at(i));
|
||||
outp->writech(text.at(i));
|
||||
}
|
||||
outp.set_color(rgb_color_t::normal(), rgb_color_t::normal());
|
||||
return outp.contents();
|
||||
outp->set_color(rgb_color_t::normal(), rgb_color_t::normal());
|
||||
auto contents = outp->contents();
|
||||
return std::string(contents.begin(), contents.end());
|
||||
}
|
||||
|
||||
void highlight_shell(const wcstring &buff, std::vector<highlight_spec_t> &color,
|
||||
|
|
309
src/output.cpp
309
src/output.cpp
|
@ -33,297 +33,9 @@
|
|||
#include "wcstringutil.h"
|
||||
#include "wutil.h" // IWYU pragma: keep
|
||||
|
||||
/// Whether term256 and term24bit are supported.
|
||||
static color_support_t color_support = 0;
|
||||
|
||||
/// Returns true if we think fish_tparm can handle outputting a color index
|
||||
static bool term_supports_color_natively(unsigned int c) {
|
||||
return static_cast<unsigned>(max_colors) >= c + 1;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
void output_set_color_support(color_support_t val) { color_support = val; }
|
||||
color_support_t output_get_color_support() { return color_support; }
|
||||
}
|
||||
|
||||
unsigned char index_for_color(rgb_color_t c) {
|
||||
if (c.is_named() || !(output_get_color_support() & color_support_term256)) {
|
||||
return c.to_name_index();
|
||||
}
|
||||
return c.to_term256_index();
|
||||
}
|
||||
|
||||
static bool write_color_escape(outputter_t &outp, const char *todo, unsigned char idx, bool is_fg) {
|
||||
if (term_supports_color_natively(idx)) {
|
||||
// Use fish_tparm to emit color escape.
|
||||
writembs(outp, fish_tparm(const_cast<char *>(todo), idx));
|
||||
return true;
|
||||
}
|
||||
|
||||
// We are attempting to bypass the term here. Generate the ANSI escape sequence ourself.
|
||||
char buff[16] = "";
|
||||
if (idx < 16) {
|
||||
// this allows the non-bright color to happen instead of no color working at all when a
|
||||
// bright is attempted when only colors 0-7 are supported.
|
||||
//
|
||||
// TODO: enter bold mode in builtin_set_color in the same circumstance- doing that combined
|
||||
// with what we do here, will make the brights actually work for virtual consoles/ancient
|
||||
// emulators.
|
||||
if (max_colors == 8 && idx > 8) idx -= 8;
|
||||
snprintf(buff, sizeof buff, "\x1B[%dm", ((idx > 7) ? 82 : 30) + idx + !is_fg * 10);
|
||||
} else {
|
||||
snprintf(buff, sizeof buff, "\x1B[%d;5;%dm", is_fg ? 38 : 48, idx);
|
||||
}
|
||||
|
||||
outp.writestr(buff);
|
||||
return true;
|
||||
}
|
||||
|
||||
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(outp, set_a_foreground, idx, true);
|
||||
} else if (set_foreground && set_foreground[0]) {
|
||||
return write_color_escape(outp, set_foreground, idx, true);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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(outp, set_a_background, idx, false);
|
||||
} else if (set_background && set_background[0]) {
|
||||
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 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)(*this, idx);
|
||||
}
|
||||
|
||||
// 24 bit! No fish_tparm here, just ANSI escape sequences.
|
||||
// Foreground: ^[38;2;<r>;<g>;<b>m
|
||||
// Background: ^[48;2;<r>;<g>;<b>m
|
||||
color24_t rgb = color.to_color24();
|
||||
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]);
|
||||
writestr(buff);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Sets the fg and bg color. May be called as often as you like, since if the new color is the same
|
||||
/// as the previous, nothing will be written. Negative values for set_color will also be ignored.
|
||||
/// Since the terminfo string this function emits can potentially cause the screen to flicker, the
|
||||
/// function takes care to write as little as possible.
|
||||
///
|
||||
/// Possible values for colors are rgb_color_t colors or special values like rgb_color_t::normal()
|
||||
///
|
||||
/// In order to set the color to normal, three terminfo strings may have to be written.
|
||||
///
|
||||
/// - First a string to set the color, such as set_a_foreground. This is needed because otherwise
|
||||
/// the previous strings colors might be removed as well.
|
||||
///
|
||||
/// - After that we write the exit_attribute_mode string to reset all color attributes.
|
||||
///
|
||||
/// - Lastly we may need to write set_a_background or set_a_foreground to set the other half of the
|
||||
/// color pair to what it should be.
|
||||
///
|
||||
/// \param fg Foreground color.
|
||||
/// \param bg Background color.
|
||||
void outputter_t::set_color(rgb_color_t fg, rgb_color_t bg) {
|
||||
// Test if we have at least basic support for setting fonts, colors and related bits - otherwise
|
||||
// just give up...
|
||||
if (!cur_term || !exit_attribute_mode) return;
|
||||
|
||||
const rgb_color_t normal = rgb_color_t::normal();
|
||||
bool bg_set = false, last_bg_set = false;
|
||||
bool is_bold = fg.is_bold() || bg.is_bold();
|
||||
bool is_underline = fg.is_underline() || bg.is_underline();
|
||||
bool is_italics = fg.is_italics() || bg.is_italics();
|
||||
bool is_dim = fg.is_dim() || bg.is_dim();
|
||||
bool is_reverse = fg.is_reverse() || bg.is_reverse();
|
||||
|
||||
if (fg.is_reset() || bg.is_reset()) {
|
||||
fg = bg = normal;
|
||||
reset_modes();
|
||||
// If we exit attribute mode, we must first set a color, or previously colored text might
|
||||
// lose it's color. Terminals are weird...
|
||||
write_foreground_color(*this, 0);
|
||||
writembs(*this, exit_attribute_mode);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((was_bold && !is_bold) || (was_dim && !is_dim) || (was_reverse && !is_reverse)) {
|
||||
// Only way to exit bold/dim/reverse mode is a reset of all attributes.
|
||||
writembs(*this, exit_attribute_mode);
|
||||
last_color = normal;
|
||||
last_color2 = normal;
|
||||
reset_modes();
|
||||
}
|
||||
|
||||
if (!last_color2.is_special()) {
|
||||
// Background was set.
|
||||
// "Special" here refers to the special "normal", "reset" and "none" colors,
|
||||
// that really jus disable the background.
|
||||
last_bg_set = true;
|
||||
}
|
||||
|
||||
if (!bg.is_special()) {
|
||||
// Background is set.
|
||||
bg_set = true;
|
||||
if (fg == bg)
|
||||
fg = (bg == rgb_color_t::white()) ? rgb_color_t::black() : rgb_color_t::white();
|
||||
}
|
||||
|
||||
if (enter_bold_mode && enter_bold_mode[0] != '\0') {
|
||||
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(*this, enter_bold_mode);
|
||||
}
|
||||
if (!bg_set && last_bg_set) {
|
||||
// Background color changed and is no longer set, so we exit bold mode.
|
||||
writembs(*this, exit_attribute_mode);
|
||||
reset_modes();
|
||||
// We don't know if exit_attribute_mode resets colors, so we set it to something known.
|
||||
if (write_foreground_color(*this, 0)) {
|
||||
last_color = rgb_color_t::black();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (last_color != fg) {
|
||||
if (fg.is_normal()) {
|
||||
write_foreground_color(*this, 0);
|
||||
writembs(*this, exit_attribute_mode);
|
||||
|
||||
last_color2 = rgb_color_t::normal();
|
||||
reset_modes();
|
||||
} else if (!fg.is_special()) {
|
||||
write_color(fg, true /* foreground */);
|
||||
}
|
||||
}
|
||||
|
||||
last_color = fg;
|
||||
|
||||
if (last_color2 != bg) {
|
||||
if (bg.is_normal()) {
|
||||
write_background_color(*this, 0);
|
||||
|
||||
writembs(*this, exit_attribute_mode);
|
||||
if (!last_color.is_normal()) {
|
||||
write_color(last_color, true /* foreground */);
|
||||
}
|
||||
|
||||
reset_modes();
|
||||
last_color2 = bg;
|
||||
} else if (!bg.is_special()) {
|
||||
write_color(bg, false /* not foreground */);
|
||||
last_color2 = bg;
|
||||
}
|
||||
}
|
||||
|
||||
// Lastly, we set bold, underline, italics, dim, and reverse modes correctly.
|
||||
if (is_bold && !was_bold && enter_bold_mode && enter_bold_mode[0] != '\0' && !bg_set) {
|
||||
// The unconst cast is for NetBSD's benefit. DO NOT REMOVE!
|
||||
writembs_nofail(*this, fish_tparm(const_cast<char *>(enter_bold_mode)));
|
||||
was_bold = is_bold;
|
||||
}
|
||||
|
||||
if (was_underline && !is_underline) {
|
||||
writembs_nofail(*this, exit_underline_mode);
|
||||
}
|
||||
|
||||
if (!was_underline && is_underline) {
|
||||
writembs_nofail(*this, enter_underline_mode);
|
||||
}
|
||||
was_underline = is_underline;
|
||||
|
||||
if (was_italics && !is_italics && enter_italics_mode && enter_italics_mode[0] != '\0') {
|
||||
writembs_nofail(*this, exit_italics_mode);
|
||||
was_italics = is_italics;
|
||||
}
|
||||
|
||||
if (!was_italics && is_italics && enter_italics_mode && enter_italics_mode[0] != '\0') {
|
||||
writembs_nofail(*this, enter_italics_mode);
|
||||
was_italics = is_italics;
|
||||
}
|
||||
|
||||
if (is_dim && !was_dim && enter_dim_mode && enter_dim_mode[0] != '\0') {
|
||||
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 && enter_reverse_mode[0] != '\0') {
|
||||
writembs_nofail(*this, enter_reverse_mode);
|
||||
was_reverse = is_reverse;
|
||||
} else if (enter_standout_mode && enter_standout_mode[0] != '\0') {
|
||||
writembs_nofail(*this, enter_standout_mode);
|
||||
was_reverse = is_reverse;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
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);
|
||||
s_tputs_receiver->begin_buffering();
|
||||
// On some systems, tputs takes a char*, on others a const char*.
|
||||
// Like fish_tparm, we just cast it to unconst, that should work everywhere.
|
||||
int res = tputs(const_cast<char *>(str), affcnt, tputs_writer);
|
||||
s_tputs_receiver->end_buffering();
|
||||
return res;
|
||||
}
|
||||
|
||||
void outputter_t::writestr(const wchar_t *str, size_t len) {
|
||||
wcs2string_appending(str, len, &contents_);
|
||||
maybe_flush();
|
||||
}
|
||||
|
||||
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.
|
||||
/// TODO: This is duplicated with Rust.
|
||||
rgb_color_t best_color(const std::vector<rgb_color_t> &candidates, color_support_t support) {
|
||||
if (candidates.empty()) {
|
||||
return rgb_color_t::none();
|
||||
|
@ -355,6 +67,7 @@ rgb_color_t best_color(const std::vector<rgb_color_t> &candidates, color_support
|
|||
/// Return the internal color code representing the specified color.
|
||||
/// TODO: This code should be refactored to enable sharing with builtin_set_color.
|
||||
/// In particular, the argument parsing still isn't fully capable.
|
||||
/// TODO: This is duplicated with Rust.
|
||||
rgb_color_t parse_color(const env_var_t &var, bool is_background) {
|
||||
bool is_bold = false;
|
||||
bool is_underline = false;
|
||||
|
@ -425,17 +138,9 @@ rgb_color_t parse_color(const env_var_t &var, bool is_background) {
|
|||
return result;
|
||||
}
|
||||
|
||||
/// Write specified multibyte string.
|
||||
void writembs_check(outputter_t &outp, const char *mbs, const char *mbs_name, bool critical,
|
||||
const char *file, long line) {
|
||||
if (mbs != nullptr) {
|
||||
outp.term_puts(mbs, 1);
|
||||
} else if (critical) {
|
||||
auto term = env_stack_t::globals().get(L"TERM");
|
||||
const wchar_t *fmt =
|
||||
_(L"Tried to use terminfo string %s on line %ld of %s, which is "
|
||||
L"undefined in terminal of type \"%ls\". Please report this error to %s");
|
||||
FLOG(error, fmt, mbs_name, line, file, term ? term->as_string().c_str() : L"",
|
||||
PACKAGE_BUGREPORT);
|
||||
}
|
||||
void writembs_nofail(outputter_t &outp, const char *str) {
|
||||
assert(str != nullptr && "Null string");
|
||||
outp.writembs(str);
|
||||
}
|
||||
|
||||
void writembs(outputter_t &outp, const char *str) { writembs_nofail(outp, str); }
|
||||
|
|
131
src/output.h
131
src/output.h
|
@ -5,135 +5,32 @@
|
|||
#ifndef FISH_OUTPUT_H
|
||||
#define FISH_OUTPUT_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <cwchar>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "color.h"
|
||||
#include "common.h"
|
||||
#include "fallback.h" // IWYU pragma: keep
|
||||
|
||||
#if INCLUDE_RUST_HEADERS
|
||||
#include "output.rs.h"
|
||||
#else
|
||||
// Hacks to allow us to compile without Rust headers.
|
||||
struct outputter_t;
|
||||
#endif
|
||||
|
||||
class env_var_t;
|
||||
|
||||
class outputter_t {
|
||||
/// Storage for buffered contents.
|
||||
std::string contents_;
|
||||
|
||||
/// Count of how many outstanding begin_buffering() calls there are.
|
||||
uint32_t buffer_count_{0};
|
||||
|
||||
/// fd to output to.
|
||||
int fd_{-1};
|
||||
|
||||
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;
|
||||
|
||||
void reset_modes() {
|
||||
was_bold = false;
|
||||
was_underline = false;
|
||||
was_italics = false;
|
||||
was_dim = false;
|
||||
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 && buffer_count_ == 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 fg, rgb_color_t bg);
|
||||
|
||||
/// Write a wide character to the receiver.
|
||||
void writech(wchar_t ch) { writestr(&ch, 1); }
|
||||
|
||||
/// Write a NUL-terminated wide character string to the receiver.
|
||||
void writestr(const wchar_t *str) { writestr(str, wcslen(str)); }
|
||||
|
||||
/// Write a wide character string to the receiver.
|
||||
void writestr(const wcstring &str) { writestr(str.data(), str.size()); }
|
||||
|
||||
/// Write the given terminfo string to the receiver, like tputs().
|
||||
int term_puts(const char *str, int affcnt);
|
||||
|
||||
/// Write a wide string of the given length.
|
||||
void writestr(const wchar_t *str, size_t len);
|
||||
|
||||
/// 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, std::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
|
||||
/// end_buffering() call.
|
||||
void begin_buffering() {
|
||||
buffer_count_++;
|
||||
assert(buffer_count_ > 0 && "bufferCount_ overflow");
|
||||
}
|
||||
|
||||
/// Balance a begin_buffering() call.
|
||||
void end_buffering() {
|
||||
assert(buffer_count_ > 0 && "bufferCount_ underflow");
|
||||
buffer_count_--;
|
||||
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 &var, bool is_background);
|
||||
|
||||
/// Sets what colors are supported.
|
||||
enum { color_support_term256 = 1 << 0, color_support_term24bit = 1 << 1 };
|
||||
using color_support_t = unsigned int;
|
||||
using color_support_t = uint8_t;
|
||||
extern "C" {
|
||||
color_support_t output_get_color_support();
|
||||
void output_set_color_support(color_support_t val);
|
||||
color_support_t output_get_color_support();
|
||||
void output_set_color_support(color_support_t val);
|
||||
}
|
||||
|
||||
rgb_color_t best_color(const std::vector<rgb_color_t> &candidates, color_support_t support);
|
||||
|
||||
unsigned char index_for_color(rgb_color_t c);
|
||||
// Temporary to support builtin set_color.
|
||||
void writembs_nofail(outputter_t &outp, const char *str);
|
||||
void writembs(outputter_t &outp, const char *str);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1569,7 +1569,7 @@ void reader_write_title(const wcstring &cmd, parser_t &parser, bool reset_cursor
|
|||
ignore_result(write_loop(STDOUT_FILENO, narrow.data(), narrow.size()));
|
||||
}
|
||||
|
||||
outputter_t::stdoutput().set_color(rgb_color_t::reset(), rgb_color_t::reset());
|
||||
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));
|
||||
|
@ -2611,7 +2611,7 @@ static void reader_interactive_init(parser_t &parser) {
|
|||
|
||||
/// Destroy data for interactive use.
|
||||
static void reader_interactive_destroy() {
|
||||
outputter_t::stdoutput().set_color(rgb_color_t::reset(), rgb_color_t::reset());
|
||||
stdoutput().set_color(rgb_color_t::reset(), rgb_color_t::reset());
|
||||
}
|
||||
|
||||
/// Set the specified string as the current buffer.
|
||||
|
@ -2747,7 +2747,7 @@ static eval_res_t reader_run_command(parser_t &parser, const wcstring &cmd) {
|
|||
parser.vars().set_one(L"_", ENV_GLOBAL, ft);
|
||||
}
|
||||
|
||||
outputter_t &outp = outputter_t::stdoutput();
|
||||
outputter_t &outp = stdoutput();
|
||||
reader_write_title(cmd, parser);
|
||||
outp.set_color(rgb_color_t::normal(), rgb_color_t::normal());
|
||||
term_donate();
|
||||
|
@ -2924,7 +2924,7 @@ void reader_change_cursor_selection_mode(cursor_selection_mode_t selection_mode)
|
|||
}
|
||||
|
||||
void reader_change_cursor_selection_mode(uint8_t selection_mode) {
|
||||
reader_change_cursor_selection_mode((cursor_selection_mode_t) selection_mode);
|
||||
reader_change_cursor_selection_mode((cursor_selection_mode_t)selection_mode);
|
||||
}
|
||||
|
||||
static bool check_autosuggestion_enabled(const env_stack_t &vars) {
|
||||
|
@ -3482,7 +3482,7 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
|
|||
}
|
||||
case rl::cancel_commandline: {
|
||||
if (!command_line.empty()) {
|
||||
outputter_t &outp = outputter_t::stdoutput();
|
||||
outputter_t &outp = stdoutput();
|
||||
// Move cursor to the end of the line.
|
||||
update_buff_pos(&command_line, command_line.size());
|
||||
autosuggestion.clear();
|
||||
|
@ -4285,7 +4285,7 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
|
|||
break;
|
||||
}
|
||||
case rl::disable_mouse_tracking: {
|
||||
outputter_t &outp = outputter_t::stdoutput();
|
||||
outputter_t &outp = stdoutput();
|
||||
outp.writestr(L"\x1B[?1000l");
|
||||
break;
|
||||
}
|
||||
|
@ -4624,7 +4624,7 @@ maybe_t<wcstring> reader_data_t::readline(int nchars_or_0) {
|
|||
if (errno == EIO) redirect_tty_output();
|
||||
wperror(L"tcsetattr"); // return to previous mode
|
||||
}
|
||||
outputter_t::stdoutput().set_color(rgb_color_t::reset(), rgb_color_t::reset());
|
||||
stdoutput().set_color(rgb_color_t::reset(), rgb_color_t::reset());
|
||||
}
|
||||
return rls.finished ? maybe_t<wcstring>{command_line.text()} : none();
|
||||
}
|
||||
|
|
|
@ -63,9 +63,7 @@ class scoped_buffer_t : noncopyable_t, nonmovable_t {
|
|||
// Note this is deliberately exported so that init_curses can clear it.
|
||||
layout_cache_t layout_cache_t::shared;
|
||||
|
||||
void screen_clear_layout_cache_ffi() {
|
||||
layout_cache_t::shared.clear();
|
||||
}
|
||||
void screen_clear_layout_cache_ffi() { layout_cache_t::shared.clear(); }
|
||||
|
||||
/// Tests if the specified narrow character sequence is present at the specified position of the
|
||||
/// specified wide character string. All of \c seq must match, but str may be longer than seq.
|
||||
|
@ -584,7 +582,7 @@ void screen_t::move(int new_x, int new_y) {
|
|||
}
|
||||
|
||||
for (i = 0; i < abs(y_steps); i++) {
|
||||
writembs(outp, str);
|
||||
outp.writembs(str);
|
||||
}
|
||||
|
||||
x_steps = new_x - this->actual.cursor.x;
|
||||
|
@ -641,7 +639,7 @@ void screen_t::write_mbs(const char *s) { writembs(this->outp(), s); }
|
|||
|
||||
/// Convert a wide string to a multibyte string and append it to the buffer.
|
||||
void screen_t::write_str(const wchar_t *s) { this->outp().writestr(s); }
|
||||
void screen_t::write_str(const wcstring &s) { this->outp().writestr(s); }
|
||||
void screen_t::write_str(const wcstring &s) { this->outp().writestr(s.c_str()); }
|
||||
|
||||
/// 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
|
||||
|
@ -1333,11 +1331,11 @@ void screen_t::reset_abandoning_line(int screen_width) {
|
|||
|
||||
void screen_force_clear_to_end() {
|
||||
if (clr_eos) {
|
||||
writembs(outputter_t::stdoutput(), clr_eos);
|
||||
writembs(stdoutput(), clr_eos);
|
||||
}
|
||||
}
|
||||
|
||||
screen_t::screen_t() : outp_(outputter_t::stdoutput()) {}
|
||||
screen_t::screen_t() : outp_(stdoutput()) {}
|
||||
|
||||
bool screen_t::cursor_is_wrapped_to_own_line() const {
|
||||
// Note == comparison against the line count is correct: we do not create a line just for the
|
||||
|
|
|
@ -127,7 +127,7 @@ class screen_data_t {
|
|||
bool empty() const { return line_datas.empty(); }
|
||||
};
|
||||
|
||||
class outputter_t;
|
||||
struct outputter_t;
|
||||
|
||||
/// The class representing the current and desired screen contents.
|
||||
class screen_t {
|
||||
|
|
Loading…
Reference in a new issue