mirror of
https://github.com/irssi/irssi.git
synced 2024-11-03 04:27:19 -05:00
630 lines
15 KiB
C
630 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 "iregex.h"
|
|
|
|
#include "textbuffer.h"
|
|
|
|
#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_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;
|
|
}
|
|
|
|
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_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
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
Regex *preg;
|
|
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);
|
|
|
|
preg = NULL;
|
|
|
|
if (regexp) {
|
|
preg = i_regex_new(text, case_sensitive ? 0 : G_REGEX_CASELESS, 0, NULL);
|
|
|
|
if (preg == NULL)
|
|
return NULL;
|
|
}
|
|
|
|
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 = regexp ?
|
|
i_regex_match(preg, str->str, 0, NULL)
|
|
: 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_nth_data(matches, 0) == pre_line->prev ||
|
|
g_list_nth_data(matches, 1) == pre_line->prev)
|
|
break;
|
|
pre_line = pre_line->prev;
|
|
}
|
|
|
|
for (; pre_line != line; pre_line = pre_line->next)
|
|
matches = g_list_prepend(matches, pre_line);
|
|
|
|
match_after = after;
|
|
}
|
|
|
|
if (line_matched || match_after > 0) {
|
|
/* matched */
|
|
matches = g_list_prepend(matches, line);
|
|
|
|
if ((!line_matched && --match_after == 0) ||
|
|
(line_matched && match_after == 0 && before > 0))
|
|
matches = g_list_prepend(matches, NULL);
|
|
}
|
|
}
|
|
|
|
matches = g_list_reverse(matches);
|
|
|
|
if (preg != NULL)
|
|
i_regex_unref(preg);
|
|
g_string_free(str, TRUE);
|
|
return matches;
|
|
}
|
|
|
|
void textbuffer_init(void)
|
|
{
|
|
}
|
|
|
|
void textbuffer_deinit(void)
|
|
{
|
|
}
|