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
21 #define G_LOG_DOMAIN "TextBuffer"
27 #include "textbuffer.h"
33 #define TEXT_CHUNK_USABLE_SIZE (LINE_TEXT_CHUNK_SIZE-2-(int)sizeof(char*))
35 static GMemChunk *buffer_chunk, *line_chunk, *text_chunk;
37 TEXT_BUFFER_REC *textbuffer_create(void)
39 TEXT_BUFFER_REC *buffer;
41 buffer = g_mem_chunk_alloc0(buffer_chunk);
42 buffer->last_eol = TRUE;
46 void textbuffer_destroy(TEXT_BUFFER_REC *buffer)
48 g_return_if_fail(buffer != NULL);
50 textbuffer_remove_all_lines(buffer);
51 g_mem_chunk_free(buffer_chunk, buffer);
54 static TEXT_CHUNK_REC *text_chunk_find(TEXT_BUFFER_REC *buffer,
55 const unsigned char *data)
59 for (tmp = buffer->text_chunks; tmp != NULL; tmp = tmp->next) {
60 TEXT_CHUNK_REC *rec = tmp->data;
62 if (data >= rec->buffer &&
63 data < rec->buffer+sizeof(rec->buffer))
70 #define mark_temp_eol(chunk) G_STMT_START { \
71 (chunk)->buffer[(chunk)->pos] = 0; \
72 (chunk)->buffer[(chunk)->pos+1] = LINE_CMD_EOL; \
75 static TEXT_CHUNK_REC *text_chunk_create(TEXT_BUFFER_REC *buffer)
78 unsigned char *buf, *ptr, **pptr;
80 rec = g_mem_chunk_alloc(text_chunk);
84 if (buffer->cur_line != NULL && buffer->cur_line->text != NULL) {
85 /* create a link to new block from the old block */
86 buf = buffer->cur_text->buffer + buffer->cur_text->pos;
87 *buf++ = 0; *buf++ = (char) LINE_CMD_CONTINUE;
89 /* we want to store pointer to beginning of the new text
90 block to char* buffer. this probably isn't ANSI-C
91 compatible, and trying this without the pptr variable
92 breaks at least NetBSD/Alpha, so don't go "optimize"
94 ptr = rec->buffer; pptr = &ptr;
95 memcpy(buf, pptr, sizeof(unsigned char *));
101 buffer->cur_text = rec;
102 buffer->text_chunks = g_slist_append(buffer->text_chunks, rec);
106 static void text_chunk_destroy(TEXT_BUFFER_REC *buffer, TEXT_CHUNK_REC *chunk)
108 buffer->text_chunks = g_slist_remove(buffer->text_chunks, chunk);
109 g_mem_chunk_free(text_chunk, chunk);
112 static void text_chunk_line_free(TEXT_BUFFER_REC *buffer, LINE_REC *line)
114 TEXT_CHUNK_REC *chunk;
115 const unsigned char *text;
116 unsigned char cmd, *tmp = NULL;
118 for (text = line->text;; text++) {
124 if (cmd == LINE_CMD_CONTINUE || cmd == LINE_CMD_EOL) {
125 if (cmd == LINE_CMD_CONTINUE)
126 memcpy(&tmp, text+1, sizeof(char *));
128 /* free the previous block */
129 chunk = text_chunk_find(buffer, text);
130 if (--chunk->refcount == 0) {
131 if (buffer->cur_text == chunk)
134 text_chunk_destroy(buffer, chunk);
137 if (cmd == LINE_CMD_EOL)
141 } else if (cmd == LINE_CMD_INDENT_FUNC) {
142 text += sizeof(int (*) ());
147 static void text_chunk_append(TEXT_BUFFER_REC *buffer,
148 const unsigned char *data, int len)
150 TEXT_CHUNK_REC *chunk;
156 chunk = buffer->cur_text;
157 while (chunk->pos + len >= TEXT_CHUNK_USABLE_SIZE) {
158 left = TEXT_CHUNK_USABLE_SIZE - chunk->pos;
159 if (left > 0 && data[left-1] == 0)
160 left--; /* don't split the commands */
162 memcpy(chunk->buffer + chunk->pos, data, left);
165 chunk = text_chunk_create(buffer);
167 len -= left; data += left;
170 memcpy(chunk->buffer + chunk->pos, data, len);
173 mark_temp_eol(chunk);
176 static LINE_REC *textbuffer_line_create(TEXT_BUFFER_REC *buffer)
180 if (buffer->cur_text == NULL)
181 text_chunk_create(buffer);
183 rec = g_mem_chunk_alloc(line_chunk);
185 rec->text = buffer->cur_text->buffer + buffer->cur_text->pos;
187 buffer->cur_text->refcount++;
191 static LINE_REC *textbuffer_line_insert(TEXT_BUFFER_REC *buffer,
196 line = textbuffer_line_create(buffer);
199 line->next = buffer->first_line;
200 if (buffer->first_line != NULL)
201 buffer->first_line->prev = line;
202 buffer->first_line = line;
204 line->next = prev->next;
205 if (line->next != NULL)
206 line->next->prev = line;
210 if (prev == buffer->cur_line)
211 buffer->cur_line = line;
212 buffer->lines_count++;
217 void textbuffer_line_ref(LINE_REC *line)
219 g_return_if_fail(line != NULL);
221 if (++line->refcount == 255)
222 g_error("line reference counter wrapped - shouldn't happen");
225 void textbuffer_line_unref(TEXT_BUFFER_REC *buffer, LINE_REC *line)
227 g_return_if_fail(buffer != NULL);
228 g_return_if_fail(line != NULL);
230 if (--line->refcount == 0) {
231 text_chunk_line_free(buffer, line);
232 g_mem_chunk_free(line_chunk, line);
236 void textbuffer_line_unref_list(TEXT_BUFFER_REC *buffer, GList *list)
238 g_return_if_fail(buffer != NULL);
240 while (list != NULL) {
241 if (list->data != NULL)
242 textbuffer_line_unref(buffer, list->data);
247 LINE_REC *textbuffer_line_last(TEXT_BUFFER_REC *buffer)
251 line = buffer->cur_line;
253 while (line->next != NULL)
259 int textbuffer_line_exists_after(LINE_REC *line, LINE_REC *search)
261 while (line != NULL) {
269 LINE_REC *textbuffer_append(TEXT_BUFFER_REC *buffer,
270 const unsigned char *data, int len,
273 return textbuffer_insert(buffer, buffer->cur_line, data, len, info);
276 LINE_REC *textbuffer_insert(TEXT_BUFFER_REC *buffer, LINE_REC *insert_after,
277 const unsigned char *data, int len,
282 g_return_val_if_fail(buffer != NULL, NULL);
283 g_return_val_if_fail(data != NULL, NULL);
288 line = !buffer->last_eol ? insert_after :
289 textbuffer_line_insert(buffer, insert_after);
292 memcpy(&line->info, info, sizeof(line->info));
294 text_chunk_append(buffer, data, len);
296 buffer->last_eol = len >= 2 &&
297 data[len-2] == 0 && data[len-1] == LINE_CMD_EOL;
302 void textbuffer_remove(TEXT_BUFFER_REC *buffer, LINE_REC *line)
304 g_return_if_fail(buffer != NULL);
305 g_return_if_fail(line != NULL);
307 if (buffer->first_line == line)
308 buffer->first_line = line->next;
309 if (line->prev != NULL)
310 line->prev->next = line->next;
311 if (line->next != NULL)
312 line->next->prev = line->prev;
314 if (buffer->cur_line == line) {
315 buffer->cur_line = line->next != NULL ?
316 line->next : line->prev;
319 line->prev = line->next = NULL;
321 buffer->lines_count--;
322 textbuffer_line_unref(buffer, line);
325 /* Removes all lines from buffer, ignoring reference counters */
326 void textbuffer_remove_all_lines(TEXT_BUFFER_REC *buffer)
331 g_return_if_fail(buffer != NULL);
333 for (tmp = buffer->text_chunks; tmp != NULL; tmp = tmp->next)
334 g_mem_chunk_free(text_chunk, tmp->data);
335 g_slist_free(buffer->text_chunks);
336 buffer->text_chunks = NULL;
338 while (buffer->first_line != NULL) {
339 line = buffer->first_line->next;
340 g_mem_chunk_free(line_chunk, buffer->first_line);
341 buffer->first_line = line;
343 buffer->lines_count = 0;
345 buffer->cur_line = NULL;
346 buffer->cur_text = NULL;
348 buffer->last_eol = TRUE;
351 static void set_color(GString *str, int cmd, int *last_fg, int *last_bg)
353 if (cmd & LINE_COLOR_DEFAULT) {
354 g_string_sprintfa(str, "\004%c", FORMAT_STYLE_DEFAULTS);
356 /* need to reset the fg/bg color */
357 if (cmd & LINE_COLOR_BG) {
359 if (*last_fg != -1) {
360 g_string_sprintfa(str, "\004%c%c",
362 FORMAT_COLOR_NOCHANGE);
366 if (*last_bg != -1) {
367 g_string_sprintfa(str, "\004%c%c",
368 FORMAT_COLOR_NOCHANGE,
375 if ((cmd & LINE_COLOR_BG) == 0) {
376 /* change foreground color */
377 *last_fg = (cmd & 0x0f)+'0';
378 g_string_sprintfa(str, "\004%c%c", *last_fg,
379 FORMAT_COLOR_NOCHANGE);
381 /* change background color */
382 *last_bg = (cmd & 0x0f)+'0';
383 g_string_sprintfa(str, "\004%c%c",
384 FORMAT_COLOR_NOCHANGE, *last_bg);
388 void textbuffer_line2text(LINE_REC *line, int coloring, GString *str)
390 unsigned char cmd, *ptr, *tmp;
391 int last_fg, last_bg;
393 g_return_if_fail(line != NULL);
394 g_return_if_fail(str != NULL);
396 g_string_truncate(str, 0);
398 last_fg = last_bg = -1;
399 for (ptr = line->text;;) {
401 g_string_append_c(str, (char) *ptr);
410 if (cmd == LINE_CMD_EOL || cmd == LINE_CMD_FORMAT) {
415 if (cmd == LINE_CMD_CONTINUE) {
416 /* line continues in another address.. */
417 memcpy(&tmp, ptr, sizeof(unsigned char *));
423 /* no colors, skip coloring commands */
427 if ((cmd & 0x80) == 0) {
429 set_color(str, cmd, &last_fg, &last_bg);
430 } else switch (cmd) {
431 case LINE_CMD_UNDERLINE:
432 g_string_append_c(str, 31);
434 case LINE_CMD_REVERSE:
435 g_string_append_c(str, 22);
437 case LINE_CMD_COLOR0:
438 g_string_sprintfa(str, "\004%c%c",
439 '0', FORMAT_COLOR_NOCHANGE);
441 case LINE_CMD_INDENT:
443 case LINE_CMD_INDENT_FUNC:
444 ptr += sizeof(void *);
450 GList *textbuffer_find_text(TEXT_BUFFER_REC *buffer, LINE_REC *startline,
451 int level, int nolevel, const char *text,
452 int before, int after,
453 int regexp, int fullword, int case_sensitive)
458 LINE_REC *line, *pre_line;
461 int i, match_after, line_matched;
463 g_return_val_if_fail(buffer != NULL, NULL);
464 g_return_val_if_fail(text != NULL, NULL);
468 int flags = REG_EXTENDED | REG_NOSUB |
469 (case_sensitive ? 0 : REG_ICASE);
470 if (regcomp(&preg, text, flags) != 0)
477 matches = NULL; match_after = 0;
478 str = g_string_new(NULL);
480 line = startline != NULL ? startline : buffer->first_line;
482 for (; line != NULL; line = line->next) {
483 if ((line->info.level & level) == 0 ||
484 (line->info.level & nolevel) != 0)
488 /* no search word, everything matches */
489 textbuffer_line_ref(line);
490 matches = g_list_append(matches, line);
494 textbuffer_line2text(line, FALSE, str);
498 regexp ? regexec(&preg, str->str, 0, NULL, 0) == 0 :
500 fullword ? strstr_full_case(str->str, text, !case_sensitive) != NULL :
501 case_sensitive ? strstr(str->str, text) != NULL :
502 stristr(str->str, text) != NULL;
504 /* add the -before lines */
506 for (i = 0; i < before; i++) {
507 if (pre_line->prev == NULL ||
508 g_list_find(matches, pre_line->prev) != NULL)
510 pre_line = pre_line->prev;
513 for (; pre_line != line; pre_line = pre_line->next) {
514 textbuffer_line_ref(pre_line);
515 matches = g_list_append(matches, pre_line);
521 if (line_matched || match_after > 0) {
523 textbuffer_line_ref(line);
524 matches = g_list_append(matches, line);
526 if ((!line_matched && --match_after == 0) ||
527 (line_matched && match_after == 0 && before > 0))
528 matches = g_list_append(matches, NULL);
532 if (regexp) regfree(&preg);
534 g_string_free(str, TRUE);
538 void textbuffer_init(void)
540 buffer_chunk = g_mem_chunk_new("text buffer chunk",
541 sizeof(TEXT_BUFFER_REC),
542 sizeof(TEXT_BUFFER_REC)*32, G_ALLOC_AND_FREE);
543 line_chunk = g_mem_chunk_new("line chunk", sizeof(LINE_REC),
544 sizeof(LINE_REC)*1024, G_ALLOC_AND_FREE);
545 text_chunk = g_mem_chunk_new("text chunk", sizeof(TEXT_CHUNK_REC),
546 sizeof(TEXT_CHUNK_REC)*32, G_ALLOC_AND_FREE);
549 void textbuffer_deinit(void)
551 g_mem_chunk_destroy(buffer_chunk);
552 g_mem_chunk_destroy(line_chunk);
553 g_mem_chunk_destroy(text_chunk);