1
0
mirror of https://github.com/irssi/irssi.git synced 2025-02-02 15:08:01 -05:00
irssi/src/fe-text/textbuffer.c
Timo Sirainen ed966c6921 changed log domain and changed few g_return_if_fails to g_asserts. the
domain change makes all glib warnings be printed into stderr instead of
trying to print them into irssi window which most probably would just
mysteriously crash.

also irssi doesn't now crash if it thinks screen height is 0 :)


git-svn-id: http://svn.irssi.org/repos/irssi/trunk@2787 dbcabf3a-b0e7-0310-adc4-f8d773084564
2002-05-13 17:07:37 +00:00

552 lines
13 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., 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;
}
}
}
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);
}