make lines reformattable

- completely removed the old textbuffer representation (
  https://github.com/shabble/irssi-docs/wiki/Notes-256-Colour#textbuffer-encoding
  )

- textbuffer-formats is an extra module, so if we unhook the signals it
  should go back to the "old way" of storing pre-rendered tex

- design uses cache, original formats and list of arguments
This commit is contained in:
ailin-nemui 2019-08-13 14:59:30 +02:00
commit f95fc81130
21 changed files with 789 additions and 587 deletions

View file

@ -26,176 +26,59 @@
#include <irssi/src/core/utf8.h>
#include <irssi/src/core/iregex.h>
#include <irssi/src/fe-text/textbuffer-formats.h>
#include <irssi/src/fe-text/textbuffer.h>
#define TEXT_CHUNK_USABLE_SIZE (LINE_TEXT_CHUNK_SIZE-2-(int)sizeof(char*))
TEXT_BUFFER_REC *textbuffer_create(void)
TEXT_BUFFER_REC *textbuffer_create(WINDOW_REC *window)
{
TEXT_BUFFER_REC *buffer;
buffer = g_slice_new0(TEXT_BUFFER_REC);
buffer->window = window;
buffer->last_eol = TRUE;
buffer->last_fg = -1;
buffer->last_bg = -1;
return buffer;
buffer->cur_text = g_string_sized_new(TEXT_CHUNK_USABLE_SIZE);
return buffer;
}
void textbuffer_destroy(TEXT_BUFFER_REC *buffer)
{
GSList *tmp;
g_return_if_fail(buffer != NULL);
textbuffer_remove_all_lines(buffer);
g_slice_free(TEXT_BUFFER_REC, buffer);
}
static TEXT_CHUNK_REC *text_chunk_find(TEXT_BUFFER_REC *buffer,
const unsigned char *data)
{
GSList *tmp;
for (tmp = buffer->text_chunks; tmp != NULL; tmp = tmp->next) {
TEXT_CHUNK_REC *rec = tmp->data;
if (data >= rec->buffer &&
data < rec->buffer+sizeof(rec->buffer))
return rec;
g_string_free(buffer->cur_text, TRUE);
for (tmp = buffer->cur_info; tmp != NULL; tmp = tmp->next) {
LINE_INFO_REC *info = buffer->cur_info->data;
textbuffer_format_rec_free(info->format);
g_free(info->text);
g_free(info);
}
g_slist_free(buffer->cur_info);
return NULL;
}
#define mark_temp_eol(chunk) G_STMT_START { \
(chunk)->buffer[(chunk)->pos] = 0; \
(chunk)->buffer[(chunk)->pos+1] = LINE_CMD_EOL; \
} G_STMT_END
static TEXT_CHUNK_REC *text_chunk_create(TEXT_BUFFER_REC *buffer)
{
TEXT_CHUNK_REC *rec;
unsigned char *buf, *ptr, **pptr;
rec = g_slice_new(TEXT_CHUNK_REC);
rec->pos = 0;
rec->refcount = 0;
if (buffer->cur_line != NULL && buffer->cur_line->text != NULL) {
/* create a link to new block from the old block */
buf = buffer->cur_text->buffer + buffer->cur_text->pos;
*buf++ = 0; *buf++ = (char) LINE_CMD_CONTINUE;
/* we want to store pointer to beginning of the new text
block to char* buffer. this probably isn't ANSI-C
compatible, and trying this without the pptr variable
breaks at least NetBSD/Alpha, so don't go "optimize"
it :) */
ptr = rec->buffer; pptr = &ptr;
memcpy(buf, pptr, sizeof(unsigned char *));
} else {
/* just to be safe */
mark_temp_eol(rec);
}
buffer->cur_text = rec;
buffer->text_chunks = g_slist_append(buffer->text_chunks, rec);
return rec;
}
static void text_chunk_destroy(TEXT_BUFFER_REC *buffer, TEXT_CHUNK_REC *chunk)
{
buffer->text_chunks = g_slist_remove(buffer->text_chunks, chunk);
g_slice_free(TEXT_CHUNK_REC, chunk);
}
static void text_chunk_line_free(TEXT_BUFFER_REC *buffer, LINE_REC *line)
{
TEXT_CHUNK_REC *chunk;
const unsigned char *text;
unsigned char cmd, *tmp = NULL;
for (text = line->text;; text++) {
if (*text != '\0')
continue;
text++;
cmd = *text;
if (cmd == LINE_CMD_CONTINUE || cmd == LINE_CMD_EOL) {
if (cmd == LINE_CMD_CONTINUE)
memcpy(&tmp, text+1, sizeof(char *));
/* free the previous block */
chunk = text_chunk_find(buffer, text);
if (--chunk->refcount == 0) {
if (buffer->cur_text == chunk)
chunk->pos = 0;
else
text_chunk_destroy(buffer, chunk);
}
if (cmd == LINE_CMD_EOL)
break;
text = tmp-1;
}
}
buffer->window = NULL;
g_slice_free(TEXT_BUFFER_REC, buffer);
}
static void text_chunk_append(TEXT_BUFFER_REC *buffer,
const unsigned char *data, int len)
{
TEXT_CHUNK_REC *chunk;
int left;
int i;
if (len == 0)
return;
chunk = buffer->cur_text;
while (chunk->pos + len >= TEXT_CHUNK_USABLE_SIZE) {
left = TEXT_CHUNK_USABLE_SIZE - chunk->pos;
/* don't split utf-8 character. (assume we can split non-utf8 anywhere.) */
if (left < len && !is_utf8_leading(data[left])) {
int i;
for (i = 1; i < 4 && left >= i; i++)
if (is_utf8_leading(data[left - i])) {
left -= i;
break;
}
}
for (i = 5; i > 0; --i) {
if (left >= i && data[left-i] == 0) {
left -= i; /* don't split the commands */
break;
}
}
memcpy(chunk->buffer + chunk->pos, data, left);
chunk->pos += left;
chunk = text_chunk_create(buffer);
chunk->refcount++;
len -= left; data += left;
}
memcpy(chunk->buffer + chunk->pos, data, len);
chunk->pos += len;
mark_temp_eol(chunk);
/* g_string_append_len(buffer->cur_text, (const char *)data, len); */
g_string_append(buffer->cur_text, (const char *) data);
}
static LINE_REC *textbuffer_line_create(TEXT_BUFFER_REC *buffer)
{
LINE_REC *rec;
if (buffer->cur_text == NULL)
text_chunk_create(buffer);
rec = g_slice_new0(LINE_REC);
rec->text = buffer->cur_text->buffer + buffer->cur_text->pos;
buffer->cur_text->refcount++;
return rec;
}
@ -241,103 +124,18 @@ int textbuffer_line_exists_after(LINE_REC *line, LINE_REC *search)
return FALSE;
}
#ifdef TERM_TRUECOLOR
static void format_24bit_line_color(unsigned char *out, int *pos, int bg, unsigned int color)
{
unsigned char rgb[] = { color >> 16, color >> 8, color };
unsigned char x = bg ? 0x1 : 0;
unsigned int i;
out[(*pos)++] = LINE_COLOR_24;
for (i = 0; i < 3; ++i) {
if (rgb[i] > 0x20)
out[(*pos)++] = rgb[i];
else {
out[(*pos)++] = 0x20 + rgb[i];
x |= 0x10 << i;
}
}
out[(*pos)++] = 0x20 + x;
}
#endif
void textbuffer_line_add_colors(TEXT_BUFFER_REC *buffer, LINE_REC **line,
int fg, int bg, int flags)
{
unsigned char data[22];
int pos;
GString *out = g_string_new(NULL);
format_gui_flags(out, &buffer->last_fg, &buffer->last_bg, &buffer->last_flags, fg, bg,
flags);
pos = 0;
if (fg != buffer->last_fg
|| (flags & GUI_PRINT_FLAG_COLOR_24_FG) != (buffer->last_flags & GUI_PRINT_FLAG_COLOR_24_FG)) {
buffer->last_fg = fg;
data[pos++] = 0;
#ifdef TERM_TRUECOLOR
if (flags & GUI_PRINT_FLAG_COLOR_24_FG)
format_24bit_line_color(data, &pos, 0, fg);
else
#endif
if (fg < 0)
data[pos++] = LINE_COLOR_DEFAULT;
else if (fg < 16)
data[pos++] = fg == 0 ? LINE_CMD_COLOR0 : fg;
else if (fg < 256) {
data[pos++] = LINE_COLOR_EXT;
data[pos++] = fg;
}
if (*(out->str) != '\0') {
*line =
textbuffer_insert(buffer, *line, (unsigned char *) out->str, out->len, NULL);
}
if (bg != buffer->last_bg
|| (flags & GUI_PRINT_FLAG_COLOR_24_BG) != (buffer->last_flags & GUI_PRINT_FLAG_COLOR_24_BG)) {
buffer->last_bg = bg;
data[pos++] = 0;
#ifdef TERM_TRUECOLOR
if (flags & GUI_PRINT_FLAG_COLOR_24_BG)
format_24bit_line_color(data, &pos, 1, bg);
else
#endif
if (bg < 0)
data[pos++] = LINE_COLOR_BG | LINE_COLOR_DEFAULT;
else if (bg < 16)
data[pos++] = LINE_COLOR_BG | bg;
else if (bg < 256) {
data[pos++] = LINE_COLOR_EXT_BG;
data[pos++] = bg;
}
}
if ((flags & GUI_PRINT_FLAG_UNDERLINE) != (buffer->last_flags & GUI_PRINT_FLAG_UNDERLINE)) {
data[pos++] = 0;
data[pos++] = LINE_CMD_UNDERLINE;
}
if ((flags & GUI_PRINT_FLAG_REVERSE) != (buffer->last_flags & GUI_PRINT_FLAG_REVERSE)) {
data[pos++] = 0;
data[pos++] = LINE_CMD_REVERSE;
}
if ((flags & GUI_PRINT_FLAG_BLINK) != (buffer->last_flags & GUI_PRINT_FLAG_BLINK)) {
data[pos++] = 0;
data[pos++] = LINE_CMD_BLINK;
}
if ((flags & GUI_PRINT_FLAG_BOLD) != (buffer->last_flags & GUI_PRINT_FLAG_BOLD)) {
data[pos++] = 0;
data[pos++] = LINE_CMD_BOLD;
}
if ((flags & GUI_PRINT_FLAG_ITALIC) != (buffer->last_flags & GUI_PRINT_FLAG_ITALIC)) {
data[pos++] = 0;
data[pos++] = LINE_CMD_ITALIC;
}
if ((flags & GUI_PRINT_FLAG_MONOSPACE) != (buffer->last_flags & GUI_PRINT_FLAG_MONOSPACE)) {
data[pos++] = 0;
data[pos++] = LINE_CMD_MONOSPACE;
}
if (flags & GUI_PRINT_FLAG_INDENT) {
data[pos++] = 0;
data[pos++] = LINE_CMD_INDENT;
}
if (pos > 0) {
*line = textbuffer_insert(buffer, *line, data, pos, NULL);
}
buffer->last_flags = flags;
g_string_free(out, TRUE);
}
LINE_REC *textbuffer_append(TEXT_BUFFER_REC *buffer,
@ -368,6 +166,11 @@ LINE_REC *textbuffer_insert(TEXT_BUFFER_REC *buffer, LINE_REC *insert_after,
data[len-2] == 0 && data[len-1] == LINE_CMD_EOL;
if (buffer->last_eol) {
if (!line->info.format) {
line->info.text = g_strdup(buffer->cur_text->str);
g_string_truncate(buffer->cur_text, 0);
}
buffer->last_fg = -1;
buffer->last_bg = -1;
buffer->last_flags = 0;
@ -395,145 +198,50 @@ void textbuffer_remove(TEXT_BUFFER_REC *buffer, LINE_REC *line)
line->prev = line->next = NULL;
buffer->lines_count--;
text_chunk_line_free(buffer, line);
g_free(line->info.text);
textbuffer_format_rec_free(line->info.format);
g_slice_free(LINE_REC, line);
}
/* Removes all lines from buffer */
void textbuffer_remove_all_lines(TEXT_BUFFER_REC *buffer)
{
GSList *tmp;
LINE_REC *line;
g_return_if_fail(buffer != NULL);
for (tmp = buffer->text_chunks; tmp != NULL; tmp = tmp->next)
g_slice_free(TEXT_CHUNK_REC, tmp->data);
g_slist_free(buffer->text_chunks);
buffer->text_chunks = NULL;
while (buffer->first_line != NULL) {
line = buffer->first_line->next;
g_free(buffer->first_line->info.text);
textbuffer_format_rec_free(buffer->first_line->info.format);
g_slice_free(LINE_REC, buffer->first_line);
buffer->first_line = line;
}
buffer->lines_count = 0;
buffer->cur_line = NULL;
buffer->cur_text = NULL;
g_string_truncate(buffer->cur_text, 0);
buffer->last_eol = TRUE;
}
static void set_color(GString *str, int cmd)
void textbuffer_line2text(TEXT_BUFFER_REC *buffer, LINE_REC *line, int coloring, GString *str)
{
int color = -1;
if (!(cmd & LINE_COLOR_DEFAULT))
color = (cmd & 0x0f)+'0';
if ((cmd & LINE_COLOR_BG) == 0) {
/* change foreground color */
g_string_append_printf(str, "\004%c%c",
color, FORMAT_COLOR_NOCHANGE);
} else {
/* change background color */
g_string_append_printf(str, "\004%c%c",
FORMAT_COLOR_NOCHANGE, color);
}
}
void textbuffer_line2text(LINE_REC *line, int coloring, GString *str)
{
unsigned char cmd, *ptr, *tmp;
char *ptr, *tmp;
g_return_if_fail(line != NULL);
g_return_if_fail(str != NULL);
g_string_truncate(str, 0);
for (ptr = line->text;;) {
if (*ptr != 0) {
g_string_append_c(str, (char) *ptr);
ptr++;
continue;
}
ptr++;
cmd = *ptr;
ptr++;
if (cmd == LINE_CMD_EOL) {
/* end of line */
break;
}
if (cmd == LINE_CMD_CONTINUE) {
/* line continues in another address.. */
memcpy(&tmp, ptr, sizeof(unsigned char *));
ptr = tmp;
continue;
}
if (!coloring) {
/* no colors, skip coloring commands */
if (cmd == LINE_COLOR_EXT || cmd == LINE_COLOR_EXT_BG)
ptr++;
#ifdef TERM_TRUECOLOR
else if (cmd == LINE_COLOR_24)
ptr+=4;
#endif
continue;
}
if ((cmd & LINE_CMD_EOL) == 0) {
/* set color */
set_color(str, cmd);
} else switch (cmd) {
case LINE_CMD_UNDERLINE:
g_string_append_c(str, 31);
break;
case LINE_CMD_REVERSE:
g_string_append_c(str, 22);
break;
case LINE_CMD_BLINK:
g_string_append_printf(str, "\004%c",
FORMAT_STYLE_BLINK);
break;
case LINE_CMD_BOLD:
g_string_append_printf(str, "\004%c",
FORMAT_STYLE_BOLD);
break;
case LINE_CMD_ITALIC:
g_string_append_printf(str, "\004%c",
FORMAT_STYLE_ITALIC);
break;
case LINE_CMD_MONOSPACE:
g_string_append_printf(str, "\004%c",
FORMAT_STYLE_MONOSPACE);
break;
case LINE_CMD_COLOR0:
g_string_append_printf(str, "\004%c%c",
'0', FORMAT_COLOR_NOCHANGE);
break;
case LINE_CMD_INDENT:
g_string_append_printf(str, "\004%c",
FORMAT_STYLE_INDENT);
break;
case LINE_COLOR_EXT:
format_ext_color(str, 0, *ptr++);
break;
case LINE_COLOR_EXT_BG:
format_ext_color(str, 1, *ptr++);
break;
#ifdef TERM_TRUECOLOR
case LINE_COLOR_24:
g_string_append_printf(str, "\004%c", FORMAT_COLOR_24);
break;
#endif
}
ptr = textbuffer_line_get_text(buffer, line);
if (coloring == 0) {
tmp = ptr;
ptr = strip_codes(tmp);
g_free(tmp);
}
g_string_append(str, ptr);
g_free(ptr);
}
GList *textbuffer_find_text(TEXT_BUFFER_REC *buffer, LINE_REC *startline,
@ -575,7 +283,7 @@ GList *textbuffer_find_text(TEXT_BUFFER_REC *buffer, LINE_REC *startline,
(line->info.level & nolevel) == 0;
if (*text != '\0') {
textbuffer_line2text(line, FALSE, str);
textbuffer_line2text(buffer, line, FALSE, str);
if (line_matched) {
line_matched = regexp ?