xC: make terminal attributes abstract

And translate them for frontends.

This is very long overdue, and a rather significant cleanup.

Bump liberty.
This commit is contained in:
Přemysl Eric Janouch 2022-09-10 15:55:13 +02:00
parent add670212f
commit 4bc2f736f2
Signed by: p
GPG Key ID: A0420B94F92B9493
4 changed files with 313 additions and 226 deletions

6
NEWS
View File

@ -1,4 +1,4 @@
Unreleased 2.0.0 (Unreleased)
* xD: implemented WALLOPS, choosing to make it target even non-operators * 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, with the exception of editor_command/editor, backlog_helper/pager,
and backlog_helper_strip_formatting/pager_strip_formatting 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: replaced behaviour.save_on_quit with general.autosave
* xC: improved pager integration capabilities * xC: improved pager integration capabilities

View File

@ -26,12 +26,13 @@ As a unique bonus, you can launch a full text editor from within.
xP xP
-- --
The web frontend for 'xC', making use of its networked relay interface. 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 xF
-- --
The X11 frontend for 'xC', making use of its networked relay interface. 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 xD
-- --
@ -117,8 +118,19 @@ as a `forking` type systemd user service.
xP xP
~~ ~~
Install the Go compiler, and build the server using `make` in its directory, The precondition for running 'xC' frontends is enabling its relay interface:
then run it from within the _public_ subdirectory.
/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 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.pager = Press Tab here and change +Gb to +Gb1d
/set general.date_change_line = "%a %e %b %Y" /set general.date_change_line = "%a %e %b %Y"
/set general.plugin_autoload += "fancy-prompt.lua" /set general.plugin_autoload += "fancy-prompt.lua"
/set attributes.userhost = "\x1b[38;5;109m" /set theme.userhost = "109"
/set attributes.join = "\x1b[38;5;108m" /set theme.join = "108"
/set attributes.part = "\x1b[38;5;138m" /set theme.part = "138"
/set attributes.external = "\x1b[38;5;248m" /set theme.external = "248"
/set attributes.timestamp = "\x1b[48;5;255m\x1b[38;5;250m" /set theme.timestamp = "250 255"
/set attributes.read_marker = "\x1b[38;5;202m" /set theme.read_marker = "202"
Configuration profiles Configuration profiles
---------------------- ----------------------

@ -1 +1 @@
Subproject commit f545be725df9195a5b5897ad95a0220acf10f148 Subproject commit 22a121383f73fa7739f324021b6ad0ba6ed3cdb3

499
xC.c
View File

@ -19,7 +19,6 @@
// A table of all attributes we use for output // A table of all attributes we use for output
#define ATTR_TABLE(XX) \ #define ATTR_TABLE(XX) \
XX( PROMPT, prompt, Terminal attrs for the prompt ) \ 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( DATE_CHANGE, date_change, Terminal attrs for date change ) \
XX( READ_MARKER, read_marker, Terminal attrs for the read marker ) \ XX( READ_MARKER, read_marker, Terminal attrs for the read marker ) \
XX( WARNING, warning, Terminal attrs for warnings ) \ XX( WARNING, warning, Terminal attrs for warnings ) \
@ -34,6 +33,7 @@
enum enum
{ {
ATTR_RESET,
#define XX(x, y, z) ATTR_ ## x, #define XX(x, y, z) ATTR_ ## x,
ATTR_TABLE (XX) ATTR_TABLE (XX)
#undef XX #undef XX
@ -48,6 +48,9 @@ enum
#include "config.h" #include "config.h"
#define PROGRAM_NAME "xC" #define PROGRAM_NAME "xC"
// fmemopen
#define _POSIX_C_SOURCE 200809L
#include "common.c" #include "common.c"
#include "xD-replies.c" #include "xD-replies.c"
#include "xC-proto.c" #include "xC-proto.c"
@ -1452,6 +1455,58 @@ channel_destroy (struct channel *self)
REF_COUNTABLE_METHODS (channel) 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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~ Buffers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
enum formatter_item_type enum formatter_item_type
@ -1465,17 +1520,6 @@ enum formatter_item_type
FORMATTER_ITEM_IGNORE_ATTR ///< Un/set attribute ignoration 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 struct formatter_item
{ {
enum formatter_item_type type : 16; ///< Type of this item enum formatter_item_type type : 16; ///< Type of this item
@ -2116,12 +2160,13 @@ struct completion_hook
struct app_context struct app_context
{ {
char *attrs_defaults[ATTR_COUNT]; ///< Default terminal attributes /// Default terminal attributes
struct attrs theme_defaults[ATTR_COUNT];
// Configuration: // Configuration:
struct config config; ///< Program 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 isolate_buffers; ///< Isolate global/server buffers
bool beep_on_highlight; ///< Beep on highlight bool beep_on_highlight; ///< Beep on highlight
bool logging; ///< Logging to file enabled bool logging; ///< Logging to file enabled
@ -2305,11 +2350,6 @@ app_context_free (struct app_context *self)
plugin_destroy (iter); plugin_destroy (iter);
config_free (&self->config); 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) 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_relay_bind_change (struct config_item *item);
static void on_config_backlog_limit_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); static void on_config_logging_change (struct config_item *item);
#define TRIVIAL_BOOLEAN_ON_CHANGE(name) \ #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, \ #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) ATTR_TABLE (XX)
#undef XX #undef XX
{} {}
@ -2666,9 +2706,9 @@ load_config_general (struct config_item *subtree, void *user_data)
} }
static void 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 static void
@ -2676,11 +2716,11 @@ register_config_modules (struct app_context *ctx)
{ {
struct config *config = &ctx->config; struct config *config = &ctx->config;
// The servers are loaded later when we can create buffers for them // The servers are loaded later when we can create buffers for them
config_register_module (config, "servers", NULL, NULL); config_register_module (config, "servers", NULL, NULL);
config_register_module (config, "aliases", NULL, NULL); config_register_module (config, "aliases", NULL, NULL);
config_register_module (config, "plugins", NULL, NULL); config_register_module (config, "plugins", NULL, NULL);
config_register_module (config, "general", load_config_general, ctx); config_register_module (config, "general", load_config_general, ctx);
config_register_module (config, "attributes", load_config_attributes, 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); 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 static void
relay_prepare_buffer_line (struct app_context *ctx, struct buffer *buffer, relay_prepare_buffer_line (struct app_context *ctx, struct buffer *buffer,
struct buffer_line *line, bool leak_to_active) 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++) for (size_t i = 0; line->items[i].type; i++)
len++; len++;
// XXX: This way helps xP's JSON conversion, but is super annoying for us. // Beware of the upper bound, currently dominated by FORMATTER_ITEM_ATTR.
union relay_item_data *p = e->items = xcalloc (len * 6, sizeof *e->items); union relay_item_data *p = e->items = xcalloc (len * 9, sizeof *e->items);
for (struct formatter_item *i = line->items; len--; i++) for (struct formatter_item *i = line->items; len--; i++)
{ p = relay_translate_formatter (ctx, p, 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;
}
}
e->items_len = p - e->items; e->items_len = p - e->items;
} }
@ -3239,123 +3298,6 @@ get_attribute_printer (FILE *stream)
return NULL; 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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~ Attribute printer ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// A little tool that tries to make the most of the terminal's capabilities // 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 struct attr_printer
{ {
char **attrs; ///< Named attributes struct attrs *attrs; ///< Named attributes
FILE *stream; ///< Output stream FILE *stream; ///< Output stream
bool dirty; ///< Attributes are set 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 static void
attr_printer_filtered_puts (FILE *stream, const char *attr) attr_printer_filtered_puts (FILE *stream, const char *attr)
@ -3408,22 +3350,11 @@ static void
attr_printer_reset (struct attr_printer *self) attr_printer_reset (struct attr_printer *self)
{ {
if (self->dirty) if (self->dirty)
attr_printer_tputs (self, self->attrs[ATTR_RESET]); attr_printer_tputs (self, exit_attribute_mode);
self->dirty = false; 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: // NOTE: commonly terminals have:
// 8 colours (worst, bright fg often with BOLD, bg sometimes with BLINK) // 8 colours (worst, bright fg often with BOLD, bg sometimes with BLINK)
// 16 colours (okayish, we have the full basic range guaranteed) // 16 colours (okayish, we have the full basic range guaranteed)
@ -3551,6 +3482,135 @@ attr_printer_apply (struct attr_printer *self,
self->dirty = true; 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 ----------------------------------------------------------------- // --- Helpers -----------------------------------------------------------------
static int 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)) if (self->ctx->word_wrapping && !(flush_opts & FLUSH_OPT_NOWRAP))
line = line_wrap (line, g_terminal.columns); 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 struct line_char_attrs attrs = {}; // Won't compare equal to anything
LIST_FOR_EACH (struct line_char, c, line) 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); formatter_putc (NULL, stream);
attrs = c->attrs; attrs = c->attrs;
if (attrs.named != -1) if (attrs.named == -1)
attr_printer_apply_named (&state, attrs.named);
else
attr_printer_apply (&state, attrs.text, attrs.fg, attrs.bg); attr_printer_apply (&state, attrs.text, attrs.fg, attrs.bg);
else
attr_printer_apply_named (&state, attrs.named);
} }
formatter_putc (c, stream); 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); char *localized = iconv_xstrdup (ctx->term_from_utf8, prompt.str, -1, NULL);
str_free (&prompt); str_free (&prompt);
// XXX: to be completely correct, we should use tputs, but we cannot
if (have_attributes) if (have_attributes)
{ {
// XXX: to be completely correct, we should use tputs, but we cannot char buf[16384] = "";
input_maybe_set_prompt (ctx->input, xstrdup_printf ("%c%s%c%s%c%s%c%s", FILE *memfp = fmemopen (buf, sizeof buf - 1, "wb");
INPUT_START_IGNORE, ctx->attrs[ATTR_PROMPT], struct attr_printer state = { ctx->theme, memfp, false };
INPUT_END_IGNORE,
localized, fputc (INPUT_START_IGNORE, memfp);
INPUT_START_IGNORE, ctx->attrs[ATTR_RESET], attr_printer_apply_named (&state, ATTR_PROMPT);
INPUT_END_IGNORE, fputc (INPUT_END_IGNORE, memfp);
attributed_suffix));
fputs (localized, memfp);
free (localized); 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 else
input_maybe_set_prompt (ctx->input, localized); input_maybe_set_prompt (ctx->input, localized);