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