/** \file output.c Generic output functions */ #include "config.h" #include #include #include #include #include #include #include #ifdef HAVE_SYS_IOCTL_H #include #endif #include #include #include #if HAVE_NCURSES_H #include #else #include #endif #if HAVE_TERM_H #include #elif HAVE_NCURSES_TERM_H #include #endif #include #include #include #include #include #include "fallback.h" #include "util.h" #include "wutil.h" #include "expand.h" #include "common.h" #include "output.h" #include "highlight.h" #include "env.h" /** Number of color names in the col array */ #define FISH_COLORS (sizeof(col)/sizeof(wchar_t *)) static int writeb_internal(char c); /** The function used for output */ static int (*out)(char c) = &writeb_internal; /** Name of terminal */ static wcstring current_term; /* Whether term256 and term24bit are supported */ static color_support_t color_support = 0; void output_set_writer(int (*writer)(char)) { CHECK(writer,); out = writer; } int (*output_get_writer())(char) { return out; } static bool term256_support_is_native(void) { /* Return YES if we think the term256 support is "native" as opposed to forced. */ return max_colors >= 256; } color_support_t output_get_color_support(void) { return color_support; } void output_set_color_support(color_support_t val) { color_support = val; } 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(); } else { return c.to_term256_index(); } } static bool write_color_escape(char *todo, unsigned char idx, bool is_fg) { bool result = false; if (idx < 16 || term256_support_is_native()) { /* Use tparm */ writembs(tparm(todo, idx)); result = true; } else { /* We are attempting to bypass the term here. Generate the ANSI escape sequence ourself. */ char stridx[128]; format_long_safe(stridx, idx); char buff[128] = "\x1b["; strcat(buff, is_fg ? "38;5;" : "48;5;"); strcat(buff, stridx); strcat(buff, "m"); int (*writer)(char) = output_get_writer(); if (writer) { for (size_t i=0; buff[i]; i++) { writer(buff[i]); } } result = true; } return result; } static bool write_foreground_color(unsigned char idx) { if (set_a_foreground && set_a_foreground[0]) { return write_color_escape(set_a_foreground, idx, true); } else if (set_foreground && set_foreground[0]) { return write_color_escape(set_foreground, idx, true); } else { return false; } } static bool write_background_color(unsigned char idx) { if (set_a_background && set_a_background[0]) { return write_color_escape(set_a_background, idx, false); } else if (set_background && set_background[0]) { return write_color_escape(set_background, idx, false); } else { return false; } } void write_color(rgb_color_t color, bool is_fg) { bool supports_term24bit = !! (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); (is_fg ? write_foreground_color : write_background_color)(idx); } else { /* 24 bit! No tparm here, just ANSI escape sequences. Foreground: ^[38;2;;;m Background: ^[48;2;;;m */ color24_t rgb = color.to_color24(); char buff[128]; snprintf(buff, sizeof buff, "\x1b[%u;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]); } } } } void set_color(rgb_color_t c, rgb_color_t c2) { #if 0 wcstring tmp = c.description(); wcstring tmp2 = c2.description(); printf("set_color %ls : %ls\n", tmp.c_str(), tmp2.c_str()); #endif ASSERT_IS_MAIN_THREAD(); 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 int was_bold=0; static int was_underline=0; int bg_set=0, last_bg_set=0; int is_bold = 0; int is_underline = 0; /* Test if we have at least basic support for setting fonts, colors and related bits - otherwise just give up... */ if (!exit_attribute_mode) { return; } is_bold |= c.is_bold(); is_bold |= c2.is_bold(); is_underline |= c.is_underline(); is_underline |= c2.is_underline(); if (c.is_reset() || c2.is_reset()) { c = c2 = normal; was_bold=0; was_underline=0; /* 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); return; } if (was_bold && !is_bold) { /* Only way to exit bold mode is a reset of all attributes. */ writembs(exit_attribute_mode); last_color = normal; last_color2 = normal; was_bold=0; was_underline=0; } if (! last_color2.is_normal() && ! last_color2.is_reset() && ! last_color2.is_ignore()) { /* Background was set */ last_bg_set=1; } if (! c2.is_normal() && ! c2.is_ignore()) { /* Background is set */ bg_set=1; if (c==c2) c = (c2==rgb_color_t::white())?rgb_color_t::black():rgb_color_t::white(); } if ((enter_bold_mode != 0) && (strlen(enter_bold_mode) > 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(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); was_bold=0; was_underline=0; /* We don't know if exit_attribute_mode resets colors, so we set it to something known. */ if (write_foreground_color(0)) { last_color=rgb_color_t::black(); } } } if (last_color != c) { if (c.is_normal()) { write_foreground_color(0); writembs(exit_attribute_mode); last_color2 = rgb_color_t::normal(); was_bold=0; was_underline=0; } else if (! c.is_special()) { write_color(c, true /* foreground */); } } last_color = c; if (last_color2 != c2) { if (c2.is_normal()) { write_background_color(0); writembs(exit_attribute_mode); if (! last_color.is_normal()) { write_color(last_color, true /* foreground */); } was_bold=0; was_underline=0; last_color2 = c2; } else if (! c2.is_special()) { write_color(c2, false /* not foreground */); last_color2 = c2; } } /* Lastly, we set bold mode and underline mode correctly */ if ((enter_bold_mode != 0) && (strlen(enter_bold_mode) > 0) && !bg_set) { if (is_bold && !was_bold) { if (enter_bold_mode) { writembs(tparm(enter_bold_mode)); } } was_bold = is_bold; } if (was_underline && !is_underline) { writembs(exit_underline_mode); } if (!was_underline && is_underline) { writembs(enter_underline_mode); } was_underline = is_underline; } /** Default output method, simply calls write() on stdout */ static int writeb_internal(char c) { write_loop(1, &c, 1); return 0; } int writeb(tputs_arg_t b) { out(b); return 0; } int writech(wint_t ch) { mbstate_t state; size_t i; char buff[MB_LEN_MAX+1]; size_t bytes; if ((ch >= ENCODE_DIRECT_BASE) && (ch < ENCODE_DIRECT_BASE+256)) { buff[0] = ch - ENCODE_DIRECT_BASE; bytes=1; } else { memset(&state, 0, sizeof(state)); bytes= wcrtomb(buff, ch, &state); switch (bytes) { case (size_t)(-1): { return 1; } } } for (i=0; i candidates; wcstring_list_t el; tokenize_variable_array(val, el); for (size_t j=0; j < el.size(); j++) { const wcstring &next = el.at(j); wcstring color_name; if (is_background) { // look for something like "--background=red" const wcstring prefix = L"--background="; if (string_prefixes_string(prefix, next)) { color_name = wcstring(next, prefix.size()); } } else { if (next == L"--bold" || next == L"-o") is_bold = true; else if (next == L"--underline" || next == L"-u") is_underline = true; else color_name = next; } if (! color_name.empty()) { rgb_color_t color = rgb_color_t(color_name); if (! color.is_none()) { candidates.push_back(color); } } } // Pick the best candidate rgb_color_t first_rgb = rgb_color_t::none(), first_named = rgb_color_t::none(); for (size_t i=0; i < candidates.size(); i++) { const rgb_color_t &color = candidates.at(i); if (color.is_rgb() && first_rgb.is_none()) first_rgb = color; if (color.is_named() && first_named.is_none()) first_named = color; } // If we have both RGB and named colors, then prefer rgb if term256 is supported rgb_color_t result; bool has_term256 = !! (output_get_color_support() & color_support_term256); if ((!first_rgb.is_none() && has_term256) || first_named.is_none()) { result = first_rgb; } else { result = first_named; } if (result.is_none()) result = rgb_color_t::normal(); result.set_bold(is_bold); result.set_underline(is_underline); #if 0 wcstring desc = result.description(); printf("Parsed %ls from %ls (%s)\n", desc.c_str(), val.c_str(), is_background ? "background" : "foreground"); #endif return result; } void output_set_term(const wcstring &term) { current_term.assign(term); } const wchar_t *output_get_term() { return current_term.empty() ? L"" : current_term.c_str(); } void writembs_check(char *mbs, const char *mbs_name, const char *file, long line) { if (mbs != NULL) { tputs(mbs, 1, &writeb); } else { debug( 0, _(L"Tried to use terminfo string %s on line %ld of %s, which is undefined in terminal of type \"%ls\". Please report this error to %s"), mbs_name, line, file, output_get_term(), PACKAGE_BUGREPORT); } }