imported irssi.
[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         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(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 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 (data[left-1] == 0) left--; /* don't split the commands */
155
156                 memcpy(chunk->buffer + chunk->pos, data, left);
157                 chunk->pos += left;
158
159                 chunk = text_chunk_create(buffer);
160                 chunk->refcount++;
161                 len -= left; data += left;
162         }
163
164         memcpy(chunk->buffer + chunk->pos, data, len);
165         chunk->pos += len;
166
167         mark_temp_eol(chunk);
168 }
169
170 static LINE_REC *textbuffer_line_create(TEXT_BUFFER_REC *buffer)
171 {
172         LINE_REC *rec;
173
174         if (buffer->cur_text == NULL)
175                 text_chunk_create(buffer);
176
177         rec = g_mem_chunk_alloc(line_chunk);
178         rec->refcount = 1;
179         rec->text = buffer->cur_text->buffer + buffer->cur_text->pos;
180
181         buffer->cur_text->refcount++;
182         return rec;
183 }
184
185 static LINE_REC *textbuffer_line_insert(TEXT_BUFFER_REC *buffer,
186                                         LINE_REC *prev)
187 {
188         LINE_REC *line;
189
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);
194         } else {
195                 buffer->lines = g_list_insert(buffer->lines, line,
196                                               g_list_index(buffer->lines, prev)+1);
197         }
198         buffer->lines_count++;
199
200         return line;
201 }
202
203 void textbuffer_line_ref(LINE_REC *line)
204 {
205         g_return_if_fail(line != NULL);
206
207         if (++line->refcount == 255)
208                 g_error("line reference counter wrapped - shouldn't happen");
209 }
210
211 void textbuffer_line_unref(TEXT_BUFFER_REC *buffer, LINE_REC *line)
212 {
213         g_return_if_fail(buffer != NULL);
214         g_return_if_fail(line != NULL);
215
216         if (--line->refcount == 0) {
217                 text_chunk_line_free(buffer, line);
218                 g_mem_chunk_free(line_chunk, line);
219         }
220 }
221
222 void textbuffer_line_unref_list(TEXT_BUFFER_REC *buffer, GList *list)
223 {
224         g_return_if_fail(buffer != NULL);
225
226         while (list != NULL) {
227                 textbuffer_line_unref(buffer, list->data);
228                 list = list->next;
229         }
230 }
231
232 LINE_REC *textbuffer_append(TEXT_BUFFER_REC *buffer,
233                             const unsigned char *data, int len,
234                             LINE_INFO_REC *info)
235 {
236         return textbuffer_insert(buffer, buffer->cur_line, data, len, info);
237 }
238
239 LINE_REC *textbuffer_insert(TEXT_BUFFER_REC *buffer, LINE_REC *insert_after,
240                             const unsigned char *data, int len,
241                             LINE_INFO_REC *info)
242 {
243         LINE_REC *line;
244
245         g_return_val_if_fail(buffer != NULL, NULL);
246         g_return_val_if_fail(data != NULL, NULL);
247
248         line = !buffer->last_eol ? insert_after :
249                 textbuffer_line_insert(buffer, insert_after);
250
251         if (info != NULL)
252                 memcpy(&line->info, info, sizeof(line->info));
253
254         text_chunk_append(buffer, data, len);
255
256         buffer->last_eol = len >= 2 &&
257                 data[len-2] == 0 && data[len-1] == LINE_CMD_EOL;
258
259         return line;
260 }
261
262 void textbuffer_remove(TEXT_BUFFER_REC *buffer, LINE_REC *line)
263 {
264         g_return_if_fail(buffer != NULL);
265         g_return_if_fail(line != NULL);
266
267         buffer->lines = g_list_remove(buffer->lines, line);
268
269         if (buffer->cur_line == line) {
270                 buffer->cur_line = buffer->lines == NULL ? NULL :
271                         g_list_last(buffer->lines)->data;
272         }
273
274         buffer->lines_count--;
275         textbuffer_line_unref(buffer, line);
276 }
277
278 /* Removes all lines from buffer, ignoring reference counters */
279 void textbuffer_remove_all_lines(TEXT_BUFFER_REC *buffer)
280 {
281         GSList *tmp;
282
283         g_return_if_fail(buffer != NULL);
284
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;
289
290         g_list_free(buffer->lines);
291         buffer->lines = NULL;
292
293         buffer->cur_line = NULL;
294         buffer->lines_count = 0;
295 }
296
297 void textbuffer_line2text(LINE_REC *line, int coloring, GString *str)
298 {
299         unsigned char cmd;
300         char *ptr, *tmp;
301
302         g_return_if_fail(line != NULL);
303         g_return_if_fail(str != NULL);
304
305         g_string_truncate(str, 0);
306
307         for (ptr = line->text;;) {
308                 if (*ptr != 0) {
309                         g_string_append_c(str, *ptr);
310                         ptr++;
311                         continue;
312                 }
313
314                 ptr++;
315                 cmd = (unsigned char) *ptr;
316                 ptr++;
317
318                 if (cmd == LINE_CMD_EOL || cmd == LINE_CMD_FORMAT) {
319                         /* end of line */
320                         break;
321                 }
322
323                 if (cmd == LINE_CMD_CONTINUE) {
324                         /* line continues in another address.. */
325                         memcpy(&tmp, ptr, sizeof(char *));
326                         ptr = tmp;
327                         continue;
328                 }
329
330                 if (!coloring) {
331                         /* no colors, skip coloring commands */
332                         continue;
333                 }
334
335                 if ((cmd & 0x80) == 0) {
336                         /* set color */
337                         g_string_sprintfa(str, "\004%c%c",
338                                           (cmd & 0x0f)+'0',
339                                           ((cmd & 0xf0) >> 4)+'0');
340                 } else switch (cmd) {
341                 case LINE_CMD_UNDERLINE:
342                         g_string_append_c(str, 31);
343                         break;
344                 case LINE_CMD_COLOR0:
345                         g_string_sprintfa(str, "\004%c%c",
346                                           '0', FORMAT_COLOR_NOCHANGE);
347                         break;
348                 case LINE_CMD_COLOR8:
349                         g_string_sprintfa(str, "\004%c%c",
350                                           '8', FORMAT_COLOR_NOCHANGE);
351                         break;
352                 case LINE_CMD_BLINK:
353                         g_string_sprintfa(str, "\004%c", FORMAT_STYLE_BLINK);
354                         break;
355                 case LINE_CMD_INDENT:
356                         break;
357                 }
358         }
359 }
360
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)
364 {
365 #ifdef HAVE_REGEX_H
366         regex_t preg;
367 #endif
368         GList *line, *tmp;
369         GList *matches;
370         GString *str;
371
372         g_return_val_if_fail(buffer != NULL, NULL);
373         g_return_val_if_fail(text != NULL, NULL);
374
375         if (regexp) {
376 #ifdef HAVE_REGEX_H
377                 int flags = REG_EXTENDED | REG_NOSUB |
378                         (case_sensitive ? 0 : REG_ICASE);
379                 if (regcomp(&preg, text, flags) != 0)
380                         return NULL;
381 #else
382                 return NULL;
383 #endif
384         }
385
386         matches = NULL;
387         str = g_string_new(NULL);
388
389         line = g_list_find(buffer->lines, startline);
390         if (line == NULL)
391                 line = buffer->lines;
392
393         for (tmp = line; tmp != NULL; tmp = tmp->next) {
394                 LINE_REC *rec = tmp->data;
395
396                 if ((rec->info.level & level) == 0 ||
397                     (rec->info.level & nolevel) != 0)
398                         continue;
399
400                 if (*text == '\0') {
401                         /* no search word, everything matches */
402                         textbuffer_line_ref(rec);
403                         matches = g_list_append(matches, rec);
404                         continue;
405                 }
406
407                 textbuffer_line2text(rec, FALSE, str);
408
409                 if (
410 #ifdef HAVE_REGEX_H
411                     regexp ? regexec(&preg, str->str, 0, NULL, 0) == 0 :
412 #endif
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) {
417                         /* matched */
418                         textbuffer_line_ref(rec);
419                         matches = g_list_append(matches, rec);
420                 }
421         }
422 #ifdef HAVE_REGEX_H
423         if (regexp) regfree(&preg);
424 #endif
425         g_string_free(str, TRUE);
426         return matches;
427 }
428
429 #if 0 /* FIXME: saving formats is broken */
430 static char *line_read_format(unsigned const char **text)
431 {
432         GString *str;
433         char *ret;
434
435         str = g_string_new(NULL);
436         for (;;) {
437                 if (**text == '\0') {
438                         if ((*text)[1] == LINE_CMD_EOL) {
439                                 /* leave text at \0<eof> */
440                                 break;
441                         }
442                         if ((*text)[1] == LINE_CMD_FORMAT_CONT) {
443                                 /* leave text at \0<format_cont> */
444                                 break;
445                         }
446                         (*text)++;
447
448                         if (**text == LINE_CMD_FORMAT) {
449                                 /* move text to start after \0<format> */
450                                 (*text)++;
451                                 break;
452                         }
453
454                         if (**text == LINE_CMD_CONTINUE) {
455                                 unsigned char *tmp;
456
457                                 memcpy(&tmp, (*text)+1, sizeof(char *));
458                                 *text = tmp;
459                                 continue;
460                         } else if (**text & 0x80)
461                                 (*text)++;
462                         continue;
463                 }
464
465                 g_string_append_c(str, (char) **text);
466                 (*text)++;
467         }
468
469         ret = str->str;
470         g_string_free(str, FALSE);
471         return ret;
472 }
473
474 static char *textbuffer_line_get_format(WINDOW_REC *window, LINE_REC *line,
475                                         GString *raw)
476 {
477         const unsigned char *text;
478         char *module, *format_name, *args[MAX_FORMAT_PARAMS], *ret;
479         TEXT_DEST_REC dest;
480         int formatnum, argcount;
481
482         text = (const unsigned char *) line->text;
483
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);
489                 return NULL;
490         }
491
492         /* read format information */
493         module = line_read_format(&text);
494         format_name = line_read_format(&text);
495
496         if (raw != NULL) {
497                 g_string_append_c(raw, '\0');
498                 g_string_append_c(raw, (char)LINE_CMD_FORMAT);
499
500                 g_string_append(raw, module);
501
502                 g_string_append_c(raw, '\0');
503                 g_string_append_c(raw, (char)LINE_CMD_FORMAT);
504
505                 g_string_append(raw, format_name);
506         }
507
508         formatnum = format_find_tag(module, format_name);
509         if (formatnum == -1)
510                 ret = NULL;
511         else {
512                 argcount = 0;
513                 memset(args, 0, sizeof(args));
514                 while (*text != '\0' || text[1] != LINE_CMD_EOL) {
515                         args[argcount] = line_read_format(&text);
516                         if (raw != NULL) {
517                                 g_string_append_c(raw, '\0');
518                                 g_string_append_c(raw,
519                                                   (char)LINE_CMD_FORMAT);
520
521                                 g_string_append(raw, args[argcount]);
522                         }
523                         argcount++;
524                 }
525
526                 /* get the format text */
527                 format_create_dest(&dest, NULL, NULL, line->level, window);
528                 ret = format_get_text_theme_charargs(current_theme,
529                                                      module, &dest,
530                                                      formatnum, args);
531                 while (argcount > 0)
532                         g_free(args[--argcount]);
533         }
534
535         g_free(module);
536         g_free(format_name);
537
538         return ret;
539 }
540
541 void textbuffer_reformat_line(WINDOW_REC *window, LINE_REC *line)
542 {
543         GUI_WINDOW_REC *gui;
544         TEXT_DEST_REC dest;
545         GString *raw;
546         char *str, *tmp, *prestr, *linestart, *leveltag;
547
548         gui = WINDOW_GUI(window);
549
550         raw = g_string_new(NULL);
551         str = textbuffer_line_get_format(window, line, raw);
552
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);
563
564                 textbuffer_line_text_free(gui, line);
565
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;
570
571                 format_create_dest(&dest, NULL, NULL, line->level, window);
572
573                 linestart = format_get_line_start(current_theme, &dest, line->time);
574                 leveltag = format_get_level_tag(current_theme, &dest);
575
576                 prestr = g_strconcat(linestart == NULL ? "" : linestart,
577                                      leveltag, NULL);
578                 g_free_not_null(linestart);
579                 g_free_not_null(leveltag);
580
581                 tmp = format_add_linestart(str, prestr);
582                 g_free(str);
583                 g_free(prestr);
584
585                 format_send_to_gui(&dest, tmp);
586                 g_free(tmp);
587
588                 textbuffer_line_append(gui, raw->str, raw->len);
589
590                 gui->eol_marked = TRUE;
591                 gui->temp_line = NULL;
592         }
593         g_string_free(raw, TRUE);
594 }
595 #endif
596
597 void textbuffer_init(void)
598 {
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);
606 }
607
608 void textbuffer_deinit(void)
609 {
610         g_mem_chunk_destroy(buffer_chunk);
611         g_mem_chunk_destroy(line_chunk);
612         g_mem_chunk_destroy(text_chunk);
613 }