/* 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 #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; 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(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 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) { 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) { 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; g_return_val_if_fail(buffer != NULL, NULL); g_return_val_if_fail(data != NULL, NULL); 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); 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; 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; 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 */ break; } if ((*text)[1] == LINE_CMD_FORMAT_CONT) { /* leave text at \0 */ break; } (*text)++; if (**text == LINE_CMD_FORMAT) { /* move text to start after \0 */ (*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); }