forked from PsychoticNinja/irssi
256 colour patch is cleaned up and the remaining cases are made work, this includes especially Theme support, which was not implemented before. Changes not related to colours were reverted again, making a review of the two patches against master easier to follow. As a byproduct of the Hex-colour code parser, the 24bit colours are also implemented. Actually using them in the terminal is guarded by a compile time switch (as well as a run time switch), as it breaks the existing colour protocol and requires additional storage. To make a seamless usage, down-conversion is provided for 8 and 16 colours. Diverging from Tom's approach, the colour protocol is reverted back to the original one. Unfortunately, the changes required in the Theme engine will break the API. For more details, please refer to the patch documentation at either http://irssi-docs.wikispaces.com/Notes-256-Colour or https://github.com/shabble/irssi-docs/wiki/Notes-256-Colour
619 lines
15 KiB
C
619 lines
15 KiB
C
/*
|
|
textbuffer.c : Text buffer handling
|
|
|
|
Copyright (C) 1999-2001 Timo Sirainen
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License along
|
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
#define G_LOG_DOMAIN "TextBuffer"
|
|
|
|
#include "module.h"
|
|
#include "misc.h"
|
|
#include "formats.h"
|
|
#include "utf8.h"
|
|
|
|
#include "textbuffer.h"
|
|
|
|
#ifdef HAVE_REGEX_H
|
|
# include <regex.h>
|
|
#endif
|
|
|
|
#define TEXT_CHUNK_USABLE_SIZE (LINE_TEXT_CHUNK_SIZE-2-(int)sizeof(char*))
|
|
|
|
TEXT_BUFFER_REC *textbuffer_create(void)
|
|
{
|
|
TEXT_BUFFER_REC *buffer;
|
|
|
|
buffer = g_slice_new0(TEXT_BUFFER_REC);
|
|
buffer->last_eol = TRUE;
|
|
buffer->last_fg = -1;
|
|
buffer->last_bg = -1;
|
|
return buffer;
|
|
}
|
|
|
|
void textbuffer_destroy(TEXT_BUFFER_REC *buffer)
|
|
{
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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_new(LINE_REC);
|
|
rec->text = buffer->cur_text->buffer + buffer->cur_text->pos;
|
|
|
|
buffer->cur_text->refcount++;
|
|
return rec;
|
|
}
|
|
|
|
static LINE_REC *textbuffer_line_insert(TEXT_BUFFER_REC *buffer,
|
|
LINE_REC *prev)
|
|
{
|
|
LINE_REC *line;
|
|
|
|
line = textbuffer_line_create(buffer);
|
|
line->prev = prev;
|
|
if (prev == NULL) {
|
|
line->next = buffer->first_line;
|
|
if (buffer->first_line != NULL)
|
|
buffer->first_line->prev = line;
|
|
buffer->first_line = line;
|
|
} else {
|
|
line->next = prev->next;
|
|
if (line->next != NULL)
|
|
line->next->prev = line;
|
|
prev->next = line;
|
|
}
|
|
|
|
if (prev == buffer->cur_line)
|
|
buffer->cur_line = line;
|
|
buffer->lines_count++;
|
|
|
|
return line;
|
|
}
|
|
|
|
LINE_REC *textbuffer_line_last(TEXT_BUFFER_REC *buffer)
|
|
{
|
|
return buffer->cur_line;
|
|
}
|
|
|
|
int textbuffer_line_exists_after(LINE_REC *line, LINE_REC *search)
|
|
{
|
|
while (line != NULL) {
|
|
if (line == search)
|
|
return TRUE;
|
|
line = line->next;
|
|
}
|
|
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;
|
|
|
|
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 (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_INDENT) {
|
|
data[pos++] = 0;
|
|
data[pos++] = LINE_CMD_INDENT;
|
|
}
|
|
|
|
if (pos > 0)
|
|
*line = textbuffer_insert(buffer, *line, data, pos, NULL);
|
|
|
|
buffer->last_flags = flags;
|
|
}
|
|
|
|
LINE_REC *textbuffer_append(TEXT_BUFFER_REC *buffer,
|
|
const unsigned char *data, int len,
|
|
LINE_INFO_REC *info)
|
|
{
|
|
return textbuffer_insert(buffer, buffer->cur_line, data, len, info);
|
|
}
|
|
|
|
LINE_REC *textbuffer_insert(TEXT_BUFFER_REC *buffer, LINE_REC *insert_after,
|
|
const unsigned char *data, int len,
|
|
LINE_INFO_REC *info)
|
|
{
|
|
LINE_REC *line;
|
|
|
|
g_return_val_if_fail(buffer != NULL, NULL);
|
|
g_return_val_if_fail(data != NULL, NULL);
|
|
|
|
if (len == 0)
|
|
return insert_after;
|
|
|
|
line = !buffer->last_eol ? insert_after :
|
|
textbuffer_line_insert(buffer, insert_after);
|
|
|
|
if (info != NULL)
|
|
memcpy(&line->info, info, sizeof(line->info));
|
|
|
|
text_chunk_append(buffer, data, len);
|
|
|
|
buffer->last_eol = len >= 2 &&
|
|
data[len-2] == 0 && data[len-1] == LINE_CMD_EOL;
|
|
|
|
if (buffer->last_eol) {
|
|
buffer->last_fg = -1;
|
|
buffer->last_bg = -1;
|
|
buffer->last_flags = 0;
|
|
}
|
|
|
|
return line;
|
|
}
|
|
|
|
void textbuffer_remove(TEXT_BUFFER_REC *buffer, LINE_REC *line)
|
|
{
|
|
g_return_if_fail(buffer != NULL);
|
|
g_return_if_fail(line != NULL);
|
|
|
|
if (buffer->first_line == line)
|
|
buffer->first_line = line->next;
|
|
if (line->prev != NULL)
|
|
line->prev->next = line->next;
|
|
if (line->next != NULL)
|
|
line->next->prev = line->prev;
|
|
|
|
if (buffer->cur_line == line) {
|
|
buffer->cur_line = line->prev;
|
|
}
|
|
|
|
line->prev = line->next = NULL;
|
|
|
|
buffer->lines_count--;
|
|
text_chunk_line_free(buffer, line);
|
|
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_slice_free(LINE_REC, buffer->first_line);
|
|
buffer->first_line = line;
|
|
}
|
|
buffer->lines_count = 0;
|
|
|
|
buffer->cur_line = NULL;
|
|
buffer->cur_text = NULL;
|
|
|
|
buffer->last_eol = TRUE;
|
|
}
|
|
|
|
static void set_color(GString *str, int cmd)
|
|
{
|
|
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;
|
|
|
|
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_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
|
|
}
|
|
}
|
|
}
|
|
|
|
GList *textbuffer_find_text(TEXT_BUFFER_REC *buffer, LINE_REC *startline,
|
|
int level, int nolevel, const char *text,
|
|
int before, int after,
|
|
int regexp, int fullword, int case_sensitive)
|
|
{
|
|
#ifdef HAVE_REGEX_H
|
|
regex_t preg;
|
|
#endif
|
|
LINE_REC *line, *pre_line;
|
|
GList *matches;
|
|
GString *str;
|
|
int i, match_after, line_matched;
|
|
char * (*match_func)(const char *, const char *);
|
|
|
|
g_return_val_if_fail(buffer != NULL, NULL);
|
|
g_return_val_if_fail(text != NULL, NULL);
|
|
|
|
if (regexp) {
|
|
#ifdef HAVE_REGEX_H
|
|
int flags = REG_EXTENDED | REG_NOSUB |
|
|
(case_sensitive ? 0 : REG_ICASE);
|
|
if (regcomp(&preg, text, flags) != 0)
|
|
return NULL;
|
|
#else
|
|
return NULL;
|
|
#endif
|
|
}
|
|
|
|
matches = NULL; match_after = 0;
|
|
str = g_string_new(NULL);
|
|
|
|
line = startline != NULL ? startline : buffer->first_line;
|
|
|
|
if (fullword)
|
|
match_func = case_sensitive ? strstr_full : stristr_full;
|
|
else
|
|
match_func = case_sensitive ? strstr : stristr;
|
|
|
|
for (; line != NULL; line = line->next) {
|
|
line_matched = (line->info.level & level) != 0 &&
|
|
(line->info.level & nolevel) == 0;
|
|
|
|
if (*text != '\0') {
|
|
textbuffer_line2text(line, FALSE, str);
|
|
|
|
if (line_matched)
|
|
line_matched =
|
|
#ifdef HAVE_REGEX_H
|
|
regexp ? regexec(&preg, str->str, 0, NULL, 0) == 0 :
|
|
#endif
|
|
match_func(str->str, text) != NULL;
|
|
}
|
|
|
|
if (line_matched) {
|
|
/* add the -before lines */
|
|
pre_line = line;
|
|
for (i = 0; i < before; i++) {
|
|
if (pre_line->prev == NULL ||
|
|
g_list_find(matches, pre_line->prev) != NULL)
|
|
break;
|
|
pre_line = pre_line->prev;
|
|
}
|
|
|
|
for (; pre_line != line; pre_line = pre_line->next)
|
|
matches = g_list_append(matches, pre_line);
|
|
|
|
match_after = after;
|
|
}
|
|
|
|
if (line_matched || match_after > 0) {
|
|
/* matched */
|
|
matches = g_list_append(matches, line);
|
|
|
|
if ((!line_matched && --match_after == 0) ||
|
|
(line_matched && match_after == 0 && before > 0))
|
|
matches = g_list_append(matches, NULL);
|
|
}
|
|
}
|
|
#ifdef HAVE_REGEX_H
|
|
if (regexp) regfree(&preg);
|
|
#endif
|
|
g_string_free(str, TRUE);
|
|
return matches;
|
|
}
|
|
|
|
void textbuffer_init(void)
|
|
{
|
|
}
|
|
|
|
void textbuffer_deinit(void)
|
|
{
|
|
}
|