From 4bc2f736f2ef34464c1afa1296850c3af014b933 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Eric=20Janouch?= Date: Sat, 10 Sep 2022 15:55:13 +0200 Subject: [PATCH] xC: make terminal attributes abstract And translate them for frontends. This is very long overdue, and a rather significant cleanup. Bump liberty. --- NEWS | 6 +- README.adoc | 32 ++-- liberty | 2 +- xC.c | 499 ++++++++++++++++++++++++++++++---------------------- 4 files changed, 313 insertions(+), 226 deletions(-) diff --git a/NEWS b/NEWS index 59fb4f8..dcf8c54 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,4 @@ -Unreleased +2.0.0 (Unreleased) * xD: implemented WALLOPS, choosing to make it target even non-operators @@ -8,6 +8,10 @@ Unreleased with the exception of editor_command/editor, backlog_helper/pager, and backlog_helper_strip_formatting/pager_strip_formatting + * xC: all attributes.* configuration options have been made abstract in + a subset of the git-config(1) format, and renamed to theme.*, + with the exception of attributes.reset, which has no replacement + * xC: replaced behaviour.save_on_quit with general.autosave * xC: improved pager integration capabilities diff --git a/README.adoc b/README.adoc index 6d61bcc..10b18cd 100644 --- a/README.adoc +++ b/README.adoc @@ -26,12 +26,13 @@ As a unique bonus, you can launch a full text editor from within. xP -- The web frontend for 'xC', making use of its networked relay interface. -So far it's somewhat basic, yet usable. +So far it's a bit rough around the edges, yet fully usable. xF -- The X11 frontend for 'xC', making use of its networked relay interface. -It's currently in development, and hidden behind a CMake option. +This subproject has been put on hold, partly because of its massive overlap +with 'xP', and is hidden behind a CMake option. xD -- @@ -117,8 +118,19 @@ as a `forking` type systemd user service. xP ~~ -Install the Go compiler, and build the server using `make` in its directory, -then run it from within the _public_ subdirectory. +The precondition for running 'xC' frontends is enabling its relay interface: + + /set general.relay_bind = "127.0.0.1:9000" + +To build the web server, you'll need to install the Go compiler, and run `make` +from the _xP_ directory. Then start it from the _public_ subdirectory, +and navigate to the adress you gave it as its first argument--in the following +example, that would be http://localhost:8080[]: + + $ ../xP 127.0.0.1:8080 127.0.0.1:9000 + +For remote use, it's recommended to put 'xP' behind a reverse proxy, with TLS, +and some form of HTTP authentication. Client Certificates ------------------- @@ -165,12 +177,12 @@ properly set-up terminal emulator, it suffices to run: /set general.pager = Press Tab here and change +Gb to +Gb1d /set general.date_change_line = "%a %e %b %Y" /set general.plugin_autoload += "fancy-prompt.lua" - /set attributes.userhost = "\x1b[38;5;109m" - /set attributes.join = "\x1b[38;5;108m" - /set attributes.part = "\x1b[38;5;138m" - /set attributes.external = "\x1b[38;5;248m" - /set attributes.timestamp = "\x1b[48;5;255m\x1b[38;5;250m" - /set attributes.read_marker = "\x1b[38;5;202m" + /set theme.userhost = "109" + /set theme.join = "108" + /set theme.part = "138" + /set theme.external = "248" + /set theme.timestamp = "250 255" + /set theme.read_marker = "202" Configuration profiles ---------------------- diff --git a/liberty b/liberty index f545be7..22a1213 160000 --- a/liberty +++ b/liberty @@ -1 +1 @@ -Subproject commit f545be725df9195a5b5897ad95a0220acf10f148 +Subproject commit 22a121383f73fa7739f324021b6ad0ba6ed3cdb3 diff --git a/xC.c b/xC.c index 1399744..c35c158 100644 --- a/xC.c +++ b/xC.c @@ -19,7 +19,6 @@ // A table of all attributes we use for output #define ATTR_TABLE(XX) \ XX( PROMPT, prompt, Terminal attrs for the prompt ) \ - XX( RESET, reset, String to reset terminal attributes ) \ XX( DATE_CHANGE, date_change, Terminal attrs for date change ) \ XX( READ_MARKER, read_marker, Terminal attrs for the read marker ) \ XX( WARNING, warning, Terminal attrs for warnings ) \ @@ -34,6 +33,7 @@ enum { + ATTR_RESET, #define XX(x, y, z) ATTR_ ## x, ATTR_TABLE (XX) #undef XX @@ -48,6 +48,9 @@ enum #include "config.h" #define PROGRAM_NAME "xC" +// fmemopen +#define _POSIX_C_SOURCE 200809L + #include "common.c" #include "xD-replies.c" #include "xC-proto.c" @@ -1452,6 +1455,58 @@ channel_destroy (struct channel *self) REF_COUNTABLE_METHODS (channel) +// ~~~ Attribute utilities ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +enum +{ + TEXT_BOLD = 1 << 0, + TEXT_ITALIC = 1 << 1, + TEXT_UNDERLINE = 1 << 2, + TEXT_INVERSE = 1 << 3, + TEXT_BLINK = 1 << 4, + TEXT_CROSSED_OUT = 1 << 5, + TEXT_MONOSPACE = 1 << 6 +}; + +// Similar to code in liberty-tui.c. +struct attrs +{ + short fg; ///< Foreground (256-colour cube or -1) + short bg; ///< Background (256-colour cube or -1) + unsigned attrs; ///< TEXT_* mask +}; + +/// Decode attributes in the value using a subset of the git config format, +/// ignoring all errors since it doesn't affect functionality +static struct attrs +attrs_decode (const char *value) +{ + struct strv v = strv_make (); + cstr_split (value, " ", true, &v); + + int colors = 0; + struct attrs attrs = { -1, -1, 0 }; + for (char **it = v.vector; *it; it++) + { + char *end = NULL; + long n = strtol (*it, &end, 10); + if (*it != end && !*end && n >= SHRT_MIN && n <= SHRT_MAX) + { + if (colors == 0) attrs.fg = n; + if (colors == 1) attrs.bg = n; + colors++; + } + else if (!strcmp (*it, "bold")) attrs.attrs |= TEXT_BOLD; + else if (!strcmp (*it, "italic")) attrs.attrs |= TEXT_ITALIC; + else if (!strcmp (*it, "ul")) attrs.attrs |= TEXT_UNDERLINE; + else if (!strcmp (*it, "reverse")) attrs.attrs |= TEXT_INVERSE; + else if (!strcmp (*it, "blink")) attrs.attrs |= TEXT_BLINK; + else if (!strcmp (*it, "strike")) attrs.attrs |= TEXT_CROSSED_OUT; + } + strv_free (&v); + return attrs; +} + // ~~~ Buffers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ enum formatter_item_type @@ -1465,17 +1520,6 @@ enum formatter_item_type FORMATTER_ITEM_IGNORE_ATTR ///< Un/set attribute ignoration }; -enum -{ - TEXT_BOLD = 1 << 0, - TEXT_ITALIC = 1 << 1, - TEXT_UNDERLINE = 1 << 2, - TEXT_INVERSE = 1 << 3, - TEXT_BLINK = 1 << 4, - TEXT_CROSSED_OUT = 1 << 5, - TEXT_MONOSPACE = 1 << 6 -}; - struct formatter_item { enum formatter_item_type type : 16; ///< Type of this item @@ -2116,12 +2160,13 @@ struct completion_hook struct app_context { - char *attrs_defaults[ATTR_COUNT]; ///< Default terminal attributes + /// Default terminal attributes + struct attrs theme_defaults[ATTR_COUNT]; // Configuration: struct config config; ///< Program configuration - char *attrs[ATTR_COUNT]; ///< Terminal attributes + struct attrs theme[ATTR_COUNT]; ///< Terminal attributes bool isolate_buffers; ///< Isolate global/server buffers bool beep_on_highlight; ///< Beep on highlight bool logging; ///< Logging to file enabled @@ -2305,11 +2350,6 @@ app_context_free (struct app_context *self) plugin_destroy (iter); config_free (&self->config); - for (size_t i = 0; i < ATTR_COUNT; i++) - { - free (self->attrs_defaults[i]); - free (self->attrs[i]); - } LIST_FOR_EACH (struct buffer, iter, self->buffers) { @@ -2367,7 +2407,7 @@ on_config_show_all_prefixes_change (struct config_item *item) static void on_config_relay_bind_change (struct config_item *item); static void on_config_backlog_limit_change (struct config_item *item); -static void on_config_attribute_change (struct config_item *item); +static void on_config_theme_change (struct config_item *item); static void on_config_logging_change (struct config_item *item); #define TRIVIAL_BOOLEAN_ON_CHANGE(name) \ @@ -2648,10 +2688,10 @@ static struct config_schema g_config_general[] = {} }; -static struct config_schema g_config_attributes[] = +static struct config_schema g_config_theme[] = { #define XX(x, y, z) { .name = #y, .comment = #z, .type = CONFIG_ITEM_STRING, \ - .on_change = on_config_attribute_change }, + .on_change = on_config_theme_change }, ATTR_TABLE (XX) #undef XX {} @@ -2666,9 +2706,9 @@ load_config_general (struct config_item *subtree, void *user_data) } static void -load_config_attributes (struct config_item *subtree, void *user_data) +load_config_theme (struct config_item *subtree, void *user_data) { - config_schema_apply_to_object (g_config_attributes, subtree, user_data); + config_schema_apply_to_object (g_config_theme, subtree, user_data); } static void @@ -2676,11 +2716,11 @@ register_config_modules (struct app_context *ctx) { struct config *config = &ctx->config; // The servers are loaded later when we can create buffers for them - config_register_module (config, "servers", NULL, NULL); - config_register_module (config, "aliases", NULL, NULL); - config_register_module (config, "plugins", NULL, NULL); - config_register_module (config, "general", load_config_general, ctx); - config_register_module (config, "attributes", load_config_attributes, ctx); + config_register_module (config, "servers", NULL, NULL); + config_register_module (config, "aliases", NULL, NULL); + config_register_module (config, "plugins", NULL, NULL); + config_register_module (config, "general", load_config_general, ctx); + config_register_module (config, "theme", load_config_theme, ctx); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -3114,6 +3154,64 @@ relay_prepare_buffer_activate (struct app_context *ctx, struct buffer *buffer) e->buffer_name = str_from_cstr (buffer->name); } +static union relay_item_data * +relay_translate_formatter (struct app_context *ctx, union relay_item_data *p, + struct formatter_item *i) +{ + // XXX: See attr_printer_decode_color(), this is a footgun. + int16_t c16 = i->color; + int16_t c256 = i->color >> 16; + + unsigned attrs = i->attribute; + switch (i->type) + { + case FORMATTER_ITEM_TEXT: + p->text.text = str_from_cstr (i->text); + (p++)->kind = RELAY_ITEM_TEXT; + break; + case FORMATTER_ITEM_FG_COLOR: + p->fg_color.color = c256 <= 0 ? c16 : c256; + (p++)->kind = RELAY_ITEM_FG_COLOR; + break; + case FORMATTER_ITEM_BG_COLOR: + p->bg_color.color = c256 <= 0 ? c16 : c256; + (p++)->kind = RELAY_ITEM_BG_COLOR; + break; + case FORMATTER_ITEM_ATTR: + (p++)->kind = RELAY_ITEM_RESET; + if ((c256 = ctx->theme[i->attribute].fg) >= 0) + { + p->fg_color.color = c256; + (p++)->kind = RELAY_ITEM_FG_COLOR; + } + if ((c256 = ctx->theme[i->attribute].bg) >= 0) + { + p->bg_color.color = c256; + (p++)->kind = RELAY_ITEM_BG_COLOR; + } + + attrs = ctx->theme[i->attribute].attrs; + // Fall-through + case FORMATTER_ITEM_SIMPLE: + if (attrs & TEXT_BOLD) + (p++)->kind = RELAY_ITEM_FLIP_BOLD; + if (attrs & TEXT_ITALIC) + (p++)->kind = RELAY_ITEM_FLIP_ITALIC; + if (attrs & TEXT_UNDERLINE) + (p++)->kind = RELAY_ITEM_FLIP_UNDERLINE; + if (attrs & TEXT_INVERSE) + (p++)->kind = RELAY_ITEM_FLIP_INVERSE; + if (attrs & TEXT_CROSSED_OUT) + (p++)->kind = RELAY_ITEM_FLIP_CROSSED_OUT; + if (attrs & TEXT_MONOSPACE) + (p++)->kind = RELAY_ITEM_FLIP_MONOSPACE; + break; + default: + break; + } + return p; +} + static void relay_prepare_buffer_line (struct app_context *ctx, struct buffer *buffer, struct buffer_line *line, bool leak_to_active) @@ -3132,49 +3230,10 @@ relay_prepare_buffer_line (struct app_context *ctx, struct buffer *buffer, for (size_t i = 0; line->items[i].type; i++) len++; - // XXX: This way helps xP's JSON conversion, but is super annoying for us. - union relay_item_data *p = e->items = xcalloc (len * 6, sizeof *e->items); + // Beware of the upper bound, currently dominated by FORMATTER_ITEM_ATTR. + union relay_item_data *p = e->items = xcalloc (len * 9, sizeof *e->items); for (struct formatter_item *i = line->items; len--; i++) - { - // XXX: See attr_printer_decode_color(), this is a footgun. - int16_t c16 = i->color; - int16_t c256 = i->color >> 16; - switch (i->type) - { - case FORMATTER_ITEM_TEXT: - p->text.text = str_from_cstr (i->text); - (p++)->kind = RELAY_ITEM_TEXT; - break; - case FORMATTER_ITEM_ATTR: - // For future consideration. - (p++)->kind = RELAY_ITEM_RESET; - break; - case FORMATTER_ITEM_FG_COLOR: - p->fg_color.color = c256 <= 0 ? c16 : c256; - (p++)->kind = RELAY_ITEM_FG_COLOR; - break; - case FORMATTER_ITEM_BG_COLOR: - p->bg_color.color = c256 <= 0 ? c16 : c256; - (p++)->kind = RELAY_ITEM_BG_COLOR; - break; - case FORMATTER_ITEM_SIMPLE: - if (i->attribute & TEXT_BOLD) - (p++)->kind = RELAY_ITEM_FLIP_BOLD; - if (i->attribute & TEXT_ITALIC) - (p++)->kind = RELAY_ITEM_FLIP_ITALIC; - if (i->attribute & TEXT_UNDERLINE) - (p++)->kind = RELAY_ITEM_FLIP_UNDERLINE; - if (i->attribute & TEXT_INVERSE) - (p++)->kind = RELAY_ITEM_FLIP_INVERSE; - if (i->attribute & TEXT_CROSSED_OUT) - (p++)->kind = RELAY_ITEM_FLIP_CROSSED_OUT; - if (i->attribute & TEXT_MONOSPACE) - (p++)->kind = RELAY_ITEM_FLIP_MONOSPACE; - break; - default: - break; - } - } + p = relay_translate_formatter (ctx, p, i); e->items_len = p - e->items; } @@ -3239,123 +3298,6 @@ get_attribute_printer (FILE *stream) return NULL; } -static void -vprint_attributed (struct app_context *ctx, - FILE *stream, intptr_t attribute, const char *fmt, va_list ap) -{ - terminal_printer_fn printer = get_attribute_printer (stream); - if (!attribute) - printer = NULL; - - if (printer) - tputs (ctx->attrs[attribute], 1, printer); - - vfprintf (stream, fmt, ap); - - if (printer) - tputs (ctx->attrs[ATTR_RESET], 1, printer); -} - -static void -print_attributed (struct app_context *ctx, - FILE *stream, intptr_t attribute, const char *fmt, ...) -{ - va_list ap; - va_start (ap, fmt); - vprint_attributed (ctx, stream, attribute, fmt, ap); - va_end (ap); -} - -static void -log_message_attributed (void *user_data, const char *quote, const char *fmt, - va_list ap) -{ - FILE *stream = stderr; - struct app_context *ctx = g_ctx; - - CALL (ctx->input, hide); - - print_attributed (ctx, stream, (intptr_t) user_data, "%s", quote); - vprint_attributed (ctx, stream, (intptr_t) user_data, fmt, ap); - fputs ("\n", stream); - - CALL (ctx->input, show); -} - -static ssize_t -attr_by_name (const char *name) -{ - static const char *table[ATTR_COUNT] = - { -#define XX(x, y, z) [ATTR_ ## x] = #y, - ATTR_TABLE (XX) -#undef XX - }; - - for (size_t i = 0; i < N_ELEMENTS (table); i++) - if (!strcmp (name, table[i])) - return i; - return -1; -} - -static void -on_config_attribute_change (struct config_item *item) -{ - struct app_context *ctx = item->user_data; - ssize_t id = attr_by_name (item->schema->name); - if (id != -1) - { - cstr_set (&ctx->attrs[id], xstrdup (item->type == CONFIG_ITEM_NULL - ? ctx->attrs_defaults[id] - : item->value.string.str)); - } -} - -static void -init_colors (struct app_context *ctx) -{ - bool have_ti = init_terminal (); - char **defaults = ctx->attrs_defaults; - -#define INIT_ATTR(id, ti) defaults[ATTR_ ## id] = xstrdup (have_ti ? (ti) : "") - - INIT_ATTR (PROMPT, enter_bold_mode); - INIT_ATTR (RESET, exit_attribute_mode); - INIT_ATTR (DATE_CHANGE, enter_bold_mode); - INIT_ATTR (READ_MARKER, g_terminal.color_set_fg[COLOR_MAGENTA]); - INIT_ATTR (WARNING, g_terminal.color_set_fg[COLOR_YELLOW]); - INIT_ATTR (ERROR, g_terminal.color_set_fg[COLOR_RED]); - - INIT_ATTR (EXTERNAL, g_terminal.color_set_fg[COLOR_WHITE]); - INIT_ATTR (TIMESTAMP, g_terminal.color_set_fg[COLOR_WHITE]); - INIT_ATTR (ACTION, g_terminal.color_set_fg[COLOR_RED]); - INIT_ATTR (USERHOST, g_terminal.color_set_fg[COLOR_CYAN]); - INIT_ATTR (JOIN, g_terminal.color_set_fg[COLOR_GREEN]); - INIT_ATTR (PART, g_terminal.color_set_fg[COLOR_RED]); - - char *highlight = have_ti ? xstrdup_printf ("%s%s%s", - g_terminal.color_set_fg[COLOR_YELLOW], - g_terminal.color_set_bg[COLOR_MAGENTA], - enter_bold_mode) : NULL; - INIT_ATTR (HIGHLIGHT, highlight); - free (highlight); - -#undef INIT_ATTR - - // This prevents formatters from obtaining an attribute printer function - if (!have_ti) - { - g_terminal.stdout_is_tty = false; - g_terminal.stderr_is_tty = false; - } - - g_log_message_real = log_message_attributed; - - // Apply the default values so that we start with any formatting at all - config_schema_call_changed - (config_item_get (ctx->config.root, "attributes", NULL)); -} - // ~~~ Attribute printer ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // A little tool that tries to make the most of the terminal's capabilities @@ -3365,12 +3307,12 @@ init_colors (struct app_context *ctx) struct attr_printer { - char **attrs; ///< Named attributes + struct attrs *attrs; ///< Named attributes FILE *stream; ///< Output stream bool dirty; ///< Attributes are set }; -#define ATTR_PRINTER_INIT(ctx, stream) { ctx->attrs, stream, true } +#define ATTR_PRINTER_INIT(attrs, stream) { attrs, stream, true } static void attr_printer_filtered_puts (FILE *stream, const char *attr) @@ -3408,22 +3350,11 @@ static void attr_printer_reset (struct attr_printer *self) { if (self->dirty) - attr_printer_tputs (self, self->attrs[ATTR_RESET]); + attr_printer_tputs (self, exit_attribute_mode); self->dirty = false; } -static void -attr_printer_apply_named (struct attr_printer *self, int attribute) -{ - attr_printer_reset (self); - if (attribute != ATTR_RESET) - { - attr_printer_tputs (self, self->attrs[attribute]); - self->dirty = true; - } -} - // NOTE: commonly terminals have: // 8 colours (worst, bright fg often with BOLD, bg sometimes with BLINK) // 16 colours (okayish, we have the full basic range guaranteed) @@ -3551,6 +3482,135 @@ attr_printer_apply (struct attr_printer *self, self->dirty = true; } +static void +attr_printer_apply_named (struct attr_printer *self, int attribute) +{ + attr_printer_reset (self); + if (attribute == ATTR_RESET) + return; + + // See the COLOR_256 macro or attr_printer_decode_color(). + struct attrs *a = &self->attrs[attribute]; + attr_printer_apply (self, a->attrs, + a->fg < 16 ? a->fg : (a->fg << 16 | (-1 & 0xFFFF)), + a->bg < 16 ? a->bg : (a->bg << 16 | (-1 & 0xFFFF))); + self->dirty = true; +} + +// ~~~ Logging redirect ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +static void +vprint_attributed (struct app_context *ctx, + FILE *stream, intptr_t attribute, const char *fmt, va_list ap) +{ + terminal_printer_fn printer = get_attribute_printer (stream); + if (!attribute) + printer = NULL; + + struct attr_printer state = ATTR_PRINTER_INIT (ctx->theme, stream); + if (printer) + attr_printer_apply_named (&state, attribute); + + vfprintf (stream, fmt, ap); + + if (printer) + attr_printer_reset (&state); +} + +static void +print_attributed (struct app_context *ctx, + FILE *stream, intptr_t attribute, const char *fmt, ...) +{ + va_list ap; + va_start (ap, fmt); + vprint_attributed (ctx, stream, attribute, fmt, ap); + va_end (ap); +} + +static void +log_message_attributed (void *user_data, const char *quote, const char *fmt, + va_list ap) +{ + FILE *stream = stderr; + struct app_context *ctx = g_ctx; + + CALL (ctx->input, hide); + + print_attributed (ctx, stream, (intptr_t) user_data, "%s", quote); + vprint_attributed (ctx, stream, (intptr_t) user_data, fmt, ap); + fputs ("\n", stream); + + CALL (ctx->input, show); +} + +// ~~~ Theme ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +static ssize_t +attr_by_name (const char *name) +{ + static const char *table[ATTR_COUNT] = + { + NULL, +#define XX(x, y, z) [ATTR_ ## x] = #y, + ATTR_TABLE (XX) +#undef XX + }; + + for (size_t i = 1; i < N_ELEMENTS (table); i++) + if (!strcmp (name, table[i])) + return i; + return -1; +} + +static void +on_config_theme_change (struct config_item *item) +{ + struct app_context *ctx = item->user_data; + ssize_t id = attr_by_name (item->schema->name); + if (id != -1) + { + // TODO: There should be a validator. + ctx->theme[id] = item->type == CONFIG_ITEM_NULL + ? ctx->theme_defaults[id] + : attrs_decode (item->value.string.str); + } +} + +static void +init_colors (struct app_context *ctx) +{ + bool have_ti = init_terminal (); + +#define INIT_ATTR(id, ...) ctx->theme[ATTR_ ## id] = \ + ctx->theme_defaults[ATTR_ ## id] = (struct attrs) { __VA_ARGS__ } + + INIT_ATTR (PROMPT, -1, -1, TEXT_BOLD); + INIT_ATTR (RESET, -1, -1); + INIT_ATTR (DATE_CHANGE, -1, -1, TEXT_BOLD); + INIT_ATTR (READ_MARKER, COLOR_MAGENTA, -1); + INIT_ATTR (WARNING, COLOR_YELLOW, -1); + INIT_ATTR (ERROR, COLOR_RED, -1); + + INIT_ATTR (EXTERNAL, COLOR_WHITE, -1); + INIT_ATTR (TIMESTAMP, COLOR_WHITE, -1); + INIT_ATTR (HIGHLIGHT, COLOR_BRIGHT (YELLOW), COLOR_MAGENTA, TEXT_BOLD); + INIT_ATTR (ACTION, COLOR_RED, -1); + INIT_ATTR (USERHOST, COLOR_CYAN, -1); + INIT_ATTR (JOIN, COLOR_GREEN, -1); + INIT_ATTR (PART, COLOR_RED, -1); + +#undef INIT_ATTR + + // This prevents formatters from obtaining an attribute printer function + if (!have_ti) + { + g_terminal.stdout_is_tty = false; + g_terminal.stderr_is_tty = false; + } + + g_log_message_real = log_message_attributed; +} + // --- Helpers ----------------------------------------------------------------- static int @@ -4413,7 +4473,7 @@ formatter_flush (struct formatter *self, FILE *stream, int flush_opts) if (self->ctx->word_wrapping && !(flush_opts & FLUSH_OPT_NOWRAP)) line = line_wrap (line, g_terminal.columns); - struct attr_printer state = ATTR_PRINTER_INIT (self->ctx, stream); + struct attr_printer state = ATTR_PRINTER_INIT (self->ctx->theme, stream); struct line_char_attrs attrs = {}; // Won't compare equal to anything LIST_FOR_EACH (struct line_char, c, line) { @@ -4425,10 +4485,10 @@ formatter_flush (struct formatter *self, FILE *stream, int flush_opts) formatter_putc (NULL, stream); attrs = c->attrs; - if (attrs.named != -1) - attr_printer_apply_named (&state, attrs.named); - else + if (attrs.named == -1) attr_printer_apply (&state, attrs.text, attrs.fg, attrs.bg); + else + attr_printer_apply_named (&state, attrs.named); } formatter_putc (c, stream); @@ -6820,17 +6880,28 @@ on_refresh_prompt (struct app_context *ctx) char *localized = iconv_xstrdup (ctx->term_from_utf8, prompt.str, -1, NULL); str_free (&prompt); + // XXX: to be completely correct, we should use tputs, but we cannot if (have_attributes) { - // XXX: to be completely correct, we should use tputs, but we cannot - input_maybe_set_prompt (ctx->input, xstrdup_printf ("%c%s%c%s%c%s%c%s", - INPUT_START_IGNORE, ctx->attrs[ATTR_PROMPT], - INPUT_END_IGNORE, - localized, - INPUT_START_IGNORE, ctx->attrs[ATTR_RESET], - INPUT_END_IGNORE, - attributed_suffix)); + char buf[16384] = ""; + FILE *memfp = fmemopen (buf, sizeof buf - 1, "wb"); + struct attr_printer state = { ctx->theme, memfp, false }; + + fputc (INPUT_START_IGNORE, memfp); + attr_printer_apply_named (&state, ATTR_PROMPT); + fputc (INPUT_END_IGNORE, memfp); + + fputs (localized, memfp); free (localized); + + fputc (INPUT_START_IGNORE, memfp); + attr_printer_reset (&state); + fputc (INPUT_END_IGNORE, memfp); + + fputs (attributed_suffix, memfp); + + fclose (memfp); + input_maybe_set_prompt (ctx->input, xstrdup (buf)); } else input_maybe_set_prompt (ctx->input, localized);