degesch: add output text formatting
This commit is contained in:
		
							
								
								
									
										470
									
								
								degesch.c
									
									
									
									
									
								
							
							
						
						
									
										470
									
								
								degesch.c
									
									
									
									
									
								
							@@ -27,6 +27,11 @@
 | 
			
		||||
#define ATTR_WARNING   "attr_warning"
 | 
			
		||||
#define ATTR_ERROR     "attr_error"
 | 
			
		||||
 | 
			
		||||
#define ATTR_TIMESTAMP "attr_timestamp"
 | 
			
		||||
#define ATTR_ACTION    "attr_action"
 | 
			
		||||
#define ATTR_JOIN      "attr_join"
 | 
			
		||||
#define ATTR_PART      "attr_part"
 | 
			
		||||
 | 
			
		||||
// User data for logger functions to enable formatted logging
 | 
			
		||||
#define print_fatal_data    ATTR_ERROR
 | 
			
		||||
#define print_error_data    ATTR_ERROR
 | 
			
		||||
@@ -86,6 +91,11 @@ static struct config_item g_config_table[] =
 | 
			
		||||
	{ ATTR_WARNING,      NULL,    "Terminal attributes for warnings"         },
 | 
			
		||||
	{ ATTR_ERROR,        NULL,    "Terminal attributes for errors"           },
 | 
			
		||||
 | 
			
		||||
	{ ATTR_TIMESTAMP,    NULL,    "Terminal attributes for timestamps"       },
 | 
			
		||||
	{ ATTR_ACTION,       NULL,    "Terminal attributes for user actions"     },
 | 
			
		||||
	{ ATTR_JOIN,         NULL,    "Terminal attributes for joins"            },
 | 
			
		||||
	{ ATTR_PART,         NULL,    "Terminal attributes for parts"            },
 | 
			
		||||
 | 
			
		||||
	{ NULL,              NULL,    NULL                                       }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -94,6 +104,12 @@ static struct config_item g_config_table[] =
 | 
			
		||||
// All text stored in our data structures is encoded in UTF-8.
 | 
			
		||||
// Or at least should be.  The exception is IRC identifiers.
 | 
			
		||||
 | 
			
		||||
static bool
 | 
			
		||||
isdigit_ascii (int c)
 | 
			
		||||
{
 | 
			
		||||
	return c >= '0' && c <= '9';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Shorthand to set an error and return failure from the function
 | 
			
		||||
#define FAIL(...)                                                              \
 | 
			
		||||
	BLOCK_START                                                                \
 | 
			
		||||
@@ -575,7 +591,8 @@ static struct
 | 
			
		||||
	bool stdout_is_tty;                 ///< `stdout' is a terminal
 | 
			
		||||
	bool stderr_is_tty;                 ///< `stderr' is a terminal
 | 
			
		||||
 | 
			
		||||
	char *color_set[8];                 ///< Codes to set the foreground colour
 | 
			
		||||
	char *color_set_fg[8];              ///< Codes to set the foreground colour
 | 
			
		||||
	char *color_set_bg[8];              ///< Codes to set the background colour
 | 
			
		||||
}
 | 
			
		||||
g_terminal;
 | 
			
		||||
 | 
			
		||||
@@ -593,15 +610,20 @@ init_terminal (void)
 | 
			
		||||
		return false;
 | 
			
		||||
 | 
			
		||||
	// Make sure all terminal features used by us are supported
 | 
			
		||||
	if (!set_a_foreground || !enter_bold_mode || !exit_attribute_mode)
 | 
			
		||||
	if (!set_a_foreground || !set_a_background
 | 
			
		||||
	 || !enter_bold_mode || !exit_attribute_mode)
 | 
			
		||||
	{
 | 
			
		||||
		del_curterm (cur_term);
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for (size_t i = 0; i < N_ELEMENTS (g_terminal.color_set); i++)
 | 
			
		||||
		g_terminal.color_set[i] = xstrdup (tparm (set_a_foreground,
 | 
			
		||||
	for (size_t i = 0; i < N_ELEMENTS (g_terminal.color_set_fg); i++)
 | 
			
		||||
	{
 | 
			
		||||
		g_terminal.color_set_fg[i] = xstrdup (tparm (set_a_foreground,
 | 
			
		||||
			i, 0, 0, 0, 0, 0, 0, 0, 0));
 | 
			
		||||
		g_terminal.color_set_bg[i] = xstrdup (tparm (set_a_background,
 | 
			
		||||
			i, 0, 0, 0, 0, 0, 0, 0, 0));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return g_terminal.initialized = true;
 | 
			
		||||
}
 | 
			
		||||
@@ -612,8 +634,11 @@ free_terminal (void)
 | 
			
		||||
	if (!g_terminal.initialized)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	for (size_t i = 0; i < N_ELEMENTS (g_terminal.color_set); i++)
 | 
			
		||||
		free (g_terminal.color_set[i]);
 | 
			
		||||
	for (size_t i = 0; i < N_ELEMENTS (g_terminal.color_set_fg); i++)
 | 
			
		||||
	{
 | 
			
		||||
		free (g_terminal.color_set_fg[i]);
 | 
			
		||||
		free (g_terminal.color_set_bg[i]);
 | 
			
		||||
	}
 | 
			
		||||
	del_curterm (cur_term);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -738,10 +763,15 @@ init_colors (struct app_context *ctx)
 | 
			
		||||
#define INIT_ATTR(id, ti, vt100) \
 | 
			
		||||
	str_map_set (&ctx->config, (id), xstrdup (have_ti ? (ti) : (vt100)));
 | 
			
		||||
 | 
			
		||||
	INIT_ATTR (ATTR_PROMPT,   enter_bold_mode,         "\x1b[1m");
 | 
			
		||||
	INIT_ATTR (ATTR_RESET,    exit_attribute_mode,     "\x1b[0m");
 | 
			
		||||
	INIT_ATTR (ATTR_WARNING,  g_terminal.color_set[3], "\x1b[33m");
 | 
			
		||||
	INIT_ATTR (ATTR_ERROR,    g_terminal.color_set[1], "\x1b[31m");
 | 
			
		||||
	INIT_ATTR (ATTR_PROMPT,    enter_bold_mode,            "\x1b[1m");
 | 
			
		||||
	INIT_ATTR (ATTR_RESET,     exit_attribute_mode,        "\x1b[0m");
 | 
			
		||||
	INIT_ATTR (ATTR_WARNING,   g_terminal.color_set_fg[3], "\x1b[33m");
 | 
			
		||||
	INIT_ATTR (ATTR_ERROR,     g_terminal.color_set_fg[1], "\x1b[31m");
 | 
			
		||||
 | 
			
		||||
	INIT_ATTR (ATTR_TIMESTAMP, g_terminal.color_set_fg[7], "\x1b[37m");
 | 
			
		||||
	INIT_ATTR (ATTR_ACTION,    g_terminal.color_set_fg[1], "\x1b[31m");
 | 
			
		||||
	INIT_ATTR (ATTR_JOIN,      g_terminal.color_set_fg[2], "\x1b[32m");
 | 
			
		||||
	INIT_ATTR (ATTR_PART,      g_terminal.color_set_fg[1], "\x1b[31m");
 | 
			
		||||
 | 
			
		||||
#undef INIT_ATTR
 | 
			
		||||
 | 
			
		||||
@@ -829,6 +859,274 @@ setup_signal_handlers (void)
 | 
			
		||||
		exit_fatal ("sigaction: %s", strerror (errno));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// --- Output formatter --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
// This complicated piece of code makes attributed text formatting simple.
 | 
			
		||||
// We use a printf-inspired syntax to push attributes and text to the object,
 | 
			
		||||
// then flush it either to a terminal, or a log file with formatting stripped.
 | 
			
		||||
//
 | 
			
		||||
// Format strings use a #-quoted notation, to differentiate from printf:
 | 
			
		||||
//   #s inserts a string
 | 
			
		||||
//   #d inserts a signed integer; also supports the #<N> and #0<N> notation
 | 
			
		||||
//
 | 
			
		||||
//   #a inserts named attributes (auto-resets)
 | 
			
		||||
//   #r resets terminal attributes
 | 
			
		||||
//   #c sets foreground color
 | 
			
		||||
//   #C sets background color
 | 
			
		||||
 | 
			
		||||
enum formatter_item_type
 | 
			
		||||
{
 | 
			
		||||
	FORMATTER_ITEM_TEXT,                ///< Text
 | 
			
		||||
	FORMATTER_ITEM_ATTR,                ///< Named formatting attributes
 | 
			
		||||
	FORMATTER_ITEM_FG_COLOR,            ///< Foreground color
 | 
			
		||||
	FORMATTER_ITEM_BG_COLOR             ///< Background color
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct formatter_item
 | 
			
		||||
{
 | 
			
		||||
	LIST_HEADER (struct formatter_item)
 | 
			
		||||
 | 
			
		||||
	enum formatter_item_type type;      ///< Type of this item
 | 
			
		||||
	int color;                          ///< Color
 | 
			
		||||
	char *data;                         ///< Either text or an attribute string
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static struct formatter_item *
 | 
			
		||||
formatter_item_new (void)
 | 
			
		||||
{
 | 
			
		||||
	struct formatter_item *self = xcalloc (1, sizeof *self);
 | 
			
		||||
	return self;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
formatter_item_destroy (struct formatter_item *self)
 | 
			
		||||
{
 | 
			
		||||
	free (self->data);
 | 
			
		||||
	free (self);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
			
		||||
 | 
			
		||||
struct formatter
 | 
			
		||||
{
 | 
			
		||||
	struct app_context *ctx;            ///< Application context
 | 
			
		||||
 | 
			
		||||
	struct formatter_item *items;       ///< Items
 | 
			
		||||
	struct formatter_item *items_tail;  ///< Tail of items
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
formatter_init (struct formatter *self, struct app_context *ctx)
 | 
			
		||||
{
 | 
			
		||||
	memset (self, 0, sizeof *self);
 | 
			
		||||
	self->ctx = ctx;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
formatter_free (struct formatter *self)
 | 
			
		||||
{
 | 
			
		||||
	LIST_FOR_EACH (struct formatter_item, iter, self->items)
 | 
			
		||||
		formatter_item_destroy (iter);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct formatter_item *
 | 
			
		||||
formatter_add_blank (struct formatter *self)
 | 
			
		||||
{
 | 
			
		||||
	struct formatter_item *item = formatter_item_new ();
 | 
			
		||||
	LIST_APPEND_WITH_TAIL (self->items, self->items_tail, item);
 | 
			
		||||
	return item;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
formatter_add_attr (struct formatter *self, const char *attr_name)
 | 
			
		||||
{
 | 
			
		||||
	struct formatter_item *item = formatter_add_blank (self);
 | 
			
		||||
	item->type = FORMATTER_ITEM_ATTR;
 | 
			
		||||
	item->data = xstrdup (str_map_find (&self->ctx->config, attr_name));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
formatter_add_reset (struct formatter *self)
 | 
			
		||||
{
 | 
			
		||||
	struct formatter_item *item = formatter_add_blank (self);
 | 
			
		||||
	item->type = FORMATTER_ITEM_ATTR;
 | 
			
		||||
	item->data = NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
formatter_add_text (struct formatter *self, const char *text)
 | 
			
		||||
{
 | 
			
		||||
	struct formatter_item *item = formatter_add_blank (self);
 | 
			
		||||
	item->type = FORMATTER_ITEM_TEXT;
 | 
			
		||||
	item->data = xstrdup (text);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
formatter_add_fg_color (struct formatter *self, int color)
 | 
			
		||||
{
 | 
			
		||||
	struct formatter_item *item = formatter_add_blank (self);
 | 
			
		||||
	item->type = FORMATTER_ITEM_FG_COLOR;
 | 
			
		||||
	item->color = color;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
formatter_add_bg_color (struct formatter *self, int color)
 | 
			
		||||
{
 | 
			
		||||
	struct formatter_item *item = formatter_add_blank (self);
 | 
			
		||||
	item->type = FORMATTER_ITEM_BG_COLOR;
 | 
			
		||||
	item->color = color;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const char *
 | 
			
		||||
formatter_parse_field (struct formatter *self,
 | 
			
		||||
	const char *field, struct str *buf, va_list *ap)
 | 
			
		||||
{
 | 
			
		||||
	size_t width = 0;
 | 
			
		||||
	bool zero_padded = false;
 | 
			
		||||
	int c;
 | 
			
		||||
 | 
			
		||||
restart:
 | 
			
		||||
	switch ((c = *field++))
 | 
			
		||||
	{
 | 
			
		||||
		char *s;
 | 
			
		||||
 | 
			
		||||
		// We can push boring text content to the caller's buffer
 | 
			
		||||
		// and let it flush the buffer only when it's actually needed
 | 
			
		||||
	case 's':
 | 
			
		||||
		s = va_arg (*ap, char *);
 | 
			
		||||
		for (size_t len = strlen (s); len < width; len++)
 | 
			
		||||
			str_append_c (buf, ' ');
 | 
			
		||||
		str_append (buf, s);
 | 
			
		||||
		break;
 | 
			
		||||
	case 'd':
 | 
			
		||||
		s = xstrdup_printf ("%d", va_arg (*ap, int));
 | 
			
		||||
		for (size_t len = strlen (s); len < width; len++)
 | 
			
		||||
			str_append_c (buf, " 0"[zero_padded]);
 | 
			
		||||
		str_append (buf, s);
 | 
			
		||||
		free (s);
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
	case 'a':
 | 
			
		||||
		formatter_add_attr     (self, va_arg (*ap, const char *));
 | 
			
		||||
		break;
 | 
			
		||||
	case 'c':
 | 
			
		||||
		formatter_add_fg_color (self, va_arg (*ap, int));
 | 
			
		||||
		break;
 | 
			
		||||
	case 'C':
 | 
			
		||||
		formatter_add_bg_color (self, va_arg (*ap, int));
 | 
			
		||||
		break;
 | 
			
		||||
	case 'r':
 | 
			
		||||
		formatter_add_reset    (self);
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
	default:
 | 
			
		||||
		if (c == '0' && !zero_padded)
 | 
			
		||||
			zero_padded = true;
 | 
			
		||||
		else if (isdigit_ascii (c))
 | 
			
		||||
			width = width * 10 + (c - '0');
 | 
			
		||||
		else if (c)
 | 
			
		||||
			hard_assert (!"unexpected format specifier");
 | 
			
		||||
		else
 | 
			
		||||
			hard_assert (!"unexpected end of format string");
 | 
			
		||||
		goto restart;
 | 
			
		||||
	}
 | 
			
		||||
	return field;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
formatter_add (struct formatter *self, const char *format, ...)
 | 
			
		||||
{
 | 
			
		||||
	struct str buf;
 | 
			
		||||
	str_init (&buf);
 | 
			
		||||
 | 
			
		||||
	va_list ap;
 | 
			
		||||
	va_start (ap, format);
 | 
			
		||||
 | 
			
		||||
	while (*format)
 | 
			
		||||
	{
 | 
			
		||||
		if (*format != '#' || *++format == '#')
 | 
			
		||||
		{
 | 
			
		||||
			str_append_c (&buf, *format++);
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
		if (buf.len)
 | 
			
		||||
		{
 | 
			
		||||
			formatter_add_text (self, buf.str);
 | 
			
		||||
			str_reset (&buf);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		format = formatter_parse_field (self, format, &buf, &ap);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (buf.len)
 | 
			
		||||
		formatter_add_text (self, buf.str);
 | 
			
		||||
 | 
			
		||||
	str_free (&buf);
 | 
			
		||||
	va_end (ap);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
formatter_flush (struct formatter *self, FILE *stream)
 | 
			
		||||
{
 | 
			
		||||
	terminal_printer_fn printer = get_attribute_printer (stream);
 | 
			
		||||
 | 
			
		||||
	const char *attr_reset = str_map_find (&self->ctx->config, ATTR_RESET);
 | 
			
		||||
	if (printer)
 | 
			
		||||
		tputs (attr_reset, 1, printer);
 | 
			
		||||
 | 
			
		||||
	bool is_attributed = false;
 | 
			
		||||
	bool is_tty = isatty (fileno (stream));
 | 
			
		||||
	LIST_FOR_EACH (struct formatter_item, iter, self->items)
 | 
			
		||||
	{
 | 
			
		||||
		switch (iter->type)
 | 
			
		||||
		{
 | 
			
		||||
		case FORMATTER_ITEM_TEXT:
 | 
			
		||||
			if (is_tty)
 | 
			
		||||
			{
 | 
			
		||||
				char *term = iconv_xstrdup
 | 
			
		||||
					(self->ctx->term_from_utf8, iter->data, -1, NULL);
 | 
			
		||||
				fputs (term, stream);
 | 
			
		||||
				free (term);
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
				fputs (iter->data, stream);
 | 
			
		||||
			break;
 | 
			
		||||
		case FORMATTER_ITEM_ATTR:
 | 
			
		||||
			if (!printer)
 | 
			
		||||
				continue;
 | 
			
		||||
 | 
			
		||||
			if (is_attributed)
 | 
			
		||||
			{
 | 
			
		||||
				tputs (attr_reset, 1, printer);
 | 
			
		||||
				is_attributed = false;
 | 
			
		||||
			}
 | 
			
		||||
			if (iter->data)
 | 
			
		||||
			{
 | 
			
		||||
				tputs (iter->data, 1, printer);
 | 
			
		||||
				is_attributed = true;
 | 
			
		||||
			}
 | 
			
		||||
			break;
 | 
			
		||||
		case FORMATTER_ITEM_FG_COLOR:
 | 
			
		||||
			if (!printer)
 | 
			
		||||
				continue;
 | 
			
		||||
 | 
			
		||||
			tputs (g_terminal.color_set_fg[iter->color], 1, printer);
 | 
			
		||||
			is_attributed = true;
 | 
			
		||||
			break;
 | 
			
		||||
		case FORMATTER_ITEM_BG_COLOR:
 | 
			
		||||
			if (!printer)
 | 
			
		||||
				continue;
 | 
			
		||||
 | 
			
		||||
			tputs (g_terminal.color_set_bg[iter->color], 1, printer);
 | 
			
		||||
			is_attributed = true;
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (is_attributed)
 | 
			
		||||
		tputs (attr_reset, 1, printer);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// --- Buffers -----------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
@@ -856,117 +1154,117 @@ buffer_update_time (struct app_context *ctx, time_t now)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
buffer_line_display (struct app_context *ctx, struct buffer_line *line)
 | 
			
		||||
buffer_line_display (struct app_context *ctx,
 | 
			
		||||
	struct buffer_line *line, bool is_external)
 | 
			
		||||
{
 | 
			
		||||
	// Normal timestamps don't include the date, this way the user won't be
 | 
			
		||||
	// confused as to when an event has happened
 | 
			
		||||
	buffer_update_time (ctx, line->when);
 | 
			
		||||
 | 
			
		||||
	struct str output;
 | 
			
		||||
	str_init (&output);
 | 
			
		||||
	struct buffer_line_args *a = &line->args;
 | 
			
		||||
 | 
			
		||||
	char *nick = NULL;
 | 
			
		||||
	const char *userhost = NULL;
 | 
			
		||||
	int nick_color = -1;
 | 
			
		||||
	int object_color = -1;
 | 
			
		||||
 | 
			
		||||
	if (a->who)
 | 
			
		||||
	{
 | 
			
		||||
		nick = irc_cut_nickname (a->who);
 | 
			
		||||
		userhost = irc_find_userhost (a->who);
 | 
			
		||||
		nick_color = str_map_hash (nick, strlen (nick)) % 8;
 | 
			
		||||
	}
 | 
			
		||||
	if (a->object)
 | 
			
		||||
		object_color = str_map_hash (a->object, strlen (a->object)) % 8;
 | 
			
		||||
 | 
			
		||||
	struct formatter f;
 | 
			
		||||
	formatter_init (&f, ctx);
 | 
			
		||||
 | 
			
		||||
	struct tm current;
 | 
			
		||||
	if (!localtime_r (&line->when, ¤t))
 | 
			
		||||
		print_error ("%s: %s", "localtime_r", strerror (errno));
 | 
			
		||||
	else
 | 
			
		||||
		str_append_printf (&output, "%02d:%02d:%02d ",
 | 
			
		||||
			current.tm_hour, current.tm_min, current.tm_sec);
 | 
			
		||||
		formatter_add (&f, "#a#02d:#02d:#02d#r ",
 | 
			
		||||
			ATTR_TIMESTAMP, current.tm_hour, current.tm_min, current.tm_sec);
 | 
			
		||||
 | 
			
		||||
#define GET_FIELD(name)  char *name = line->args.name                                  \
 | 
			
		||||
	? iconv_xstrdup (ctx->term_from_utf8, line->args.name, -1, NULL) : NULL
 | 
			
		||||
	// TODO: when this comes from a different buffer (is_external),
 | 
			
		||||
	//   ignore all attributes and instead print it with ATTR_OTHER
 | 
			
		||||
 | 
			
		||||
	GET_FIELD (who);
 | 
			
		||||
	GET_FIELD (object);
 | 
			
		||||
	GET_FIELD (text);
 | 
			
		||||
	GET_FIELD (reason);
 | 
			
		||||
 | 
			
		||||
#undef GET_FIELD
 | 
			
		||||
 | 
			
		||||
	// TODO: colorize the output, note that we shouldn't put everything through
 | 
			
		||||
	//   tputs but only the attribute strings.  That might prove a bit
 | 
			
		||||
	//   challenging.  Maybe we could create a helper object to pust text
 | 
			
		||||
	//   and formatting into.  We could have a varargs function to make it a bit
 | 
			
		||||
	//   more friendly, e.g. push(&x, ATTR_JOIN, "--> ", ATTR_RESET, who, NULL)
 | 
			
		||||
 | 
			
		||||
	char *nick = NULL;
 | 
			
		||||
	const char *userhost = NULL;
 | 
			
		||||
 | 
			
		||||
	if (who)
 | 
			
		||||
	{
 | 
			
		||||
		nick = irc_cut_nickname (who);
 | 
			
		||||
		userhost = irc_find_userhost (who);
 | 
			
		||||
	}
 | 
			
		||||
	// TODO: try to decode as much as possible using mIRC formatting;
 | 
			
		||||
	//   could either add a #m format specifier, or write a separate function
 | 
			
		||||
	//   to translate the formatting into formatter API calls
 | 
			
		||||
 | 
			
		||||
	switch (line->type)
 | 
			
		||||
	{
 | 
			
		||||
	case BUFFER_LINE_PRIVMSG:
 | 
			
		||||
		str_append_printf (&output, "<%s> %s", nick, text);
 | 
			
		||||
		formatter_add (&f, "<#c#s#r> #s", nick_color, nick, a->text);
 | 
			
		||||
		break;
 | 
			
		||||
	case BUFFER_LINE_ACTION:
 | 
			
		||||
		str_append_printf (&output, " *  %s %s", nick, text);
 | 
			
		||||
		formatter_add (&f, " #a*#r  ", ATTR_ACTION);
 | 
			
		||||
		formatter_add (&f, "#c#s#r #s", nick_color, nick, a->text);
 | 
			
		||||
		break;
 | 
			
		||||
	case BUFFER_LINE_NOTICE:
 | 
			
		||||
		str_append_printf (&output, " -  Notice(%s): %s", nick, text);
 | 
			
		||||
		formatter_add (&f, " -  ");
 | 
			
		||||
		formatter_add (&f, "#s(#c#s#r): #s",
 | 
			
		||||
			"Notice", nick_color, nick, a->text);
 | 
			
		||||
		break;
 | 
			
		||||
	case BUFFER_LINE_JOIN:
 | 
			
		||||
		if (who)
 | 
			
		||||
			str_append_printf (&output, "--> %s (%s) has joined %s",
 | 
			
		||||
				nick, userhost, object);
 | 
			
		||||
		else
 | 
			
		||||
			str_append_printf (&output, "--> You have joined %s", object);
 | 
			
		||||
		formatter_add (&f, "#a-->#r ", ATTR_JOIN);
 | 
			
		||||
		formatter_add (&f, "#c#s#r (#s) #a#s#r #s",
 | 
			
		||||
			nick_color, nick, userhost,
 | 
			
		||||
			ATTR_JOIN, "has joined", a->object);
 | 
			
		||||
		break;
 | 
			
		||||
	case BUFFER_LINE_PART:
 | 
			
		||||
		if (who)
 | 
			
		||||
			str_append_printf (&output, "<-- %s (%s) has left %s (%s)",
 | 
			
		||||
				nick, userhost, object, reason);
 | 
			
		||||
		else
 | 
			
		||||
			str_append_printf (&output, "<-- You have left %s (%s)",
 | 
			
		||||
				object, reason);
 | 
			
		||||
		formatter_add (&f, "#a<--#r ", ATTR_PART);
 | 
			
		||||
		formatter_add (&f, "#c#s#r (#s) #a#s#r #s",
 | 
			
		||||
			nick_color, nick, userhost,
 | 
			
		||||
			ATTR_PART, "has left", a->object);
 | 
			
		||||
		if (a->reason)
 | 
			
		||||
			formatter_add (&f, " (#s)", a->reason);
 | 
			
		||||
		break;
 | 
			
		||||
	case BUFFER_LINE_KICK:
 | 
			
		||||
		if (who)
 | 
			
		||||
			str_append_printf (&output, "<-- %s has kicked %s (%s)",
 | 
			
		||||
				nick, object, reason);
 | 
			
		||||
		else
 | 
			
		||||
			str_append_printf (&output, "<-- You have kicked %s (%s)",
 | 
			
		||||
				object, reason);
 | 
			
		||||
		formatter_add (&f, "#a<--#r ", ATTR_PART);
 | 
			
		||||
		formatter_add (&f, "#c#s#r (#s) #a#s#r #c#s#r",
 | 
			
		||||
			nick_color, nick, userhost,
 | 
			
		||||
			ATTR_PART, "has kicked", object_color, a->object);
 | 
			
		||||
		if (a->reason)
 | 
			
		||||
			formatter_add (&f, " (#s)", a->reason);
 | 
			
		||||
		break;
 | 
			
		||||
	case BUFFER_LINE_NICK:
 | 
			
		||||
		if (who)
 | 
			
		||||
			str_append_printf (&output, " -  %s is now known as %s",
 | 
			
		||||
				nick, object);
 | 
			
		||||
		formatter_add (&f, " -  ");
 | 
			
		||||
		if (a->who)
 | 
			
		||||
			formatter_add (&f, "#c#s#r #s #c#s#r",
 | 
			
		||||
				nick_color, nick,
 | 
			
		||||
				"is now known as", object_color, a->object);
 | 
			
		||||
		else
 | 
			
		||||
			str_append_printf (&output, " -  You are now known as %s", object);
 | 
			
		||||
			formatter_add (&f, "#s #s",
 | 
			
		||||
				"You are now known as", a->object);
 | 
			
		||||
		break;
 | 
			
		||||
	case BUFFER_LINE_TOPIC:
 | 
			
		||||
		if (who)
 | 
			
		||||
			str_append_printf (&output,
 | 
			
		||||
				" -  %s has changed the topic to: %s", nick, text);
 | 
			
		||||
		else
 | 
			
		||||
			str_append_printf (&output,
 | 
			
		||||
				" -  You have changed the topic to: %s", text);
 | 
			
		||||
		formatter_add (&f, " -  ");
 | 
			
		||||
		formatter_add (&f, "#c#s#r #s \"#s\"",
 | 
			
		||||
			nick_color, nick,
 | 
			
		||||
			"has changed the topic to", a->text);
 | 
			
		||||
		break;
 | 
			
		||||
	case BUFFER_LINE_QUIT:
 | 
			
		||||
		if (who)
 | 
			
		||||
			str_append_printf (&output, "<-- %s (%s) has quit (%s)",
 | 
			
		||||
				nick, userhost, reason);
 | 
			
		||||
		else
 | 
			
		||||
			str_append_printf (&output, "<-- You have quit (%s)", reason);
 | 
			
		||||
		formatter_add (&f, "#a<--#r ", ATTR_PART);
 | 
			
		||||
		formatter_add (&f, "#c#s#r (%s) #a#s#r",
 | 
			
		||||
			nick_color, nick, userhost,
 | 
			
		||||
			ATTR_PART, "has quit");
 | 
			
		||||
		if (a->reason)
 | 
			
		||||
			formatter_add (&f, " (#s)", a->reason);
 | 
			
		||||
		break;
 | 
			
		||||
	case BUFFER_LINE_STATUS:
 | 
			
		||||
		str_append_printf (&output, " -  %s", text);
 | 
			
		||||
		formatter_add (&f, " -  ");
 | 
			
		||||
		formatter_add (&f, "#s", a->text);
 | 
			
		||||
		break;
 | 
			
		||||
	case BUFFER_LINE_ERROR:
 | 
			
		||||
		str_append_printf (&output, "=!= %s", text);
 | 
			
		||||
		formatter_add (&f, "#a=!=#r ", ATTR_ERROR);
 | 
			
		||||
		formatter_add (&f, "#s", a->text);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	free (nick);
 | 
			
		||||
 | 
			
		||||
	free (who);
 | 
			
		||||
	free (object);
 | 
			
		||||
	free (text);
 | 
			
		||||
	free (reason);
 | 
			
		||||
 | 
			
		||||
	struct app_readline_state state;
 | 
			
		||||
	if (ctx->readline_prompt_shown)
 | 
			
		||||
		app_readline_hide (&state);
 | 
			
		||||
@@ -974,8 +1272,9 @@ buffer_line_display (struct app_context *ctx, struct buffer_line *line)
 | 
			
		||||
	// TODO: write the line to a log file; note that the global and server
 | 
			
		||||
	//   buffers musn't collide with filenames
 | 
			
		||||
 | 
			
		||||
	printf ("%s\n", output.str);
 | 
			
		||||
	str_free (&output);
 | 
			
		||||
	formatter_add (&f, "\n");
 | 
			
		||||
	formatter_flush (&f, stdout);
 | 
			
		||||
	formatter_free (&f);
 | 
			
		||||
 | 
			
		||||
	if (ctx->readline_prompt_shown)
 | 
			
		||||
		app_readline_restore (&state, ctx->readline_prompt);
 | 
			
		||||
@@ -996,11 +1295,10 @@ buffer_send_internal (struct app_context *ctx, struct buffer *buffer,
 | 
			
		||||
	buffer->lines_count++;
 | 
			
		||||
 | 
			
		||||
	if (buffer == ctx->current_buffer)
 | 
			
		||||
		buffer_line_display (ctx, line);
 | 
			
		||||
		buffer_line_display (ctx, line, false);
 | 
			
		||||
	else if (!ctx->isolate_buffers &&
 | 
			
		||||
		(buffer == ctx->global_buffer || buffer == ctx->server_buffer))
 | 
			
		||||
		// TODO: show this in another color or something
 | 
			
		||||
		buffer_line_display (ctx, line);
 | 
			
		||||
		buffer_line_display (ctx, line, true);
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		buffer->unseen_messages_count++;
 | 
			
		||||
@@ -1100,7 +1398,7 @@ buffer_activate (struct app_context *ctx, struct buffer *buffer)
 | 
			
		||||
 | 
			
		||||
	// Once we've found where we want to start with the backlog, print it
 | 
			
		||||
	for (; line; line = line->next)
 | 
			
		||||
		buffer_line_display (ctx, line);
 | 
			
		||||
		buffer_line_display (ctx, line, false);
 | 
			
		||||
	buffer->unseen_messages_count = 0;
 | 
			
		||||
 | 
			
		||||
	// The following part shows you why it's not a good idea to use
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user