Compare commits
	
		
			17 Commits
		
	
	
		
			v1.3.0
			...
			5165f76b7c
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						
						
							
						
						5165f76b7c
	
				 | 
					
					
						|||
| 
						
						
							
						
						92ac13f3c6
	
				 | 
					
					
						|||
| 
						
						
							
						
						df4ca74580
	
				 | 
					
					
						|||
| 
						
						
							
						
						9e297244a4
	
				 | 
					
					
						|||
| 
						
						
							
						
						d32ba133c0
	
				 | 
					
					
						|||
| 
						
						
							
						
						ce3976e1ec
	
				 | 
					
					
						|||
| 
						
						
							
						
						e5ed89646b
	
				 | 
					
					
						|||
| 
						
						
							
						
						5e728f6d31
	
				 | 
					
					
						|||
| 
						
						
							
						
						766f68e070
	
				 | 
					
					
						|||
| 
						
						
							
						
						3dc5242d43
	
				 | 
					
					
						|||
| 
						
						
							
						
						fd9d5db1d2
	
				 | 
					
					
						|||
| 
						
						
							
						
						cb480b4c71
	
				 | 
					
					
						|||
| 
						
						
							
						
						59cc423694
	
				 | 
					
					
						|||
| 
						
						
							
						
						9323089d66
	
				 | 
					
					
						|||
| 
						
						
							
						
						de7df1f60d
	
				 | 
					
					
						|||
| 
						
						
							
						
						b082e82b62
	
				 | 
					
					
						|||
| 
						
						
							
						
						b8dbc70a9c
	
				 | 
					
					
						
							
								
								
									
										32
									
								
								.clang-format
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								.clang-format
									
									
									
									
									
										Normal 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
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -7,3 +7,5 @@
 | 
			
		||||
/uirc3.files
 | 
			
		||||
/uirc3.creator*
 | 
			
		||||
/uirc3.includes
 | 
			
		||||
/uirc3.cflags
 | 
			
		||||
/uirc3.cxxflags
 | 
			
		||||
 
 | 
			
		||||
@@ -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
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								NEWS
									
									
									
									
									
								
							@@ -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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										24
									
								
								common.c
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								common.c
									
									
									
									
									
								
							@@ -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 -----------------------------------------------------------------
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								liberty
									
									
									
									
									
								
							
							
								
								
								
								
								
							
						
						
									
										2
									
								
								liberty
									
									
									
									
									
								
							 Submodule liberty updated: 9639777814...1b9d89cab3
									
								
							
							
								
								
									
										460
									
								
								xC.c
									
									
									
									
									
								
							
							
						
						
									
										460
									
								
								xC.c
									
									
									
									
									
								
							@@ -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", "fó", "ó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",
 | 
			
		||||
		"fó", "ób", "árb", "áz\x02", "\002a\0031", "\0031\002b" };
 | 
			
		||||
 | 
			
		||||
	struct strv v = strv_make ();
 | 
			
		||||
	hard_assert (wrap_message (message, 4, &v, NULL));
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										20
									
								
								xD.c
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								xD.c
									
									
									
									
									
								
							@@ -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;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user