summaryrefslogtreecommitdiff
path: root/src/fe-text/textbuffer.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/fe-text/textbuffer.c')
-rw-r--r--src/fe-text/textbuffer.c601
1 files changed, 601 insertions, 0 deletions
diff --git a/src/fe-text/textbuffer.c b/src/fe-text/textbuffer.c
new file mode 100644
index 00000000..b0ef2e70
--- /dev/null
+++ b/src/fe-text/textbuffer.c
@@ -0,0 +1,601 @@
+/*
+ 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
+*/
+
+#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)
+{
+ 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;
+ char *buf, *ptr, **pptr;
+
+ g_return_val_if_fail(buffer != NULL, NULL);
+
+ 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(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)
+{
+ g_return_if_fail(buffer != NULL);
+ g_return_if_fail(chunk != NULL);
+
+ 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 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 (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);
+ if (prev == buffer->cur_line) {
+ buffer->cur_line = line;
+ buffer->lines = g_list_append(buffer->lines, buffer->cur_line);
+ } else {
+ buffer->lines = g_list_insert(buffer->lines, line,
+ g_list_index(buffer->lines, prev)+1);
+ }
+ buffer->lines_count++;
+
+ return line;
+}
+
+void textbuffer_line_ref(LINE_REC *line)
+{
+ if (++line->refcount == 255)
+ g_error("line reference counter wrapped - shouldn't happen");
+}
+
+void textbuffer_line_unref(TEXT_BUFFER_REC *buffer, LINE_REC *line)
+{
+ 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)
+{
+ while (list != NULL) {
+ textbuffer_line_unref(buffer, list->data);
+ list = list->next;
+ }
+}
+
+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;
+
+ 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)
+{
+ buffer->lines = g_list_remove(buffer->lines, line);
+
+ if (buffer->cur_line == line) {
+ buffer->cur_line = buffer->lines == NULL ? NULL :
+ g_list_last(buffer->lines)->data;
+ }
+
+ 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;
+
+ 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;
+
+ g_list_free(buffer->lines);
+ buffer->lines = NULL;
+
+ buffer->cur_line = NULL;
+ buffer->lines_count = 0;
+}
+
+void textbuffer_line2text(LINE_REC *line, int coloring, GString *str)
+{
+ unsigned char cmd;
+ 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, *ptr);
+ ptr++;
+ continue;
+ }
+
+ ptr++;
+ cmd = (unsigned char) *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(char *));
+ ptr = tmp;
+ continue;
+ }
+
+ if (!coloring) {
+ /* no colors, skip coloring commands */
+ continue;
+ }
+
+ if ((cmd & 0x80) == 0) {
+ /* set color */
+ g_string_sprintfa(str, "\004%c%c",
+ (cmd & 0x0f)+'0',
+ ((cmd & 0xf0) >> 4)+'0');
+ } else switch (cmd) {
+ case LINE_CMD_UNDERLINE:
+ g_string_append_c(str, 31);
+ break;
+ case LINE_CMD_COLOR0:
+ g_string_sprintfa(str, "\004%c%c",
+ '0', FORMAT_COLOR_NOCHANGE);
+ break;
+ case LINE_CMD_COLOR8:
+ g_string_sprintfa(str, "\004%c%c",
+ '8', FORMAT_COLOR_NOCHANGE);
+ break;
+ case LINE_CMD_BLINK:
+ g_string_sprintfa(str, "\004%c", FORMAT_STYLE_BLINK);
+ break;
+ case LINE_CMD_INDENT:
+ break;
+ }
+ }
+}
+
+GList *textbuffer_find_text(TEXT_BUFFER_REC *buffer, LINE_REC *startline,
+ int level, int nolevel, const char *text,
+ int regexp, int fullword, int case_sensitive)
+{
+#ifdef HAVE_REGEX_H
+ regex_t preg;
+#endif
+ GList *line, *tmp;
+ GList *matches;
+ GString *str;
+
+ 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;
+ str = g_string_new(NULL);
+
+ line = g_list_find(buffer->lines, startline);
+ if (line == NULL)
+ line = buffer->lines;
+
+ for (tmp = line; tmp != NULL; tmp = tmp->next) {
+ LINE_REC *rec = tmp->data;
+
+ if ((rec->info.level & level) == 0 ||
+ (rec->info.level & nolevel) != 0)
+ continue;
+
+ if (*text == '\0') {
+ /* no search word, everything matches */
+ textbuffer_line_ref(rec);
+ matches = g_list_append(matches, rec);
+ continue;
+ }
+
+ textbuffer_line2text(rec, FALSE, str);
+
+ if (
+#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) {
+ /* matched */
+ textbuffer_line_ref(rec);
+ matches = g_list_append(matches, rec);
+ }
+ }
+#ifdef HAVE_REGEX_H
+ if (regexp) regfree(&preg);
+#endif
+ g_string_free(str, TRUE);
+ return matches;
+}
+
+#if 0 /* FIXME: saving formats is broken */
+static char *line_read_format(unsigned const char **text)
+{
+ GString *str;
+ char *ret;
+
+ str = g_string_new(NULL);
+ for (;;) {
+ if (**text == '\0') {
+ if ((*text)[1] == LINE_CMD_EOL) {
+ /* leave text at \0<eof> */
+ break;
+ }
+ if ((*text)[1] == LINE_CMD_FORMAT_CONT) {
+ /* leave text at \0<format_cont> */
+ break;
+ }
+ (*text)++;
+
+ if (**text == LINE_CMD_FORMAT) {
+ /* move text to start after \0<format> */
+ (*text)++;
+ break;
+ }
+
+ if (**text == LINE_CMD_CONTINUE) {
+ unsigned char *tmp;
+
+ memcpy(&tmp, (*text)+1, sizeof(char *));
+ *text = tmp;
+ continue;
+ } else if (**text & 0x80)
+ (*text)++;
+ continue;
+ }
+
+ g_string_append_c(str, (char) **text);
+ (*text)++;
+ }
+
+ ret = str->str;
+ g_string_free(str, FALSE);
+ return ret;
+}
+
+static char *textbuffer_line_get_format(WINDOW_REC *window, LINE_REC *line,
+ GString *raw)
+{
+ const unsigned char *text;
+ char *module, *format_name, *args[MAX_FORMAT_PARAMS], *ret;
+ TEXT_DEST_REC dest;
+ int formatnum, argcount;
+
+ text = (const unsigned char *) line->text;
+
+ /* skip the beginning of the line until we find the format */
+ g_free(line_read_format(&text));
+ if (text[1] == LINE_CMD_FORMAT_CONT) {
+ g_string_append_c(raw, '\0');
+ g_string_append_c(raw, (char)LINE_CMD_FORMAT_CONT);
+ return NULL;
+ }
+
+ /* read format information */
+ module = line_read_format(&text);
+ format_name = line_read_format(&text);
+
+ if (raw != NULL) {
+ g_string_append_c(raw, '\0');
+ g_string_append_c(raw, (char)LINE_CMD_FORMAT);
+
+ g_string_append(raw, module);
+
+ g_string_append_c(raw, '\0');
+ g_string_append_c(raw, (char)LINE_CMD_FORMAT);
+
+ g_string_append(raw, format_name);
+ }
+
+ formatnum = format_find_tag(module, format_name);
+ if (formatnum == -1)
+ ret = NULL;
+ else {
+ argcount = 0;
+ memset(args, 0, sizeof(args));
+ while (*text != '\0' || text[1] != LINE_CMD_EOL) {
+ args[argcount] = line_read_format(&text);
+ if (raw != NULL) {
+ g_string_append_c(raw, '\0');
+ g_string_append_c(raw,
+ (char)LINE_CMD_FORMAT);
+
+ g_string_append(raw, args[argcount]);
+ }
+ argcount++;
+ }
+
+ /* get the format text */
+ format_create_dest(&dest, NULL, NULL, line->level, window);
+ ret = format_get_text_theme_charargs(current_theme,
+ module, &dest,
+ formatnum, args);
+ while (argcount > 0)
+ g_free(args[--argcount]);
+ }
+
+ g_free(module);
+ g_free(format_name);
+
+ return ret;
+}
+
+void textbuffer_reformat_line(WINDOW_REC *window, LINE_REC *line)
+{
+ GUI_WINDOW_REC *gui;
+ TEXT_DEST_REC dest;
+ GString *raw;
+ char *str, *tmp, *prestr, *linestart, *leveltag;
+
+ gui = WINDOW_GUI(window);
+
+ raw = g_string_new(NULL);
+ str = textbuffer_line_get_format(window, line, raw);
+
+ if (str == NULL && raw->len == 2 &&
+ raw->str[1] == (char)LINE_CMD_FORMAT_CONT) {
+ /* multiline format, format explained in one the
+ following lines. remove this line. */
+ textbuffer_line_remove(window, line, FALSE);
+ } else if (str != NULL) {
+ /* FIXME: ugly ugly .. and this can't handle
+ non-formatted lines.. */
+ g_string_append_c(raw, '\0');
+ g_string_append_c(raw, (char)LINE_CMD_EOL);
+
+ textbuffer_line_text_free(gui, line);
+
+ gui->temp_line = line;
+ gui->temp_line->text = gui->cur_text->buffer+gui->cur_text->pos;
+ gui->cur_text->lines++;
+ gui->eol_marked = FALSE;
+
+ format_create_dest(&dest, NULL, NULL, line->level, window);
+
+ linestart = format_get_line_start(current_theme, &dest, line->time);
+ leveltag = format_get_level_tag(current_theme, &dest);
+
+ prestr = g_strconcat(linestart == NULL ? "" : linestart,
+ leveltag, NULL);
+ g_free_not_null(linestart);
+ g_free_not_null(leveltag);
+
+ tmp = format_add_linestart(str, prestr);
+ g_free(str);
+ g_free(prestr);
+
+ format_send_to_gui(&dest, tmp);
+ g_free(tmp);
+
+ textbuffer_line_append(gui, raw->str, raw->len);
+
+ gui->eol_marked = TRUE;
+ gui->temp_line = NULL;
+ }
+ g_string_free(raw, TRUE);
+}
+#endif
+
+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);
+}