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 along
17 with this program; if not, write to the Free Software Foundation, Inc.,
18 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 TEXT_BUFFER_REC *textbuffer_create(void)
37 TEXT_BUFFER_REC *buffer;
39 buffer = g_slice_new0(TEXT_BUFFER_REC);
40 buffer->last_eol = TRUE;
41 buffer->last_fg = LINE_COLOR_DEFAULT;
42 buffer->last_bg = LINE_COLOR_DEFAULT | LINE_COLOR_BG;
46 void textbuffer_destroy(TEXT_BUFFER_REC *buffer)
48 g_return_if_fail(buffer != NULL);
50 textbuffer_remove_all_lines(buffer);
51 g_slice_free(TEXT_BUFFER_REC, 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_slice_new(TEXT_CHUNK_REC);
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_slice_free(TEXT_CHUNK_REC, 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)
145 static void text_chunk_append(TEXT_BUFFER_REC *buffer,
146 const unsigned char *data, int len)
148 TEXT_CHUNK_REC *chunk;
154 chunk = buffer->cur_text;
155 while (chunk->pos + len >= TEXT_CHUNK_USABLE_SIZE) {
156 left = TEXT_CHUNK_USABLE_SIZE - chunk->pos;
157 if (left > 0 && data[left-1] == 0)
158 left--; /* don't split the commands */
160 memcpy(chunk->buffer + chunk->pos, data, left);
163 chunk = text_chunk_create(buffer);
165 len -= left; data += left;
168 memcpy(chunk->buffer + chunk->pos, data, len);
171 mark_temp_eol(chunk);
174 static LINE_REC *textbuffer_line_create(TEXT_BUFFER_REC *buffer)
178 if (buffer->cur_text == NULL)
179 text_chunk_create(buffer);
181 rec = g_slice_new(LINE_REC);
182 rec->text = buffer->cur_text->buffer + buffer->cur_text->pos;
184 buffer->cur_text->refcount++;
188 static LINE_REC *textbuffer_line_insert(TEXT_BUFFER_REC *buffer,
193 line = textbuffer_line_create(buffer);
196 line->next = buffer->first_line;
197 if (buffer->first_line != NULL)
198 buffer->first_line->prev = line;
199 buffer->first_line = line;
201 line->next = prev->next;
202 if (line->next != NULL)
203 line->next->prev = line;
207 if (prev == buffer->cur_line)
208 buffer->cur_line = line;
209 buffer->lines_count++;
214 LINE_REC *textbuffer_line_last(TEXT_BUFFER_REC *buffer)
216 return buffer->cur_line;
219 int textbuffer_line_exists_after(LINE_REC *line, LINE_REC *search)
221 while (line != NULL) {
229 void textbuffer_line_add_colors(TEXT_BUFFER_REC *buffer, LINE_REC **line,
230 int fg, int bg, int flags)
232 unsigned char data[20];
235 /* get the fg & bg command chars */
236 fg = fg < 0 ? LINE_COLOR_DEFAULT : fg & 0x0f;
237 bg = LINE_COLOR_BG | (bg < 0 ? LINE_COLOR_DEFAULT : bg & 0x0f);
240 if (fg != buffer->last_fg) {
241 buffer->last_fg = fg;
243 data[pos++] = fg == 0 ? LINE_CMD_COLOR0 : fg;
245 if (bg != buffer->last_bg) {
246 buffer->last_bg = bg;
251 if ((flags & GUI_PRINT_FLAG_UNDERLINE) != (buffer->last_flags & GUI_PRINT_FLAG_UNDERLINE)) {
253 data[pos++] = LINE_CMD_UNDERLINE;
255 if ((flags & GUI_PRINT_FLAG_REVERSE) != (buffer->last_flags & GUI_PRINT_FLAG_REVERSE)) {
257 data[pos++] = LINE_CMD_REVERSE;
259 if ((flags & GUI_PRINT_FLAG_BLINK) != (buffer->last_flags & GUI_PRINT_FLAG_BLINK)) {
261 data[pos++] = LINE_CMD_BLINK;
263 if ((flags & GUI_PRINT_FLAG_BOLD) != (buffer->last_flags & GUI_PRINT_FLAG_BOLD)) {
265 data[pos++] = LINE_CMD_BOLD;
267 if (flags & GUI_PRINT_FLAG_INDENT) {
269 data[pos++] = LINE_CMD_INDENT;
273 *line = textbuffer_insert(buffer, *line, data, pos, NULL);
275 buffer->last_flags = flags;
278 LINE_REC *textbuffer_append(TEXT_BUFFER_REC *buffer,
279 const unsigned char *data, int len,
282 return textbuffer_insert(buffer, buffer->cur_line, data, len, info);
285 LINE_REC *textbuffer_insert(TEXT_BUFFER_REC *buffer, LINE_REC *insert_after,
286 const unsigned char *data, int len,
291 g_return_val_if_fail(buffer != NULL, NULL);
292 g_return_val_if_fail(data != NULL, NULL);
297 line = !buffer->last_eol ? insert_after :
298 textbuffer_line_insert(buffer, insert_after);
301 memcpy(&line->info, info, sizeof(line->info));
303 text_chunk_append(buffer, data, len);
305 buffer->last_eol = len >= 2 &&
306 data[len-2] == 0 && data[len-1] == LINE_CMD_EOL;
308 if (buffer->last_eol) {
309 buffer->last_fg = LINE_COLOR_DEFAULT;
310 buffer->last_bg = LINE_COLOR_DEFAULT | LINE_COLOR_BG;
311 buffer->last_flags = 0;
317 void textbuffer_remove(TEXT_BUFFER_REC *buffer, LINE_REC *line)
319 g_return_if_fail(buffer != NULL);
320 g_return_if_fail(line != NULL);
322 if (buffer->first_line == line)
323 buffer->first_line = line->next;
324 if (line->prev != NULL)
325 line->prev->next = line->next;
326 if (line->next != NULL)
327 line->next->prev = line->prev;
329 if (buffer->cur_line == line) {
330 buffer->cur_line = line->prev;
333 line->prev = line->next = NULL;
335 buffer->lines_count--;
336 text_chunk_line_free(buffer, line);
337 g_slice_free(LINE_REC, line);
340 /* Removes all lines from buffer */
341 void textbuffer_remove_all_lines(TEXT_BUFFER_REC *buffer)
346 g_return_if_fail(buffer != NULL);
348 for (tmp = buffer->text_chunks; tmp != NULL; tmp = tmp->next)
349 g_slice_free(TEXT_CHUNK_REC, tmp->data);
350 g_slist_free(buffer->text_chunks);
351 buffer->text_chunks = NULL;
353 while (buffer->first_line != NULL) {
354 line = buffer->first_line->next;
355 g_slice_free(LINE_REC, buffer->first_line);
356 buffer->first_line = line;
358 buffer->lines_count = 0;
360 buffer->cur_line = NULL;
361 buffer->cur_text = NULL;
363 buffer->last_eol = TRUE;
366 static void set_color(GString *str, int cmd)
370 if (!(cmd & LINE_COLOR_DEFAULT))
371 color = (cmd & 0x0f)+'0';
373 if ((cmd & LINE_COLOR_BG) == 0) {
374 /* change foreground color */
375 g_string_append_printf(str, "\004%c%c",
376 color, FORMAT_COLOR_NOCHANGE);
378 /* change background color */
379 g_string_append_printf(str, "\004%c%c",
380 FORMAT_COLOR_NOCHANGE, color);
384 void textbuffer_line2text(LINE_REC *line, int coloring, GString *str)
386 unsigned char cmd, *ptr, *tmp;
388 g_return_if_fail(line != NULL);
389 g_return_if_fail(str != NULL);
391 g_string_truncate(str, 0);
393 for (ptr = line->text;;) {
395 g_string_append_c(str, (char) *ptr);
404 if (cmd == LINE_CMD_EOL) {
409 if (cmd == LINE_CMD_CONTINUE) {
410 /* line continues in another address.. */
411 memcpy(&tmp, ptr, sizeof(unsigned char *));
417 /* no colors, skip coloring commands */
421 if ((cmd & 0x80) == 0) {
424 } else switch (cmd) {
425 case LINE_CMD_UNDERLINE:
426 g_string_append_c(str, 31);
428 case LINE_CMD_REVERSE:
429 g_string_append_c(str, 22);
432 g_string_append_printf(str, "\004%c",
436 g_string_append_printf(str, "\004%c",
439 case LINE_CMD_COLOR0:
440 g_string_append_printf(str, "\004%c%c",
441 '0', FORMAT_COLOR_NOCHANGE);
443 case LINE_CMD_INDENT:
444 g_string_append_printf(str, "\004%c",
445 FORMAT_STYLE_INDENT);
451 GList *textbuffer_find_text(TEXT_BUFFER_REC *buffer, LINE_REC *startline,
452 int level, int nolevel, const char *text,
453 int before, int after,
454 int regexp, int fullword, int case_sensitive)
459 LINE_REC *line, *pre_line;
462 int i, match_after, line_matched;
463 char * (*match_func)(const char *, const char *);
465 g_return_val_if_fail(buffer != NULL, NULL);
466 g_return_val_if_fail(text != NULL, NULL);
470 int flags = REG_EXTENDED | REG_NOSUB |
471 (case_sensitive ? 0 : REG_ICASE);
472 if (regcomp(&preg, text, flags) != 0)
479 matches = NULL; match_after = 0;
480 str = g_string_new(NULL);
482 line = startline != NULL ? startline : buffer->first_line;
485 match_func = case_sensitive ? strstr_full : stristr_full;
487 match_func = case_sensitive ? strstr : stristr;
489 for (; line != NULL; line = line->next) {
490 line_matched = (line->info.level & level) != 0 &&
491 (line->info.level & nolevel) == 0;
494 textbuffer_line2text(line, FALSE, str);
499 regexp ? regexec(&preg, str->str, 0, NULL, 0) == 0 :
501 match_func(str->str, text) != NULL;
505 /* add the -before lines */
507 for (i = 0; i < before; i++) {
508 if (pre_line->prev == NULL ||
509 g_list_find(matches, pre_line->prev) != NULL)
511 pre_line = pre_line->prev;
514 for (; pre_line != line; pre_line = pre_line->next)
515 matches = g_list_append(matches, pre_line);
520 if (line_matched || match_after > 0) {
522 matches = g_list_append(matches, line);
524 if ((!line_matched && --match_after == 0) ||
525 (line_matched && match_after == 0 && before > 0))
526 matches = g_list_append(matches, NULL);
530 if (regexp) regfree(&preg);
532 g_string_free(str, TRUE);
536 void textbuffer_init(void)
540 void textbuffer_deinit(void)