Added SILC Thread Queue API
[crypto.git] / apps / irssi / src / fe-text / textbuffer.c
1 /*
2  textbuffer.c : Text buffer handling
3
4     Copyright (C) 1999-2001 Timo Sirainen
5
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.
10
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.
15
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
19 */
20
21 #define G_LOG_DOMAIN "TextBuffer"
22
23 #include "module.h"
24 #include "misc.h"
25 #include "formats.h"
26
27 #include "textbuffer.h"
28
29 #ifdef HAVE_REGEX_H
30 #  include <regex.h>
31 #endif
32
33 #define TEXT_CHUNK_USABLE_SIZE (LINE_TEXT_CHUNK_SIZE-2-(int)sizeof(char*))
34
35 static GMemChunk *buffer_chunk, *line_chunk, *text_chunk;
36
37 TEXT_BUFFER_REC *textbuffer_create(void)
38 {
39         TEXT_BUFFER_REC *buffer;
40
41         buffer = g_mem_chunk_alloc0(buffer_chunk);
42         buffer->last_eol = TRUE;
43         return buffer;
44 }
45
46 void textbuffer_destroy(TEXT_BUFFER_REC *buffer)
47 {
48         g_return_if_fail(buffer != NULL);
49
50         textbuffer_remove_all_lines(buffer);
51         g_mem_chunk_free(buffer_chunk, buffer);
52 }
53
54 static TEXT_CHUNK_REC *text_chunk_find(TEXT_BUFFER_REC *buffer,
55                                        const unsigned char *data)
56 {
57         GSList *tmp;
58
59         for (tmp = buffer->text_chunks; tmp != NULL; tmp = tmp->next) {
60                 TEXT_CHUNK_REC *rec = tmp->data;
61
62                 if (data >= rec->buffer &&
63                     data < rec->buffer+sizeof(rec->buffer))
64                         return rec;
65         }
66
67         return NULL;
68 }
69
70 #define mark_temp_eol(chunk) G_STMT_START { \
71         (chunk)->buffer[(chunk)->pos] = 0; \
72         (chunk)->buffer[(chunk)->pos+1] = LINE_CMD_EOL; \
73         } G_STMT_END
74
75 static TEXT_CHUNK_REC *text_chunk_create(TEXT_BUFFER_REC *buffer)
76 {
77         TEXT_CHUNK_REC *rec;
78         unsigned char *buf, *ptr, **pptr;
79
80         rec = g_mem_chunk_alloc(text_chunk);
81         rec->pos = 0;
82         rec->refcount = 0;
83
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;
88
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"
93                    it :) */
94                 ptr = rec->buffer; pptr = &ptr;
95                 memcpy(buf, pptr, sizeof(unsigned char *));
96         } else {
97                 /* just to be safe */
98                 mark_temp_eol(rec);
99         }
100
101         buffer->cur_text = rec;
102         buffer->text_chunks = g_slist_append(buffer->text_chunks, rec);
103         return rec;
104 }
105
106 static void text_chunk_destroy(TEXT_BUFFER_REC *buffer, TEXT_CHUNK_REC *chunk)
107 {
108         buffer->text_chunks = g_slist_remove(buffer->text_chunks, chunk);
109         g_mem_chunk_free(text_chunk, chunk);
110 }
111
112 static void text_chunk_line_free(TEXT_BUFFER_REC *buffer, LINE_REC *line)
113 {
114         TEXT_CHUNK_REC *chunk;
115         const unsigned char *text;
116         unsigned char cmd, *tmp = NULL;
117
118         for (text = line->text;; text++) {
119                 if (*text != '\0')
120                         continue;
121
122                 text++;
123                 cmd = *text;
124                 if (cmd == LINE_CMD_CONTINUE || cmd == LINE_CMD_EOL) {
125                         if (cmd == LINE_CMD_CONTINUE)
126                                 memcpy(&tmp, text+1, sizeof(char *));
127
128                         /* free the previous block */
129                         chunk = text_chunk_find(buffer, text);
130                         if (--chunk->refcount == 0) {
131                                 if (buffer->cur_text == chunk)
132                                         chunk->pos = 0;
133                                 else
134                                         text_chunk_destroy(buffer, chunk);
135                         }
136
137                         if (cmd == LINE_CMD_EOL)
138                                 break;
139
140                         text = tmp-1;
141                 } else if (cmd == LINE_CMD_INDENT_FUNC) {
142                         text += sizeof(int (*) ());
143                 }
144         }
145 }
146
147 static void text_chunk_append(TEXT_BUFFER_REC *buffer,
148                               const unsigned char *data, int len)
149 {
150         TEXT_CHUNK_REC *chunk;
151         int left;
152
153         if (len == 0)
154                 return;
155
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 */
161
162                 memcpy(chunk->buffer + chunk->pos, data, left);
163                 chunk->pos += left;
164
165                 chunk = text_chunk_create(buffer);
166                 chunk->refcount++;
167                 len -= left; data += left;
168         }
169
170         memcpy(chunk->buffer + chunk->pos, data, len);
171         chunk->pos += len;
172
173         mark_temp_eol(chunk);
174 }
175
176 static LINE_REC *textbuffer_line_create(TEXT_BUFFER_REC *buffer)
177 {
178         LINE_REC *rec;
179
180         if (buffer->cur_text == NULL)
181                 text_chunk_create(buffer);
182
183         rec = g_mem_chunk_alloc(line_chunk);
184         rec->refcount = 1;
185         rec->text = buffer->cur_text->buffer + buffer->cur_text->pos;
186
187         buffer->cur_text->refcount++;
188         return rec;
189 }
190
191 static LINE_REC *textbuffer_line_insert(TEXT_BUFFER_REC *buffer,
192                                         LINE_REC *prev)
193 {
194         LINE_REC *line;
195
196         line = textbuffer_line_create(buffer);
197         line->prev = prev;
198         if (prev == NULL) {
199                 line->next = buffer->first_line;
200                 if (buffer->first_line != NULL)
201                         buffer->first_line->prev = line;
202                 buffer->first_line = line;
203         } else {
204                 line->next = prev->next;
205                 if (line->next != NULL)
206                         line->next->prev = line;
207                 prev->next = line;
208         }
209
210         if (prev == buffer->cur_line)
211                 buffer->cur_line = line;
212         buffer->lines_count++;
213
214         return line;
215 }
216
217 void textbuffer_line_ref(LINE_REC *line)
218 {
219         g_return_if_fail(line != NULL);
220
221         if (++line->refcount == 255)
222                 g_error("line reference counter wrapped - shouldn't happen");
223 }
224
225 void textbuffer_line_unref(TEXT_BUFFER_REC *buffer, LINE_REC *line)
226 {
227         g_return_if_fail(buffer != NULL);
228         g_return_if_fail(line != NULL);
229
230         if (--line->refcount == 0) {
231                 text_chunk_line_free(buffer, line);
232                 g_mem_chunk_free(line_chunk, line);
233         }
234 }
235
236 void textbuffer_line_unref_list(TEXT_BUFFER_REC *buffer, GList *list)
237 {
238         g_return_if_fail(buffer != NULL);
239
240         while (list != NULL) {
241                 if (list->data != NULL)
242                         textbuffer_line_unref(buffer, list->data);
243                 list = list->next;
244         }
245 }
246
247 LINE_REC *textbuffer_line_last(TEXT_BUFFER_REC *buffer)
248 {
249         LINE_REC *line;
250
251         line = buffer->cur_line;
252         if (line != NULL) {
253                 while (line->next != NULL)
254                         line = line->next;
255         }
256         return line;
257 }
258
259 int textbuffer_line_exists_after(LINE_REC *line, LINE_REC *search)
260 {
261         while (line != NULL) {
262                 if (line == search)
263                         return TRUE;
264                 line = line->next;
265         }
266         return FALSE;
267 }
268
269 LINE_REC *textbuffer_append(TEXT_BUFFER_REC *buffer,
270                             const unsigned char *data, int len,
271                             LINE_INFO_REC *info)
272 {
273         return textbuffer_insert(buffer, buffer->cur_line, data, len, info);
274 }
275
276 LINE_REC *textbuffer_insert(TEXT_BUFFER_REC *buffer, LINE_REC *insert_after,
277                             const unsigned char *data, int len,
278                             LINE_INFO_REC *info)
279 {
280         LINE_REC *line;
281
282         g_return_val_if_fail(buffer != NULL, NULL);
283         g_return_val_if_fail(data != NULL, NULL);
284
285         if (len == 0)
286                 return insert_after;
287
288         line = !buffer->last_eol ? insert_after :
289                 textbuffer_line_insert(buffer, insert_after);
290
291         if (info != NULL)
292                 memcpy(&line->info, info, sizeof(line->info));
293
294         text_chunk_append(buffer, data, len);
295
296         buffer->last_eol = len >= 2 &&
297                 data[len-2] == 0 && data[len-1] == LINE_CMD_EOL;
298
299         return line;
300 }
301
302 void textbuffer_remove(TEXT_BUFFER_REC *buffer, LINE_REC *line)
303 {
304         g_return_if_fail(buffer != NULL);
305         g_return_if_fail(line != NULL);
306
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;
313
314         if (buffer->cur_line == line) {
315                 buffer->cur_line = line->next != NULL ?
316                         line->next : line->prev;
317         }
318
319         line->prev = line->next = NULL;
320
321         buffer->lines_count--;
322         textbuffer_line_unref(buffer, line);
323 }
324
325 /* Removes all lines from buffer, ignoring reference counters */
326 void textbuffer_remove_all_lines(TEXT_BUFFER_REC *buffer)
327 {
328         GSList *tmp;
329         LINE_REC *line;
330
331         g_return_if_fail(buffer != NULL);
332
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;
337
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;
342         }
343         buffer->lines_count = 0;
344
345         buffer->cur_line = NULL;
346         buffer->cur_text = NULL;
347
348         buffer->last_eol = TRUE;
349 }
350
351 static void set_color(GString *str, int cmd, int *last_fg, int *last_bg)
352 {
353         if (cmd & LINE_COLOR_DEFAULT) {
354                 g_string_sprintfa(str, "\004%c", FORMAT_STYLE_DEFAULTS);
355
356                 /* need to reset the fg/bg color */
357                 if (cmd & LINE_COLOR_BG) {
358                         *last_bg = -1;
359                         if (*last_fg != -1) {
360                                 g_string_sprintfa(str, "\004%c%c",
361                                                   *last_fg,
362                                                   FORMAT_COLOR_NOCHANGE);
363                         }
364                 } else {
365                         *last_fg = -1;
366                         if (*last_bg != -1) {
367                                 g_string_sprintfa(str, "\004%c%c",
368                                                   FORMAT_COLOR_NOCHANGE,
369                                                   *last_bg);
370                         }
371                 }
372                 return;
373         }
374
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);
380         } else {
381                 /* change background color */
382                 *last_bg = (cmd & 0x0f)+'0';
383                 g_string_sprintfa(str, "\004%c%c",
384                                   FORMAT_COLOR_NOCHANGE, *last_bg);
385         }
386 }
387
388 void textbuffer_line2text(LINE_REC *line, int coloring, GString *str)
389 {
390         unsigned char cmd, *ptr, *tmp;
391         int last_fg, last_bg;
392
393         g_return_if_fail(line != NULL);
394         g_return_if_fail(str != NULL);
395
396         g_string_truncate(str, 0);
397
398         last_fg = last_bg = -1;
399         for (ptr = line->text;;) {
400                 if (*ptr != 0) {
401                         g_string_append_c(str, (char) *ptr);
402                         ptr++;
403                         continue;
404                 }
405
406                 ptr++;
407                 cmd = *ptr;
408                 ptr++;
409
410                 if (cmd == LINE_CMD_EOL || cmd == LINE_CMD_FORMAT) {
411                         /* end of line */
412                         break;
413                 }
414
415                 if (cmd == LINE_CMD_CONTINUE) {
416                         /* line continues in another address.. */
417                         memcpy(&tmp, ptr, sizeof(unsigned char *));
418                         ptr = tmp;
419                         continue;
420                 }
421
422                 if (!coloring) {
423                         /* no colors, skip coloring commands */
424                         continue;
425                 }
426
427                 if ((cmd & 0x80) == 0) {
428                         /* set color */
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);
433                         break;
434                 case LINE_CMD_REVERSE:
435                         g_string_append_c(str, 22);
436                         break;
437                 case LINE_CMD_COLOR0:
438                         g_string_sprintfa(str, "\004%c%c",
439                                           '0', FORMAT_COLOR_NOCHANGE);
440                         break;
441                 case LINE_CMD_INDENT:
442                         break;
443                 case LINE_CMD_INDENT_FUNC:
444                         ptr += sizeof(void *);
445                         break;
446                 }
447         }
448 }
449
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)
454 {
455 #ifdef HAVE_REGEX_H
456         regex_t preg;
457 #endif
458         LINE_REC *line, *pre_line;
459         GList *matches;
460         GString *str;
461         int i, match_after, line_matched;
462
463         g_return_val_if_fail(buffer != NULL, NULL);
464         g_return_val_if_fail(text != NULL, NULL);
465
466         if (regexp) {
467 #ifdef HAVE_REGEX_H
468                 int flags = REG_EXTENDED | REG_NOSUB |
469                         (case_sensitive ? 0 : REG_ICASE);
470                 if (regcomp(&preg, text, flags) != 0)
471                         return NULL;
472 #else
473                 return NULL;
474 #endif
475         }
476
477         matches = NULL; match_after = 0;
478         str = g_string_new(NULL);
479
480         line = startline != NULL ? startline : buffer->first_line;
481
482         for (; line != NULL; line = line->next) {
483                 if ((line->info.level & level) == 0 ||
484                     (line->info.level & nolevel) != 0)
485                         continue;
486
487                 if (*text == '\0') {
488                         /* no search word, everything matches */
489                         textbuffer_line_ref(line);
490                         matches = g_list_append(matches, line);
491                         continue;
492                 }
493
494                 textbuffer_line2text(line, FALSE, str);
495
496                 line_matched =
497 #ifdef HAVE_REGEX_H
498                         regexp ? regexec(&preg, str->str, 0, NULL, 0) == 0 :
499 #endif
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;
503                 if (line_matched) {
504                         /* add the -before lines */
505                         pre_line = line;
506                         for (i = 0; i < before; i++) {
507                                 if (pre_line->prev == NULL ||
508                                     g_list_find(matches, pre_line->prev) != NULL)
509                                         break;
510                                 pre_line = pre_line->prev;
511                         }
512
513                         for (; pre_line != line; pre_line = pre_line->next) {
514                                 textbuffer_line_ref(pre_line);
515                                 matches = g_list_append(matches, pre_line);
516                         }
517
518                         match_after = after;
519                 }
520
521                 if (line_matched || match_after > 0) {
522                         /* matched */
523                         textbuffer_line_ref(line);
524                         matches = g_list_append(matches, line);
525
526                         if ((!line_matched && --match_after == 0) ||
527                             (line_matched && match_after == 0 && before > 0))
528                                 matches = g_list_append(matches, NULL);
529                 }
530         }
531 #ifdef HAVE_REGEX_H
532         if (regexp) regfree(&preg);
533 #endif
534         g_string_free(str, TRUE);
535         return matches;
536 }
537
538 void textbuffer_init(void)
539 {
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);
547 }
548
549 void textbuffer_deinit(void)
550 {
551         g_mem_chunk_destroy(buffer_chunk);
552         g_mem_chunk_destroy(line_chunk);
553         g_mem_chunk_destroy(text_chunk);
554 }