2 textbuffer.c : Text buffer handling
4 Copyright (C) 1999-2001 Timo Sirainen
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 #include "textbuffer.h"
31 #define TEXT_CHUNK_USABLE_SIZE (LINE_TEXT_CHUNK_SIZE-2-(int)sizeof(char*))
33 static GMemChunk *buffer_chunk, *line_chunk, *text_chunk;
35 TEXT_BUFFER_REC *textbuffer_create(void)
37 TEXT_BUFFER_REC *buffer;
39 buffer = g_mem_chunk_alloc0(buffer_chunk);
40 buffer->last_eol = TRUE;
44 void textbuffer_destroy(TEXT_BUFFER_REC *buffer)
46 g_return_if_fail(buffer != NULL);
48 textbuffer_remove_all_lines(buffer);
49 g_mem_chunk_free(buffer_chunk, buffer);
52 static TEXT_CHUNK_REC *text_chunk_find(TEXT_BUFFER_REC *buffer,
53 const unsigned char *data)
57 for (tmp = buffer->text_chunks; tmp != NULL; tmp = tmp->next) {
58 TEXT_CHUNK_REC *rec = tmp->data;
60 if (data >= rec->buffer &&
61 data < rec->buffer+sizeof(rec->buffer))
68 #define mark_temp_eol(chunk) G_STMT_START { \
69 (chunk)->buffer[(chunk)->pos] = 0; \
70 (chunk)->buffer[(chunk)->pos+1] = LINE_CMD_EOL; \
73 static TEXT_CHUNK_REC *text_chunk_create(TEXT_BUFFER_REC *buffer)
76 char *buf, *ptr, **pptr;
78 rec = g_mem_chunk_alloc(text_chunk);
82 if (buffer->cur_line != NULL && buffer->cur_line->text != NULL) {
83 /* create a link to new block from the old block */
84 buf = buffer->cur_text->buffer + buffer->cur_text->pos;
85 *buf++ = 0; *buf++ = (char) LINE_CMD_CONTINUE;
87 /* we want to store pointer to beginning of the new text
88 block to char* buffer. this probably isn't ANSI-C
89 compatible, and trying this without the pptr variable
90 breaks at least NetBSD/Alpha, so don't go "optimize"
92 ptr = rec->buffer; pptr = &ptr;
93 memcpy(buf, pptr, sizeof(char *));
99 buffer->cur_text = rec;
100 buffer->text_chunks = g_slist_append(buffer->text_chunks, rec);
104 static void text_chunk_destroy(TEXT_BUFFER_REC *buffer, TEXT_CHUNK_REC *chunk)
106 buffer->text_chunks = g_slist_remove(buffer->text_chunks, chunk);
107 g_mem_chunk_free(text_chunk, chunk);
110 static void text_chunk_line_free(TEXT_BUFFER_REC *buffer, LINE_REC *line)
112 TEXT_CHUNK_REC *chunk;
113 const unsigned char *text;
114 unsigned char *tmp = NULL;
116 for (text = line->text;; text++) {
121 if (*text == LINE_CMD_CONTINUE || *text == LINE_CMD_EOL) {
122 if (*text == LINE_CMD_CONTINUE)
123 memcpy(&tmp, text+1, sizeof(char *));
125 /* free the previous block */
126 chunk = text_chunk_find(buffer, text);
127 if (--chunk->refcount == 0) {
128 if (buffer->cur_text == chunk)
131 text_chunk_destroy(buffer, chunk);
134 if (*text == LINE_CMD_EOL)
142 static void text_chunk_append(TEXT_BUFFER_REC *buffer,
143 const char *data, int len)
145 TEXT_CHUNK_REC *chunk;
151 chunk = buffer->cur_text;
152 while (chunk->pos + len >= TEXT_CHUNK_USABLE_SIZE) {
153 left = TEXT_CHUNK_USABLE_SIZE - chunk->pos;
154 if (data[left-1] == 0) left--; /* don't split the commands */
156 memcpy(chunk->buffer + chunk->pos, data, left);
159 chunk = text_chunk_create(buffer);
161 len -= left; data += left;
164 memcpy(chunk->buffer + chunk->pos, data, len);
167 mark_temp_eol(chunk);
170 static LINE_REC *textbuffer_line_create(TEXT_BUFFER_REC *buffer)
174 if (buffer->cur_text == NULL)
175 text_chunk_create(buffer);
177 rec = g_mem_chunk_alloc(line_chunk);
179 rec->text = buffer->cur_text->buffer + buffer->cur_text->pos;
181 buffer->cur_text->refcount++;
185 static LINE_REC *textbuffer_line_insert(TEXT_BUFFER_REC *buffer,
190 line = textbuffer_line_create(buffer);
191 if (prev == buffer->cur_line) {
192 buffer->cur_line = line;
193 buffer->lines = g_list_append(buffer->lines, buffer->cur_line);
195 buffer->lines = g_list_insert(buffer->lines, line,
196 g_list_index(buffer->lines, prev)+1);
198 buffer->lines_count++;
203 void textbuffer_line_ref(LINE_REC *line)
205 g_return_if_fail(line != NULL);
207 if (++line->refcount == 255)
208 g_error("line reference counter wrapped - shouldn't happen");
211 void textbuffer_line_unref(TEXT_BUFFER_REC *buffer, LINE_REC *line)
213 g_return_if_fail(buffer != NULL);
214 g_return_if_fail(line != NULL);
216 if (--line->refcount == 0) {
217 text_chunk_line_free(buffer, line);
218 g_mem_chunk_free(line_chunk, line);
222 void textbuffer_line_unref_list(TEXT_BUFFER_REC *buffer, GList *list)
224 g_return_if_fail(buffer != NULL);
226 while (list != NULL) {
227 textbuffer_line_unref(buffer, list->data);
232 LINE_REC *textbuffer_append(TEXT_BUFFER_REC *buffer,
233 const unsigned char *data, int len,
236 return textbuffer_insert(buffer, buffer->cur_line, data, len, info);
239 LINE_REC *textbuffer_insert(TEXT_BUFFER_REC *buffer, LINE_REC *insert_after,
240 const unsigned char *data, int len,
245 g_return_val_if_fail(buffer != NULL, NULL);
246 g_return_val_if_fail(data != NULL, NULL);
248 line = !buffer->last_eol ? insert_after :
249 textbuffer_line_insert(buffer, insert_after);
252 memcpy(&line->info, info, sizeof(line->info));
254 text_chunk_append(buffer, data, len);
256 buffer->last_eol = len >= 2 &&
257 data[len-2] == 0 && data[len-1] == LINE_CMD_EOL;
262 void textbuffer_remove(TEXT_BUFFER_REC *buffer, LINE_REC *line)
264 g_return_if_fail(buffer != NULL);
265 g_return_if_fail(line != NULL);
267 buffer->lines = g_list_remove(buffer->lines, line);
269 if (buffer->cur_line == line) {
270 buffer->cur_line = buffer->lines == NULL ? NULL :
271 g_list_last(buffer->lines)->data;
274 buffer->lines_count--;
275 textbuffer_line_unref(buffer, line);
278 /* Removes all lines from buffer, ignoring reference counters */
279 void textbuffer_remove_all_lines(TEXT_BUFFER_REC *buffer)
283 g_return_if_fail(buffer != NULL);
285 for (tmp = buffer->text_chunks; tmp != NULL; tmp = tmp->next)
286 g_mem_chunk_free(text_chunk, tmp->data);
287 g_slist_free(buffer->text_chunks);
288 buffer->text_chunks = NULL;
290 g_list_free(buffer->lines);
291 buffer->lines = NULL;
293 buffer->cur_line = NULL;
294 buffer->lines_count = 0;
297 void textbuffer_line2text(LINE_REC *line, int coloring, GString *str)
302 g_return_if_fail(line != NULL);
303 g_return_if_fail(str != NULL);
305 g_string_truncate(str, 0);
307 for (ptr = line->text;;) {
309 g_string_append_c(str, *ptr);
315 cmd = (unsigned char) *ptr;
318 if (cmd == LINE_CMD_EOL || cmd == LINE_CMD_FORMAT) {
323 if (cmd == LINE_CMD_CONTINUE) {
324 /* line continues in another address.. */
325 memcpy(&tmp, ptr, sizeof(char *));
331 /* no colors, skip coloring commands */
335 if ((cmd & 0x80) == 0) {
337 g_string_sprintfa(str, "\004%c%c",
339 ((cmd & 0xf0) >> 4)+'0');
340 } else switch (cmd) {
341 case LINE_CMD_UNDERLINE:
342 g_string_append_c(str, 31);
344 case LINE_CMD_COLOR0:
345 g_string_sprintfa(str, "\004%c%c",
346 '0', FORMAT_COLOR_NOCHANGE);
348 case LINE_CMD_COLOR8:
349 g_string_sprintfa(str, "\004%c%c",
350 '8', FORMAT_COLOR_NOCHANGE);
353 g_string_sprintfa(str, "\004%c", FORMAT_STYLE_BLINK);
355 case LINE_CMD_INDENT:
361 GList *textbuffer_find_text(TEXT_BUFFER_REC *buffer, LINE_REC *startline,
362 int level, int nolevel, const char *text,
363 int regexp, int fullword, int case_sensitive)
372 g_return_val_if_fail(buffer != NULL, NULL);
373 g_return_val_if_fail(text != NULL, NULL);
377 int flags = REG_EXTENDED | REG_NOSUB |
378 (case_sensitive ? 0 : REG_ICASE);
379 if (regcomp(&preg, text, flags) != 0)
387 str = g_string_new(NULL);
389 line = g_list_find(buffer->lines, startline);
391 line = buffer->lines;
393 for (tmp = line; tmp != NULL; tmp = tmp->next) {
394 LINE_REC *rec = tmp->data;
396 if ((rec->info.level & level) == 0 ||
397 (rec->info.level & nolevel) != 0)
401 /* no search word, everything matches */
402 textbuffer_line_ref(rec);
403 matches = g_list_append(matches, rec);
407 textbuffer_line2text(rec, FALSE, str);
411 regexp ? regexec(&preg, str->str, 0, NULL, 0) == 0 :
413 fullword ? strstr_full_case(str->str, text,
414 !case_sensitive) != NULL :
415 case_sensitive ? strstr(str->str, text) != NULL :
416 stristr(str->str, text) != NULL) {
418 textbuffer_line_ref(rec);
419 matches = g_list_append(matches, rec);
423 if (regexp) regfree(&preg);
425 g_string_free(str, TRUE);
429 #if 0 /* FIXME: saving formats is broken */
430 static char *line_read_format(unsigned const char **text)
435 str = g_string_new(NULL);
437 if (**text == '\0') {
438 if ((*text)[1] == LINE_CMD_EOL) {
439 /* leave text at \0<eof> */
442 if ((*text)[1] == LINE_CMD_FORMAT_CONT) {
443 /* leave text at \0<format_cont> */
448 if (**text == LINE_CMD_FORMAT) {
449 /* move text to start after \0<format> */
454 if (**text == LINE_CMD_CONTINUE) {
457 memcpy(&tmp, (*text)+1, sizeof(char *));
460 } else if (**text & 0x80)
465 g_string_append_c(str, (char) **text);
470 g_string_free(str, FALSE);
474 static char *textbuffer_line_get_format(WINDOW_REC *window, LINE_REC *line,
477 const unsigned char *text;
478 char *module, *format_name, *args[MAX_FORMAT_PARAMS], *ret;
480 int formatnum, argcount;
482 text = (const unsigned char *) line->text;
484 /* skip the beginning of the line until we find the format */
485 g_free(line_read_format(&text));
486 if (text[1] == LINE_CMD_FORMAT_CONT) {
487 g_string_append_c(raw, '\0');
488 g_string_append_c(raw, (char)LINE_CMD_FORMAT_CONT);
492 /* read format information */
493 module = line_read_format(&text);
494 format_name = line_read_format(&text);
497 g_string_append_c(raw, '\0');
498 g_string_append_c(raw, (char)LINE_CMD_FORMAT);
500 g_string_append(raw, module);
502 g_string_append_c(raw, '\0');
503 g_string_append_c(raw, (char)LINE_CMD_FORMAT);
505 g_string_append(raw, format_name);
508 formatnum = format_find_tag(module, format_name);
513 memset(args, 0, sizeof(args));
514 while (*text != '\0' || text[1] != LINE_CMD_EOL) {
515 args[argcount] = line_read_format(&text);
517 g_string_append_c(raw, '\0');
518 g_string_append_c(raw,
519 (char)LINE_CMD_FORMAT);
521 g_string_append(raw, args[argcount]);
526 /* get the format text */
527 format_create_dest(&dest, NULL, NULL, line->level, window);
528 ret = format_get_text_theme_charargs(current_theme,
532 g_free(args[--argcount]);
541 void textbuffer_reformat_line(WINDOW_REC *window, LINE_REC *line)
546 char *str, *tmp, *prestr, *linestart, *leveltag;
548 gui = WINDOW_GUI(window);
550 raw = g_string_new(NULL);
551 str = textbuffer_line_get_format(window, line, raw);
553 if (str == NULL && raw->len == 2 &&
554 raw->str[1] == (char)LINE_CMD_FORMAT_CONT) {
555 /* multiline format, format explained in one the
556 following lines. remove this line. */
557 textbuffer_line_remove(window, line, FALSE);
558 } else if (str != NULL) {
559 /* FIXME: ugly ugly .. and this can't handle
560 non-formatted lines.. */
561 g_string_append_c(raw, '\0');
562 g_string_append_c(raw, (char)LINE_CMD_EOL);
564 textbuffer_line_text_free(gui, line);
566 gui->temp_line = line;
567 gui->temp_line->text = gui->cur_text->buffer+gui->cur_text->pos;
568 gui->cur_text->lines++;
569 gui->eol_marked = FALSE;
571 format_create_dest(&dest, NULL, NULL, line->level, window);
573 linestart = format_get_line_start(current_theme, &dest, line->time);
574 leveltag = format_get_level_tag(current_theme, &dest);
576 prestr = g_strconcat(linestart == NULL ? "" : linestart,
578 g_free_not_null(linestart);
579 g_free_not_null(leveltag);
581 tmp = format_add_linestart(str, prestr);
585 format_send_to_gui(&dest, tmp);
588 textbuffer_line_append(gui, raw->str, raw->len);
590 gui->eol_marked = TRUE;
591 gui->temp_line = NULL;
593 g_string_free(raw, TRUE);
597 void textbuffer_init(void)
599 buffer_chunk = g_mem_chunk_new("text buffer chunk",
600 sizeof(TEXT_BUFFER_REC),
601 sizeof(TEXT_BUFFER_REC)*32, G_ALLOC_AND_FREE);
602 line_chunk = g_mem_chunk_new("line chunk", sizeof(LINE_REC),
603 sizeof(LINE_REC)*1024, G_ALLOC_AND_FREE);
604 text_chunk = g_mem_chunk_new("text chunk", sizeof(TEXT_CHUNK_REC),
605 sizeof(TEXT_CHUNK_REC)*32, G_ALLOC_AND_FREE);
608 void textbuffer_deinit(void)
610 g_mem_chunk_destroy(buffer_chunk);
611 g_mem_chunk_destroy(line_chunk);
612 g_mem_chunk_destroy(text_chunk);