1
0
mirror of https://github.com/irssi/irssi.git synced 2024-09-15 04:28:09 -04:00
irssi/src/fe-text/textbuffer.c

554 lines
14 KiB
C
Raw Normal View History

/*
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#define G_LOG_DOMAIN "TextBuffer"
#include "module.h"
#include "misc.h"
#include "formats.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*))
static GMemChunk *buffer_chunk, *line_chunk, *text_chunk;
TEXT_BUFFER_REC *textbuffer_create(void)
{
TEXT_BUFFER_REC *buffer;
buffer = g_mem_chunk_alloc0(buffer_chunk);
buffer->last_eol = TRUE;
return buffer;
}
void textbuffer_destroy(TEXT_BUFFER_REC *buffer)
{
g_return_if_fail(buffer != NULL);
textbuffer_remove_all_lines(buffer);
g_mem_chunk_free(buffer_chunk, 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_mem_chunk_alloc(text_chunk);
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_mem_chunk_free(text_chunk, chunk);
}
static void text_chunk_line_free(TEXT_BUFFER_REC *buffer, LINE_REC *line)
{
TEXT_CHUNK_REC *chunk;
const unsigned char *text;
unsigned char *tmp = NULL;
for (text = line->text;; text++) {
if (*text != '\0')
continue;
text++;
if (*text == LINE_CMD_CONTINUE || *text == LINE_CMD_EOL) {
if (*text == 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 (*text == LINE_CMD_EOL)
break;
text = tmp-1;
} else if (*text == LINE_CMD_INDENT_FUNC) {
text += sizeof(int (*) ());
}
}
}
static void text_chunk_append(TEXT_BUFFER_REC *buffer,
const unsigned char *data, int len)
{
TEXT_CHUNK_REC *chunk;
int left;
if (len == 0)
return;
chunk = buffer->cur_text;
while (chunk->pos + len >= TEXT_CHUNK_USABLE_SIZE) {
left = TEXT_CHUNK_USABLE_SIZE - chunk->pos;
if (left > 0 && data[left-1] == 0)
left--; /* don't split the commands */
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_mem_chunk_alloc(line_chunk);
rec->refcount = 1;
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;
}
void textbuffer_line_ref(LINE_REC *line)
{
g_return_if_fail(line != NULL);
if (++line->refcount == 255)
g_error("line reference counter wrapped - shouldn't happen");
}
void textbuffer_line_unref(TEXT_BUFFER_REC *buffer, LINE_REC *line)
{
g_return_if_fail(buffer != NULL);
g_return_if_fail(line != NULL);
if (--line->refcount == 0) {
text_chunk_line_free(buffer, line);
g_mem_chunk_free(line_chunk, line);
}
}
void textbuffer_line_unref_list(TEXT_BUFFER_REC *buffer, GList *list)
{
g_return_if_fail(buffer != NULL);
while (list != NULL) {
if (list->data != NULL)
textbuffer_line_unref(buffer, list->data);
list = list->next;
}
}
LINE_REC *textbuffer_line_last(TEXT_BUFFER_REC *buffer)
{
LINE_REC *line;
line = buffer->cur_line;
if (line != NULL) {
while (line->next != NULL)
line = line->next;
}
return 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;
}
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;
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->next != NULL ?
line->next : line->prev;
}
line->prev = line->next = NULL;
buffer->lines_count--;
textbuffer_line_unref(buffer, line);
}
/* Removes all lines from buffer, ignoring reference counters */
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_mem_chunk_free(text_chunk, tmp->data);
g_slist_free(buffer->text_chunks);
buffer->text_chunks = NULL;
while (buffer->first_line != NULL) {
line = buffer->first_line->next;
g_mem_chunk_free(line_chunk, 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 *last_fg, int *last_bg)
{
if (cmd & LINE_COLOR_DEFAULT) {
g_string_sprintfa(str, "\004%c", FORMAT_STYLE_DEFAULTS);
/* need to reset the fg/bg color */
if (cmd & LINE_COLOR_BG) {
*last_bg = -1;
if (*last_fg != -1) {
g_string_sprintfa(str, "\004%c%c",
*last_fg,
FORMAT_COLOR_NOCHANGE);
}
} else {
*last_fg = -1;
if (*last_bg != -1) {
g_string_sprintfa(str, "\004%c%c",
FORMAT_COLOR_NOCHANGE,
*last_bg);
}
}
return;
}
if ((cmd & LINE_COLOR_BG) == 0) {
/* change foreground color */
*last_fg = (cmd & 0x0f)+'0';
g_string_sprintfa(str, "\004%c%c", *last_fg,
FORMAT_COLOR_NOCHANGE);
} else {
/* change background color */
*last_bg = (cmd & 0x0f)+'0';
g_string_sprintfa(str, "\004%c%c",
FORMAT_COLOR_NOCHANGE, *last_bg);
}
}
void textbuffer_line2text(LINE_REC *line, int coloring, GString *str)
{
unsigned char cmd, *ptr, *tmp;
int last_fg, last_bg;
g_return_if_fail(line != NULL);
g_return_if_fail(str != NULL);
g_string_truncate(str, 0);
last_fg = last_bg = -1;
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 || cmd == LINE_CMD_FORMAT) {
/* 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 */
continue;
}
if ((cmd & 0x80) == 0) {
/* set color */
set_color(str, cmd, &last_fg, &last_bg);
} 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_COLOR0:
g_string_sprintfa(str, "\004%c%c",
'0', FORMAT_COLOR_NOCHANGE);
break;
case LINE_CMD_INDENT:
break;
case LINE_CMD_INDENT_FUNC:
ptr += sizeof(void *);
break;
}
}
}
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;
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;
for (; line != NULL; line = line->next) {
if ((line->info.level & level) == 0 ||
(line->info.level & nolevel) != 0)
continue;
if (*text == '\0') {
/* no search word, everything matches */
textbuffer_line_ref(line);
matches = g_list_append(matches, line);
continue;
}
textbuffer_line2text(line, FALSE, str);
line_matched =
#ifdef HAVE_REGEX_H
regexp ? regexec(&preg, str->str, 0, NULL, 0) == 0 :
#endif
fullword ? strstr_full_case(str->str, text, !case_sensitive) != NULL :
case_sensitive ? strstr(str->str, text) != NULL :
stristr(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) {
textbuffer_line_ref(pre_line);
matches = g_list_append(matches, pre_line);
}
match_after = after;
}
if (line_matched || match_after > 0) {
/* matched */
textbuffer_line_ref(line);
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)
{
buffer_chunk = g_mem_chunk_new("text buffer chunk",
sizeof(TEXT_BUFFER_REC),
sizeof(TEXT_BUFFER_REC)*32, G_ALLOC_AND_FREE);
line_chunk = g_mem_chunk_new("line chunk", sizeof(LINE_REC),
sizeof(LINE_REC)*1024, G_ALLOC_AND_FREE);
text_chunk = g_mem_chunk_new("text chunk", sizeof(TEXT_CHUNK_REC),
sizeof(TEXT_CHUNK_REC)*32, G_ALLOC_AND_FREE);
}
void textbuffer_deinit(void)
{
g_mem_chunk_destroy(buffer_chunk);
g_mem_chunk_destroy(line_chunk);
g_mem_chunk_destroy(text_chunk);
}