17 Commits

Author SHA1 Message Date
5165f76b7c xC: quote text coming from a bracketed paste
Not having this has caused me much annoyance over the years.
2021-10-30 09:27:32 +02:00
92ac13f3c6 xC: allow passing the cursor position to editors
Add a configuration option to set a custom editor command,
different from EDITOR or VISUAL--those remain as defaults.

Implement substitutions allowing to convey cursor information
to VIM and Emacs (the latter of which is fairly painful to cater to),
and put usage hints in the configuration option's description.

This should make the editing experience a bit more seamless
for users, even though the position is carried over in one way only.

No sophisticated quoting capabilities were deemed necessary,
it is a lot of code already.  The particular syntax is inspired
by .desktop files and systemd.

["/bin/sh", "-c", "vim +$2go \"$1\"", filename, position, line, column]
would be a slightly simpler but cryptic way of implementing this.
2021-10-30 09:02:35 +02:00
df4ca74580 xC: make libedit autocomplete less miserable
Omitting even this hack was a huge hit to overall usability.
2021-10-30 08:29:16 +02:00
9e297244a4 Update .gitignore 2021-10-30 03:37:22 +02:00
d32ba133c0 Add clang-format configuration, clean up 2021-10-30 02:55:19 +02:00
ce3976e1ec xC: normalize ^J behaviour to follow Readline
For some reason Editline inserts it verbatim,
but in a more broken manner than it has with ^V^J.
2021-10-28 08:49:01 +02:00
e5ed89646b xC: fix newer libedit (2021-08-29) 2021-10-28 08:23:52 +02:00
5e728f6d31 Bump version, update NEWS 2021-10-06 14:05:23 +02:00
766f68e070 Bump liberty 2021-10-06 13:52:59 +02:00
3dc5242d43 Bump liberty
Importing some minor unimportant fixes.
2021-09-26 08:55:46 +02:00
fd9d5db1d2 xD: bump the soft file descriptor limit
By default it's a mere thousand connections, which is unnecessarily
crippling our advertised ability to handle lots of them.

Thanks for the advice, Lennart.
2021-09-23 20:32:00 +02:00
cb480b4c71 xC: show orphan outcoming actions differently
It's hard to think of anything actually good here.

This would be an exceptionally rare thing to do, anyway.
2021-09-05 02:51:05 +02:00
59cc423694 xC: abandon Freenode, embrace IRCnet
You're not fucking supposed to require a fucking registration
on fucking IRC networks.
2021-08-29 15:18:20 +02:00
9323089d66 xC: mIRC didn't invent all IRC formatting
So let's not confuse ourselves.
2021-08-29 12:12:52 +02:00
de7df1f60d xC: refactor parsing of IRC formatting 2021-08-29 12:06:53 +02:00
b082e82b62 xC: fix displaying IRC colours above 16
First, we indexed the colour array without a required offset.
Second, the data type was too small and overflowed negative.

Detected during a refactor, which this is a part of.
2021-08-28 18:25:03 +02:00
b8dbc70a9c xC: respect text formatting when autosplitting 2021-08-28 18:24:20 +02:00
8 changed files with 426 additions and 128 deletions

32
.clang-format Normal file
View File

@@ -0,0 +1,32 @@
# clang-format is fairly limited, and these rules are approximate:
# - array initializers can get terribly mangled with clang-format 12.0,
# - sometimes it still aligns with space characters,
# - struct name NL { NL ... NL } NL name; is unachievable.
BasedOnStyle: GNU
ColumnLimit: 80
IndentWidth: 4
TabWidth: 4
UseTab: ForContinuationAndIndentation
BreakBeforeBraces: Allman
SpaceAfterCStyleCast: true
AlignAfterOpenBracket: DontAlign
AlignOperands: DontAlign
AlignConsecutiveMacros: Consecutive
AllowAllArgumentsOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
IndentGotoLabels: false
# IncludeCategories has some potential, but it may also break the build.
# Note that the documentation says the value should be "Never".
SortIncludes: false
# This is a compromise, it generally works out aesthetically better.
BinPackArguments: false
# Unfortunately, this can't be told to align to column 40 or so.
SpacesBeforeTrailingComments: 2
# liberty-specific macro body wrappers.
MacroBlockBegin: "BLOCK_START"
MacroBlockEnd: "BLOCK_END"
ForEachMacros: ["LIST_FOR_EACH"]

2
.gitignore vendored
View File

@@ -7,3 +7,5 @@
/uirc3.files
/uirc3.creator*
/uirc3.includes
/uirc3.cflags
/uirc3.cxxflags

View File

@@ -1,5 +1,5 @@
cmake_minimum_required (VERSION 3.0)
project (uirc3 VERSION 1.3.0 LANGUAGES C)
project (uirc3 VERSION 1.4.0 LANGUAGES C)
# Options
option (WANT_READLINE "Use GNU Readline for the UI (better)" ON)

12
NEWS
View File

@@ -1,3 +1,15 @@
1.4.0 (2021-10-06) "Call Me Scruffy Scruffington"
* xC: made message autosplitting respect text formatting
* xC: fixed displaying IRC colours above 16
* xC: offer IRCnet as an IRC network to connect to,
rather than the lunatic new Freenode
* xD: started bumping the soft limit on file descriptors to the hard one
1.3.0 (2021-08-07) "New World Order"
* xC: made nick autocompletion offer recent speakers first

View File

@@ -22,11 +22,11 @@
#define LIBERTY_WANT_PROTO_IRC
#ifdef WANT_SYSLOG_LOGGING
#define print_fatal_data ((void *) LOG_ERR)
#define print_error_data ((void *) LOG_ERR)
#define print_warning_data ((void *) LOG_WARNING)
#define print_status_data ((void *) LOG_INFO)
#define print_debug_data ((void *) LOG_DEBUG)
#define print_fatal_data ((void *) LOG_ERR)
#define print_error_data ((void *) LOG_ERR)
#define print_warning_data ((void *) LOG_WARNING)
#define print_status_data ((void *) LOG_INFO)
#define print_debug_data ((void *) LOG_DEBUG)
#endif // WANT_SYSLOG_LOGGING
#include "liberty/liberty.c"
@@ -87,15 +87,15 @@ static time_t
unixtime_msec (long *msec)
{
#ifdef _POSIX_TIMERS
struct timespec tp;
hard_assert (clock_gettime (CLOCK_REALTIME, &tp) != -1);
*msec = tp.tv_nsec / 1000000;
struct timespec tp;
hard_assert (clock_gettime (CLOCK_REALTIME, &tp) != -1);
*msec = tp.tv_nsec / 1000000;
#else // ! _POSIX_TIMERS
struct timeval tp;
hard_assert (gettimeofday (&tp, NULL) != -1);
*msec = tp.tv_usec / 1000;
struct timeval tp;
hard_assert (gettimeofday (&tp, NULL) != -1);
*msec = tp.tv_usec / 1000;
#endif // ! _POSIX_TIMERS
return tp.tv_sec;
return tp.tv_sec;
}
// --- Logging -----------------------------------------------------------------

Submodule liberty updated: 9639777814...1b9d89cab3

460
xC.c
View File

@@ -238,8 +238,8 @@ struct input_vtable
/// Bind Alt+key to the given named function
void (*bind_meta) (void *input, char key, const char *fn);
/// Get the current line input
char *(*get_line) (void *input);
/// Get the current line input and position within
char *(*get_line) (void *input, int *position);
/// Clear the current line input
void (*clear_line) (void *input);
/// Insert text at current position
@@ -361,9 +361,10 @@ input_rl_insert (void *input, const char *s)
}
static char *
input_rl_get_line (void *input)
input_rl_get_line (void *input, int *position)
{
(void) input;
if (position) *position = rl_point;
return rl_copy_text (0, rl_end);
}
@@ -771,24 +772,13 @@ struct input_el
static void app_editline_init (struct input_el *self);
static int
input_el__get_termios (int character, int fallback)
{
if (!g_terminal.initialized)
return fallback;
cc_t value = g_terminal.termios.c_cc[character];
if (value == _POSIX_VDISABLE)
return fallback;
return value;
}
static void
input_el__redisplay (void *input)
{
// See rl_redisplay()
// See rl_redisplay(), however NetBSD editline's map.c v1.54 breaks VREPRINT
// so we bind redisplay somewhere else in app_editline_init()
struct input_el *self = input;
char x[] = { input_el__get_termios (VREPRINT, 'R' - 0x40), 0 };
char x[] = { 'q' & 31, 0 };
el_push (self->editline, x);
// We have to do this or it gets stuck and nothing is done
@@ -871,10 +861,12 @@ input_el_insert (void *input, const char *s)
}
static char *
input_el_get_line (void *input)
input_el_get_line (void *input, int *position)
{
struct input_el *self = input;
const LineInfo *info = el_line (self->editline);
int point = info->cursor - info->buffer;
if (position) *position = point;
return xstrndup (info->buffer, info->lastchar - info->buffer);
}
@@ -1448,7 +1440,7 @@ enum formatter_item_type
FORMATTER_ITEM_ATTR, ///< Formatting attributes
FORMATTER_ITEM_FG_COLOR, ///< Foreground colour
FORMATTER_ITEM_BG_COLOR, ///< Background colour
FORMATTER_ITEM_SIMPLE, ///< Toggle mIRC formatting
FORMATTER_ITEM_SIMPLE, ///< Toggle IRC formatting
FORMATTER_ITEM_IGNORE_ATTR ///< Un/set attribute ignoration
};
@@ -2089,7 +2081,7 @@ struct app_context
int *nick_palette; ///< A 256-colour palette for nicknames
size_t nick_palette_len; ///< Number of entries in nick_palette
bool awaiting_mirc_escape; ///< Awaiting a mIRC attribute escape
bool awaiting_formatting_escape; ///< Awaiting an IRC formatting escape
bool in_bracketed_paste; ///< User is pasting some content
struct str input_buffer; ///< Buffered pasted content
@@ -2450,6 +2442,13 @@ static struct config_schema g_config_behaviour[] =
.type = CONFIG_ITEM_BOOLEAN,
.default_ = "on",
.on_change = on_config_word_wrapping_change },
{ .name = "editor_command",
.comment = "VIM: \"vim +%Bgo %F\", Emacs: \"emacs -nw +%L:%C %F\"",
.type = CONFIG_ITEM_STRING },
{ .name = "process_pasted_text",
.comment = "Normalize newlines and quote the command prefix in pastes",
.type = CONFIG_ITEM_BOOLEAN,
.default_ = "on" },
{ .name = "date_change_line",
.comment = "Input to strftime(3) for the date change line",
.type = CONFIG_ITEM_STRING,
@@ -2797,7 +2796,8 @@ enum
TEXT_UNDERLINE = 1 << 2,
TEXT_INVERSE = 1 << 3,
TEXT_BLINK = 1 << 4,
TEXT_CROSSED_OUT = 1 << 5
TEXT_CROSSED_OUT = 1 << 5,
TEXT_MONOSPACE = 1 << 6
};
struct attr_printer
@@ -2960,6 +2960,7 @@ attr_printer_apply (struct attr_printer *self,
attr_printer_reset (self);
// TEXT_MONOSPACE is unimplemented, for obvious reasons
if (text_attrs)
attr_printer_tputs (self, tparm (set_attributes,
0, // standout
@@ -3121,7 +3122,7 @@ irc_to_utf8 (const char *text)
// #l inserts a locale-encoded string
//
// #S inserts a string from the server with unknown encoding
// #m inserts a mIRC-formatted string (auto-resets at boundaries)
// #m inserts an IRC-formatted string (auto-resets at boundaries)
// #n cuts the nickname from a string and automatically colours it
// #N is like #n but also appends userhost, if present
//
@@ -3152,8 +3153,6 @@ formatter_add_item (struct formatter *self, struct formatter_item template_)
FORMATTER_ADD_ITEM ((self), ATTR, .attribute = ATTR_RESET)
#define FORMATTER_ADD_TEXT(self, text_) \
FORMATTER_ADD_ITEM ((self), TEXT, .text = (text_))
#define FORMATTER_ADD_SIMPLE(self, attribute_) \
FORMATTER_ADD_ITEM ((self), SIMPLE, .attribute = TEXT_ ## attribute_)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -3190,7 +3189,7 @@ static const int g_mirc_to_terminal[] =
// https://modern.ircdocs.horse/formatting.html
// http://anti.teamidiot.de/static/nei/*/extended_mirc_color_proposal.html
static const char g_extra_to_256[100 - 16] =
static const int16_t g_extra_to_256[100 - 16] =
{
52, 94, 100, 58, 22, 29, 23, 24, 17, 54, 53, 89,
88, 130, 142, 64, 28, 35, 30, 25, 18, 91, 90, 125,
@@ -3202,42 +3201,127 @@ static const char g_extra_to_256[100 - 16] =
};
static const char *
formatter_parse_mirc_color (struct formatter *self, const char *s)
irc_parse_mirc_color (const char *s, uint8_t *fg, uint8_t *bg)
{
if (!isdigit_ascii (*s))
{
FORMATTER_ADD_ITEM (self, FG_COLOR, .color = -1);
FORMATTER_ADD_ITEM (self, BG_COLOR, .color = -1);
*fg = *bg = 99;
return s;
}
int fg = *s++ - '0';
*fg = *s++ - '0';
if (isdigit_ascii (*s))
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]));
*fg = *fg * 10 + (*s++ - '0');
if (*s != ',' || !isdigit_ascii (s[1]))
return s;
s++;
int bg = *s++ - '0';
*bg = *s++ - '0';
if (isdigit_ascii (*s))
bg = bg * 10 + (*s++ - '0');
*bg = *bg * 10 + (*s++ - '0');
return s;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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');
}
static int
irc_parse_attribute (char c)
{
switch (c)
{
case '\x02' /* ^B */: return TEXT_BOLD;
case '\x11' /* ^Q */: return TEXT_MONOSPACE;
case '\x16' /* ^V */: return TEXT_INVERSE;
case '\x1d' /* ^] */: return TEXT_ITALIC;
case '\x1e' /* ^^ */: return TEXT_CROSSED_OUT;
case '\x1f' /* ^_ */: return TEXT_UNDERLINE;
case '\x0f' /* ^O */: return -1;
}
return 0;
}
// The text needs to be NUL-terminated, and a valid UTF-8 string
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);
int attribute = irc_parse_attribute (*start);
if (*start == '\x03')
text = irc_parse_mirc_color (text, &next.fg, &next.bg);
else if (attribute > 0)
next.attributes ^= attribute;
else if (attribute < 0)
next = blank;
while (start++ != text)
{
attrs[i++] = cur;
cur.starts_at_boundary = false;
}
}
return attrs;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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)
FORMATTER_ADD_ITEM (self, BG_COLOR, .color = g_mirc_to_terminal[bg]);
else
else if (bg < 100)
FORMATTER_ADD_ITEM (self, BG_COLOR,
.color = COLOR_256 (DEFAULT, g_extra_to_256[bg]));
.color = COLOR_256 (DEFAULT, g_extra_to_256[bg - 16]));
return s;
}
static void
formatter_parse_mirc (struct formatter *self, const char *s)
formatter_parse_message (struct formatter *self, const char *s)
{
FORMATTER_ADD_RESET (self);
@@ -3251,24 +3335,15 @@ formatter_parse_mirc (struct formatter *self, const char *s)
str_reset (&buf);
}
switch (c)
{
case '\x02': FORMATTER_ADD_SIMPLE (self, BOLD); break;
case '\x11': /* monospace, N/A */ break;
case '\x1d': FORMATTER_ADD_SIMPLE (self, ITALIC); break;
case '\x1e': FORMATTER_ADD_SIMPLE (self, CROSSED_OUT); break;
case '\x1f': FORMATTER_ADD_SIMPLE (self, UNDERLINE); break;
case '\x16': FORMATTER_ADD_SIMPLE (self, INVERSE); break;
case '\x03':
int attribute = irc_parse_attribute (c);
if (c == '\x03')
s = formatter_parse_mirc_color (self, s);
break;
case '\x0f':
else if (attribute > 0)
FORMATTER_ADD_ITEM (self, SIMPLE, .attribute = attribute);
else if (attribute < 0)
FORMATTER_ADD_RESET (self);
break;
default:
else
str_append_c (&buf, c);
}
}
if (buf.len)
@@ -3378,7 +3453,7 @@ restart:
break;
case 'm':
tmp = irc_to_utf8 ((s = va_arg (*ap, char *)));
formatter_parse_mirc (self, tmp);
formatter_parse_message (self, tmp);
free (tmp);
break;
case 'n':
@@ -4086,6 +4161,9 @@ log_full (struct app_context *ctx, struct server *s, struct buffer *buffer,
log_server_status ((s), (s)->buffer, "Notice -> #n: #m", (target), (text))
#define log_outcoming_orphan_privmsg(s, target, text) \
log_server_status ((s), (s)->buffer, "MSG(#n): #m", (target), (text))
#define log_outcoming_orphan_action(s, target, text) \
log_server_status ((s), (s)->buffer, "MSG(#n): #a*#r #m", (target), \
ATTR_ACTION, (text))
#define log_ctcp_query(s, target, tag) \
log_server_status ((s), (s)->buffer, "CTCP query to #S: #S", target, tag)
@@ -6210,7 +6288,7 @@ irc_is_highlight (struct server *s, const char *message)
// Strip formatting from the message so that it doesn't interfere
// with nickname detection (colour sequences in particular)
struct formatter f = formatter_make (s->ctx, NULL);
formatter_parse_mirc (&f, message);
formatter_parse_message (&f, message);
struct str stripped = str_make ();
for (size_t i = 0; i < f.items_len; i++)
@@ -6511,8 +6589,9 @@ irc_handle_sent_privmsg_text (struct server *s,
prefixes, s->irc_user->nickname, text->str);
free (prefixes);
}
else if (is_action)
log_outcoming_orphan_action (s, target, text->str);
else
// TODO: also handle actions here
log_outcoming_orphan_privmsg (s, target, text->str);
}
@@ -6825,7 +6904,7 @@ irc_handle_join (struct server *s, const struct irc_message *msg)
buffer_add (s->ctx, buffer);
char *input = CALL (s->ctx->input, get_line);
char *input = CALL_ (s->ctx->input, get_line, NULL);
if (!*input)
buffer_activate (s->ctx, buffer);
else
@@ -8227,12 +8306,12 @@ irc_process_message (const struct irc_message *msg, struct server *s)
// --- Message autosplitting magic ---------------------------------------------
// This is the most basic acceptable algorithm; something like ICU with proper
// This is a rather basic algorithm; something like ICU with proper
// locale specification would be needed to make it work better.
static size_t
wrap_text_for_single_line (const char *text, size_t text_len,
size_t line_len, struct str *output)
wrap_text_for_single_line (const char *text, struct irc_char_attrs *attrs,
size_t text_len, size_t target_len, struct str *output)
{
size_t eaten = 0;
@@ -8240,7 +8319,7 @@ wrap_text_for_single_line (const char *text, size_t text_len,
const char *word_start;
const char *word_end = text + strcspn (text, " ");
size_t word_len = word_end - text;
while (line_len && word_len <= line_len)
while (target_len && word_len <= target_len)
{
if (word_len)
{
@@ -8248,7 +8327,7 @@ wrap_text_for_single_line (const char *text, size_t text_len,
text += word_len;
eaten += word_len;
line_len -= word_len;
target_len -= word_len;
}
// Find the next word's end
@@ -8262,53 +8341,60 @@ wrap_text_for_single_line (const char *text, size_t text_len,
return eaten + (word_start - text);
// And if that doesn't help, cut the longest valid block of characters
for (const char *p = text; (size_t) (p - text) <= line_len; )
{
eaten = p - text;
hard_assert (utf8_decode (&p, text_len - eaten) >= 0);
}
for (size_t i = 1; i <= text_len && i <= target_len; i++)
if (i == text_len || attrs[i].starts_at_boundary)
eaten = i;
str_append_data (output, text, eaten);
return eaten;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// In practice, this should never fail at all, although it's not guaranteed
static bool
wrap_message (const char *message,
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)
goto error;
int message_left = strlen (message);
while (message_left > line_max)
while (m.len + message_left > (size_t) line_max)
{
struct str m = str_make ();
size_t eaten = wrap_text_for_single_line
(message, message_left, line_max, &m);
(message + i, attrs + i, message_left, line_max - m.len, &m);
if (!eaten)
{
str_free (&m);
goto error;
}
strv_append_owned (output, str_steal (&m));
message += eaten;
message_left -= eaten;
m = str_make ();
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)
strv_append (output, message);
strv_append_owned (output,
xstrdup_printf ("%s%s", m.str, message + i));
free (attrs);
str_free (&m);
return true;
error:
// Well, that's just weird
error_set (e,
free (attrs);
str_free (&m);
return error_set (e,
"Message splitting was unsuccessful as there was "
"too little room for UTF-8 characters");
return false;
}
/// Automatically splits messages that arrive at other clients with our prefix
@@ -12542,8 +12628,6 @@ process_input (struct app_context *ctx, char *user_input)
else
{
struct strv lines = strv_make ();
// XXX: this interprets commands in pasted text
cstr_split (input, "\r\n", false, &lines);
for (size_t i = 0; i < lines.len; i++)
(void) process_input_utf8 (ctx,
@@ -13076,7 +13160,7 @@ dump_input_to_file (struct app_context *ctx, char *template, struct error **e)
if (fd < 0)
return error_set (e, "%s", strerror (errno));
char *input = CALL (ctx->input, get_line);
char *input = CALL_ (ctx->input, get_line, NULL);
bool success = xwrite (fd, input, strlen (input), e);
free (input);
@@ -13104,6 +13188,103 @@ try_dump_input_to_file (struct app_context *ctx)
return NULL;
}
static struct strv
build_editor_command (struct app_context *ctx, const char *filename)
{
struct strv argv = strv_make ();
const char *editor = get_config_string
(ctx->config.root, "behaviour.editor_command");
if (!editor)
{
const char *command;
if (!(command = getenv ("VISUAL"))
&& !(command = getenv ("EDITOR")))
command = "vi";
strv_append (&argv, command);
strv_append (&argv, filename);
return argv;
}
int cursor = 0;
char *input = CALL_ (ctx->input, get_line, &cursor);
hard_assert (cursor >= 0);
mbstate_t ps;
memset (&ps, 0, sizeof ps);
wchar_t wch;
size_t len, processed = 0, line_one_based = 1, column = 0;
while (processed < (size_t) cursor
&& (len = mbrtowc (&wch, input + processed, cursor - processed, &ps))
&& len != (size_t) -2 && len != (size_t) -1)
{
// Both VIM and Emacs use the caret notation with columns.
// Consciously leaving tabs broken, they're too difficult to handle.
int width = wcwidth (wch);
if (width < 0)
width = 2;
processed += len;
if (wch == '\n')
{
line_one_based++;
column = 0;
}
else
column += width;
}
free (input);
// Trivially split the command on spaces and substitute our values
struct str argument = str_make ();
for (; *editor; editor++)
{
if (*editor == ' ')
{
if (argument.len)
{
strv_append_owned (&argv, str_steal (&argument));
argument = str_make ();
}
continue;
}
if (*editor != '%' || !editor[1])
{
str_append_c (&argument, *editor);
continue;
}
// None of them are zero-length, thus words don't get lost
switch (*++editor)
{
case 'F':
str_append (&argument, filename);
break;
case 'L':
str_append_printf (&argument, "%zu", line_one_based);
break;
case 'C':
str_append_printf (&argument, "%zu", column + 1);
break;
case 'B':
str_append_printf (&argument, "%d", cursor + 1);
break;
case '%':
case ' ':
str_append_c (&argument, *editor);
break;
default:
print_warning ("unknown substitution variable");
}
}
if (argument.len)
strv_append_owned (&argv, str_steal (&argument));
else
str_free (&argument);
return argv;
}
static bool
on_edit_input (int count, int key, void *user_data)
{
@@ -13115,16 +13296,15 @@ on_edit_input (int count, int key, void *user_data)
if (!(filename = try_dump_input_to_file (ctx)))
return false;
const char *command;
if (!(command = getenv ("VISUAL"))
&& !(command = getenv ("EDITOR")))
command = "vi";
struct strv argv = build_editor_command (ctx, filename);
if (!argv.len)
strv_append (&argv, "true");
hard_assert (!ctx->running_editor);
switch (spawn_helper_child (ctx))
{
case 0:
execlp (command, command, filename, NULL);
execvp (argv.vector[0], argv.vector);
print_error ("%s: %s",
"Failed to launch editor", strerror (errno));
_exit (EXIT_FAILURE);
@@ -13137,6 +13317,7 @@ on_edit_input (int count, int key, void *user_data)
ctx->running_editor = true;
ctx->editor_filename = filename;
}
strv_free (&argv);
return true;
}
@@ -13417,7 +13598,7 @@ on_insert_attribute (int count, int key, void *user_data)
(void) key;
struct app_context *ctx = user_data;
ctx->awaiting_mirc_escape = true;
ctx->awaiting_formatting_escape = true;
return true;
}
@@ -13451,7 +13632,7 @@ input_add_functions (void *user_data)
XX ("toggle-unimportant", "Toggle junk msgs", on_toggle_unimportant)
XX ("edit-input", "Edit input", on_edit_input)
XX ("redraw-screen", "Redraw screen", on_redraw_screen)
XX ("insert-attribute", "mIRC formatting", on_insert_attribute)
XX ("insert-attribute", "IRC formatting", on_insert_attribute)
XX ("start-paste-mode", "Bracketed paste", on_start_paste_mode)
#undef XX
}
@@ -13622,19 +13803,33 @@ on_editline_complete (EditLine *editline, int key)
// Insert the best match instead
el_insertstr (editline, completions[0]);
// I'm not sure if Readline's menu-complete can at all be implemented
// with Editline--we have no way of detecting what the last executed handler
// was. Employ the formatter's wrapping feature to spew all options.
bool only_match = !completions[1];
if (!only_match)
{
CALL (ctx->input, hide);
redraw_screen (ctx);
struct formatter f = formatter_make (ctx, NULL);
for (char **p = completions; *++p; )
formatter_add (&f, " #l", *p);
formatter_add (&f, "\n");
formatter_flush (&f, stdout, 0);
formatter_free (&f);
CALL (ctx->input, show);
}
for (char **p = completions; *p; p++)
free (*p);
free (completions);
// I'm not sure if Readline's menu-complete can at all be implemented
// with Editline. Spamming the terminal with possible completions
// probably isn't what the user wants and we have no way of detecting
// what the last executed handler was.
if (!only_match)
return CC_REFRESH_BEEP;
// But if there actually is just one match, finish the word
// If there actually is just one match, finish the word
el_insertstr (editline, " ");
return CC_REFRESH;
}
@@ -13695,8 +13890,11 @@ app_editline_init (struct input_el *self)
CALL_ (input, bind_control, 'w', "ed-delete-prev-word");
// Just what are you doing?
CALL_ (input, bind_control, 'u', "vi-kill-line-prev");
// See input_el__redisplay(), functionally important
CALL_ (input, bind_control, 'q', "ed-redisplay");
// We need to hide the prompt and input first
CALL_ (input, bind, "\r", "send-line");
CALL_ (input, bind, "\n", "send-line");
CALL_ (input, bind_control, 'i', "complete");
@@ -13722,9 +13920,9 @@ static const char *g_first_time_help[] =
"F5/Ctrl-P\x02 or \x02" "F6/Ctrl-N\x02.",
"",
"Finally, adding a network is as simple as:",
" - \x02/server add freenode\x02",
" - \x02/set servers.freenode.addresses = \"chat.freenode.net\"\x02",
" - \x02/connect freenode\x02",
" - \x02/server add IRCnet\x02",
" - \x02/set servers.IRCnet.addresses = \"open.ircnet.net\"\x02",
" - \x02/connect IRCnet\x02",
"",
"That should be enough to get you started. Have fun!",
""
@@ -13981,7 +14179,7 @@ on_signal_pipe_readable (const struct pollfd *fd, struct app_context *ctx)
}
static void
process_mirc_escape (const struct pollfd *fd, struct app_context *ctx)
process_formatting_escape (const struct pollfd *fd, struct app_context *ctx)
{
// There's no other way with libedit, as both el_getc() in a function
// handler and CC_ARGHACK would block execution
@@ -14033,11 +14231,45 @@ error:
CALL (ctx->input, ding);
done:
str_reset (buf);
ctx->awaiting_mirc_escape = false;
ctx->awaiting_formatting_escape = false;
}
#define BRACKETED_PASTE_LIMIT 102400 ///< How much text can be pasted
static bool
insert_paste (struct app_context *ctx, char *paste, size_t len)
{
if (!get_config_boolean (ctx->config.root, "behaviour.process_pasted_text"))
return CALL_ (ctx->input, insert, paste);
// Without ICRNL, which Editline keeps but Readline doesn't,
// the terminal sends newlines as carriage returns (seen on urxvt)
for (size_t i = 0; i < len; i++)
if (paste[i] == '\r')
paste[i] = '\n';
int position = 0;
char *input = CALL_ (ctx->input, get_line, &position);
bool quote_first_slash = !position || strchr ("\r\n", input[position - 1]);
free (input);
// Executing commands by accident is much more common than pasting them
// intentionally, although the latter may also have security consequences
struct str processed = str_make ();
str_reserve (&processed, len);
for (size_t i = 0; i < len; i++)
{
if (paste[i] == '/'
&& ((!i && quote_first_slash) || (i && paste[i - 1] == '\n')))
str_append_c (&processed, paste[i]);
str_append_c (&processed, paste[i]);
}
bool success = CALL_ (ctx->input, insert, processed.str);
str_free (&processed);
return success;
}
static void
process_bracketed_paste (const struct pollfd *fd, struct app_context *ctx)
{
@@ -14062,7 +14294,7 @@ process_bracketed_paste (const struct pollfd *fd, struct app_context *ctx)
(int) (text_len = BRACKETED_PASTE_LIMIT));
buf->str[text_len] = '\0';
if (CALL_ (ctx->input, insert, buf->str))
if (insert_paste (ctx, buf->str, text_len))
goto done;
error:
@@ -14129,8 +14361,8 @@ on_tty_readable (const struct pollfd *fd, struct app_context *ctx)
if (fd->revents & ~(POLLIN | POLLHUP | POLLERR))
print_debug ("fd %d: unexpected revents: %d", fd->fd, fd->revents);
if (ctx->awaiting_mirc_escape)
process_mirc_escape (fd, ctx);
if (ctx->awaiting_formatting_escape)
process_formatting_escape (fd, ctx);
else if (ctx->in_bracketed_paste)
process_bracketed_paste (fd, ctx);
else if (!ctx->quitting)
@@ -14303,9 +14535,11 @@ test_aliases (void)
static void
test_wrapping (void)
{
static const char *message = " foo bar foobar fóóbárbáz";
static const char *split[] =
{ " foo", "bar", "foob", "ar", "", "ób", "árb", "áz" };
static const char *message = " foo bar foobar fóóbárbáz\002 a\0031 b";
// XXX: formatting continuation order is implementation-dependent here
// (irc_serialize_char_attrs() makes a choice in serialization)
static const char *split[] = { " foo", "bar", "foob", "ar",
"", "ób", "árb", "áz\x02", "\002a\0031", "\0031\002b" };
struct strv v = strv_make ();
hard_assert (wrap_message (message, 4, &v, NULL));

20
xD.c
View File

@@ -1,7 +1,7 @@
/*
* xD.c: an IRC daemon
*
* Copyright (c) 2014 - 2020, Přemysl Eric Janouch <p@janouch.name>
* Copyright (c) 2014 - 2021, Přemysl Eric Janouch <p@janouch.name>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
@@ -22,7 +22,9 @@
#define WANT_SYSLOG_LOGGING
#include "common.c"
#include "xD-replies.c"
#include <nl_types.h>
#include <sys/resource.h>
enum { PIPE_READ, PIPE_WRITE };
@@ -3984,6 +3986,21 @@ daemonize (struct server_context *ctx)
poller_post_fork (&ctx->poller);
}
static void
setup_limits (void)
{
struct rlimit limit;
if (getrlimit (RLIMIT_NOFILE, &limit))
{
print_warning ("%s: %s", "getrlimit", strerror (errno));
return;
}
limit.rlim_cur = limit.rlim_max;
if (setrlimit (RLIMIT_NOFILE, &limit))
print_warning ("%s: %s", "setrlimit", strerror (errno));
}
int
main (int argc, char *argv[])
{
@@ -4030,6 +4047,7 @@ main (int argc, char *argv[])
print_status (PROGRAM_NAME " " PROGRAM_VERSION " starting");
setup_signal_handlers ();
setup_limits ();
init_openssl ();
struct server_context ctx;