Compare commits
No commits in common. "b082e82b625eca244ac0d1381c152f12b946d463" and "e0ad67a921277608579c84182bafd7ebc006a889" have entirely different histories.
b082e82b62
...
e0ad67a921
7
NEWS
7
NEWS
@ -1,10 +1,3 @@
|
|||||||
1.4.0 (xxxx-xx-xx)
|
|
||||||
|
|
||||||
* xC: made message autosplitting respect text formatting
|
|
||||||
|
|
||||||
* xC: fixed displaying IRC colours above 16
|
|
||||||
|
|
||||||
|
|
||||||
1.3.0 (2021-08-07) "New World Order"
|
1.3.0 (2021-08-07) "New World Order"
|
||||||
|
|
||||||
* xC: made nick autocompletion offer recent speakers first
|
* xC: made nick autocompletion offer recent speakers first
|
||||||
|
173
xC.c
173
xC.c
@ -2797,8 +2797,7 @@ enum
|
|||||||
TEXT_UNDERLINE = 1 << 2,
|
TEXT_UNDERLINE = 1 << 2,
|
||||||
TEXT_INVERSE = 1 << 3,
|
TEXT_INVERSE = 1 << 3,
|
||||||
TEXT_BLINK = 1 << 4,
|
TEXT_BLINK = 1 << 4,
|
||||||
TEXT_CROSSED_OUT = 1 << 5,
|
TEXT_CROSSED_OUT = 1 << 5
|
||||||
TEXT_MONOSPACE = 1 << 6
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct attr_printer
|
struct attr_printer
|
||||||
@ -3191,7 +3190,7 @@ static const int g_mirc_to_terminal[] =
|
|||||||
|
|
||||||
// https://modern.ircdocs.horse/formatting.html
|
// https://modern.ircdocs.horse/formatting.html
|
||||||
// http://anti.teamidiot.de/static/nei/*/extended_mirc_color_proposal.html
|
// http://anti.teamidiot.de/static/nei/*/extended_mirc_color_proposal.html
|
||||||
static const int16_t g_extra_to_256[100 - 16] =
|
static const char g_extra_to_256[100 - 16] =
|
||||||
{
|
{
|
||||||
52, 94, 100, 58, 22, 29, 23, 24, 17, 54, 53, 89,
|
52, 94, 100, 58, 22, 29, 23, 24, 17, 54, 53, 89,
|
||||||
88, 130, 142, 64, 28, 35, 30, 25, 18, 91, 90, 125,
|
88, 130, 142, 64, 28, 35, 30, 25, 18, 91, 90, 125,
|
||||||
@ -3203,45 +3202,36 @@ static const int16_t g_extra_to_256[100 - 16] =
|
|||||||
};
|
};
|
||||||
|
|
||||||
static const char *
|
static const char *
|
||||||
irc_parse_mirc_color (const char *s, uint8_t *fg, uint8_t *bg)
|
formatter_parse_mirc_color (struct formatter *self, const char *s)
|
||||||
{
|
{
|
||||||
if (!isdigit_ascii (*s))
|
if (!isdigit_ascii (*s))
|
||||||
{
|
{
|
||||||
*fg = *bg = 99;
|
FORMATTER_ADD_ITEM (self, FG_COLOR, .color = -1);
|
||||||
|
FORMATTER_ADD_ITEM (self, BG_COLOR, .color = -1);
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
*fg = *s++ - '0';
|
int fg = *s++ - '0';
|
||||||
if (isdigit_ascii (*s))
|
if (isdigit_ascii (*s))
|
||||||
*fg = *fg * 10 + (*s++ - '0');
|
fg = fg * 10 + (*s++ - '0');
|
||||||
|
if (fg < 16)
|
||||||
|
FORMATTER_ADD_ITEM (self, FG_COLOR, .color = g_mirc_to_terminal[fg]);
|
||||||
|
else
|
||||||
|
FORMATTER_ADD_ITEM (self, FG_COLOR,
|
||||||
|
.color = COLOR_256 (DEFAULT, g_extra_to_256[fg]));
|
||||||
|
|
||||||
if (*s != ',' || !isdigit_ascii (s[1]))
|
if (*s != ',' || !isdigit_ascii (s[1]))
|
||||||
return s;
|
return s;
|
||||||
s++;
|
s++;
|
||||||
|
|
||||||
*bg = *s++ - '0';
|
int bg = *s++ - '0';
|
||||||
if (isdigit_ascii (*s))
|
if (isdigit_ascii (*s))
|
||||||
*bg = *bg * 10 + (*s++ - '0');
|
bg = bg * 10 + (*s++ - '0');
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char *
|
|
||||||
formatter_parse_mirc_color (struct formatter *self, const char *s)
|
|
||||||
{
|
|
||||||
uint8_t fg = 255, bg = 255;
|
|
||||||
s = irc_parse_mirc_color (s, &fg, &bg);
|
|
||||||
|
|
||||||
if (fg < 16)
|
|
||||||
FORMATTER_ADD_ITEM (self, FG_COLOR, .color = g_mirc_to_terminal[fg]);
|
|
||||||
else if (fg < 100)
|
|
||||||
FORMATTER_ADD_ITEM (self, FG_COLOR,
|
|
||||||
.color = COLOR_256 (DEFAULT, g_extra_to_256[fg - 16]));
|
|
||||||
|
|
||||||
if (bg < 16)
|
if (bg < 16)
|
||||||
FORMATTER_ADD_ITEM (self, BG_COLOR, .color = g_mirc_to_terminal[bg]);
|
FORMATTER_ADD_ITEM (self, BG_COLOR, .color = g_mirc_to_terminal[bg]);
|
||||||
else if (bg < 100)
|
else
|
||||||
FORMATTER_ADD_ITEM (self, BG_COLOR,
|
FORMATTER_ADD_ITEM (self, BG_COLOR,
|
||||||
.color = COLOR_256 (DEFAULT, g_extra_to_256[bg - 16]));
|
.color = COLOR_256 (DEFAULT, g_extra_to_256[bg]));
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
@ -8237,76 +8227,12 @@ irc_process_message (const struct irc_message *msg, struct server *s)
|
|||||||
|
|
||||||
// --- Message autosplitting magic ---------------------------------------------
|
// --- Message autosplitting magic ---------------------------------------------
|
||||||
|
|
||||||
// This is a rather basic algorithm; something like ICU with proper
|
// This is the most basic acceptable algorithm; something like ICU with proper
|
||||||
// locale specification would be needed to make it work better.
|
// locale specification would be needed to make it work better.
|
||||||
|
|
||||||
struct irc_char_attrs
|
|
||||||
{
|
|
||||||
uint8_t fg, bg; ///< {Fore,back}ground colour or 99
|
|
||||||
uint8_t attributes; ///< TEXT_* flags, except TEXT_BLINK
|
|
||||||
uint8_t starts_at_boundary; ///< Possible to split here?
|
|
||||||
};
|
|
||||||
|
|
||||||
static void
|
|
||||||
irc_serialize_char_attrs (const struct irc_char_attrs *attrs, struct str *out)
|
|
||||||
{
|
|
||||||
soft_assert (attrs->fg < 100 && attrs->bg < 100);
|
|
||||||
|
|
||||||
if (attrs->fg != 99 || attrs->bg != 99)
|
|
||||||
{
|
|
||||||
str_append_printf (out, "\x03%u", attrs->fg);
|
|
||||||
if (attrs->bg != 99)
|
|
||||||
str_append_printf (out, ",%02u", attrs->bg);
|
|
||||||
}
|
|
||||||
if (attrs->attributes & TEXT_BOLD) str_append_c (out, '\x02');
|
|
||||||
if (attrs->attributes & TEXT_ITALIC) str_append_c (out, '\x1d');
|
|
||||||
if (attrs->attributes & TEXT_UNDERLINE) str_append_c (out, '\x1f');
|
|
||||||
if (attrs->attributes & TEXT_INVERSE) str_append_c (out, '\x16');
|
|
||||||
if (attrs->attributes & TEXT_CROSSED_OUT) str_append_c (out, '\x1e');
|
|
||||||
if (attrs->attributes & TEXT_MONOSPACE) str_append_c (out, '\x11');
|
|
||||||
}
|
|
||||||
|
|
||||||
// The text needs to be NUL-terminated
|
|
||||||
// TODO: try to deduplicate analogous code in formatter_parse_mirc()
|
|
||||||
static struct irc_char_attrs *
|
|
||||||
irc_analyze_text (const char *text, size_t len)
|
|
||||||
{
|
|
||||||
struct irc_char_attrs *attrs = xcalloc (len, sizeof *attrs),
|
|
||||||
blank = { .fg = 99, .bg = 99, .starts_at_boundary = true },
|
|
||||||
next = blank, cur = next;
|
|
||||||
|
|
||||||
for (size_t i = 0; i != len; cur = next)
|
|
||||||
{
|
|
||||||
const char *start = text;
|
|
||||||
hard_assert (utf8_decode (&text, len - i) >= 0);
|
|
||||||
switch (*start)
|
|
||||||
{
|
|
||||||
case '\x02': next.attributes ^= TEXT_BOLD; break;
|
|
||||||
case '\x11': next.attributes ^= TEXT_MONOSPACE; break;
|
|
||||||
case '\x1d': next.attributes ^= TEXT_ITALIC; break;
|
|
||||||
case '\x1e': next.attributes ^= TEXT_CROSSED_OUT; break;
|
|
||||||
case '\x1f': next.attributes ^= TEXT_UNDERLINE; break;
|
|
||||||
case '\x16': next.attributes ^= TEXT_INVERSE; break;
|
|
||||||
|
|
||||||
case '\x03':
|
|
||||||
text = irc_parse_mirc_color (text, &next.fg, &next.bg);
|
|
||||||
break;
|
|
||||||
case '\x0f':
|
|
||||||
next = blank;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (start++ != text)
|
|
||||||
{
|
|
||||||
attrs[i++] = cur;
|
|
||||||
cur.starts_at_boundary = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return attrs;
|
|
||||||
}
|
|
||||||
|
|
||||||
static size_t
|
static size_t
|
||||||
wrap_text_for_single_line (const char *text, struct irc_char_attrs *attrs,
|
wrap_text_for_single_line (const char *text, size_t text_len,
|
||||||
size_t text_len, size_t target_len, struct str *output)
|
size_t line_len, struct str *output)
|
||||||
{
|
{
|
||||||
size_t eaten = 0;
|
size_t eaten = 0;
|
||||||
|
|
||||||
@ -8314,7 +8240,7 @@ wrap_text_for_single_line (const char *text, struct irc_char_attrs *attrs,
|
|||||||
const char *word_start;
|
const char *word_start;
|
||||||
const char *word_end = text + strcspn (text, " ");
|
const char *word_end = text + strcspn (text, " ");
|
||||||
size_t word_len = word_end - text;
|
size_t word_len = word_end - text;
|
||||||
while (target_len && word_len <= target_len)
|
while (line_len && word_len <= line_len)
|
||||||
{
|
{
|
||||||
if (word_len)
|
if (word_len)
|
||||||
{
|
{
|
||||||
@ -8322,7 +8248,7 @@ wrap_text_for_single_line (const char *text, struct irc_char_attrs *attrs,
|
|||||||
|
|
||||||
text += word_len;
|
text += word_len;
|
||||||
eaten += word_len;
|
eaten += word_len;
|
||||||
target_len -= word_len;
|
line_len -= word_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the next word's end
|
// Find the next word's end
|
||||||
@ -8336,62 +8262,53 @@ wrap_text_for_single_line (const char *text, struct irc_char_attrs *attrs,
|
|||||||
return eaten + (word_start - text);
|
return eaten + (word_start - text);
|
||||||
|
|
||||||
// And if that doesn't help, cut the longest valid block of characters
|
// And if that doesn't help, cut the longest valid block of characters
|
||||||
for (size_t i = 1; i <= text_len && i <= target_len; i++)
|
for (const char *p = text; (size_t) (p - text) <= line_len; )
|
||||||
if (i == text_len || attrs[i].starts_at_boundary)
|
{
|
||||||
eaten = i;
|
eaten = p - text;
|
||||||
|
hard_assert (utf8_decode (&p, text_len - eaten) >= 0);
|
||||||
|
}
|
||||||
str_append_data (output, text, eaten);
|
str_append_data (output, text, eaten);
|
||||||
return eaten;
|
return eaten;
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
// In practice, this should never fail at all, although it's not guaranteed
|
|
||||||
static bool
|
static bool
|
||||||
wrap_message (const char *message,
|
wrap_message (const char *message,
|
||||||
int line_max, struct strv *output, struct error **e)
|
int line_max, struct strv *output, struct error **e)
|
||||||
{
|
{
|
||||||
size_t message_left = strlen (message), i = 0;
|
|
||||||
struct irc_char_attrs *attrs = irc_analyze_text (message, message_left);
|
|
||||||
struct str m = str_make ();
|
|
||||||
if (line_max <= 0)
|
if (line_max <= 0)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
while (m.len + message_left > (size_t) line_max)
|
int message_left = strlen (message);
|
||||||
|
while (message_left > line_max)
|
||||||
{
|
{
|
||||||
|
struct str m = str_make ();
|
||||||
|
|
||||||
size_t eaten = wrap_text_for_single_line
|
size_t eaten = wrap_text_for_single_line
|
||||||
(message + i, attrs + i, message_left, line_max - m.len, &m);
|
(message, message_left, line_max, &m);
|
||||||
if (!eaten)
|
if (!eaten)
|
||||||
|
{
|
||||||
|
str_free (&m);
|
||||||
goto error;
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
strv_append_owned (output, str_steal (&m));
|
strv_append_owned (output, str_steal (&m));
|
||||||
m = str_make ();
|
message += eaten;
|
||||||
|
message_left -= eaten;
|
||||||
i += eaten;
|
|
||||||
if (!(message_left -= eaten))
|
|
||||||
break;
|
|
||||||
|
|
||||||
irc_serialize_char_attrs (attrs + i, &m);
|
|
||||||
if (m.len >= (size_t) line_max)
|
|
||||||
{
|
|
||||||
print_debug ("formatting continuation too long");
|
|
||||||
str_reset (&m);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message_left)
|
if (message_left)
|
||||||
strv_append_owned (output,
|
strv_append (output, message);
|
||||||
xstrdup_printf ("%s%s", m.str, message + i));
|
|
||||||
|
|
||||||
free (attrs);
|
|
||||||
str_free (&m);
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
error:
|
error:
|
||||||
free (attrs);
|
// Well, that's just weird
|
||||||
str_free (&m);
|
error_set (e,
|
||||||
return error_set (e,
|
|
||||||
"Message splitting was unsuccessful as there was "
|
"Message splitting was unsuccessful as there was "
|
||||||
"too little room for UTF-8 characters");
|
"too little room for UTF-8 characters");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Automatically splits messages that arrive at other clients with our prefix
|
/// Automatically splits messages that arrive at other clients with our prefix
|
||||||
@ -14386,11 +14303,9 @@ test_aliases (void)
|
|||||||
static void
|
static void
|
||||||
test_wrapping (void)
|
test_wrapping (void)
|
||||||
{
|
{
|
||||||
static const char *message = " foo bar foobar fóóbárbáz\002 a\0031 b";
|
static const char *message = " foo bar foobar fóóbárbáz";
|
||||||
// XXX: formatting continuation order is implementation-dependent here
|
static const char *split[] =
|
||||||
// (irc_serialize_char_attrs() makes a choice in serialization)
|
{ " foo", "bar", "foob", "ar", "fó", "ób", "árb", "áz" };
|
||||||
static const char *split[] = { " foo", "bar", "foob", "ar",
|
|
||||||
"fó", "ób", "árb", "áz\x02", "\002a\0031", "\0031\002b" };
|
|
||||||
|
|
||||||
struct strv v = strv_make ();
|
struct strv v = strv_make ();
|
||||||
hard_assert (wrap_message (message, 4, &v, NULL));
|
hard_assert (wrap_message (message, 4, &v, NULL));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user