Merge Irssi 0.8.16-rc1
[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 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.
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 TEXT_BUFFER_REC *textbuffer_create(void)
36 {
37         TEXT_BUFFER_REC *buffer;
38
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;
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_slice_free(TEXT_BUFFER_REC, 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_slice_new(TEXT_CHUNK_REC);
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_slice_free(TEXT_CHUNK_REC, 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                 }
142         }
143 }
144
145 static void text_chunk_append(TEXT_BUFFER_REC *buffer,
146                               const unsigned char *data, int len)
147 {
148         TEXT_CHUNK_REC *chunk;
149         int left;
150
151         if (len == 0)
152                 return;
153
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 */
159
160                 memcpy(chunk->buffer + chunk->pos, data, left);
161                 chunk->pos += left;
162
163                 chunk = text_chunk_create(buffer);
164                 chunk->refcount++;
165                 len -= left; data += left;
166         }
167
168         memcpy(chunk->buffer + chunk->pos, data, len);
169         chunk->pos += len;
170
171         mark_temp_eol(chunk);
172 }
173
174 static LINE_REC *textbuffer_line_create(TEXT_BUFFER_REC *buffer)
175 {
176         LINE_REC *rec;
177
178         if (buffer->cur_text == NULL)
179                 text_chunk_create(buffer);
180
181         rec = g_slice_new(LINE_REC);
182         rec->text = buffer->cur_text->buffer + buffer->cur_text->pos;
183
184         buffer->cur_text->refcount++;
185         return rec;
186 }
187
188 static LINE_REC *textbuffer_line_insert(TEXT_BUFFER_REC *buffer,
189                                         LINE_REC *prev)
190 {
191         LINE_REC *line;
192
193         line = textbuffer_line_create(buffer);
194         line->prev = prev;
195         if (prev == NULL) {
196                 line->next = buffer->first_line;
197                 if (buffer->first_line != NULL)
198                         buffer->first_line->prev = line;
199                 buffer->first_line = line;
200         } else {
201                 line->next = prev->next;
202                 if (line->next != NULL)
203                         line->next->prev = line;
204                 prev->next = line;
205         }
206
207         if (prev == buffer->cur_line)
208                 buffer->cur_line = line;
209         buffer->lines_count++;
210
211         return line;
212 }
213
214 LINE_REC *textbuffer_line_last(TEXT_BUFFER_REC *buffer)
215 {
216         return buffer->cur_line;
217 }
218
219 int textbuffer_line_exists_after(LINE_REC *line, LINE_REC *search)
220 {
221         while (line != NULL) {
222                 if (line == search)
223                         return TRUE;
224                 line = line->next;
225         }
226         return FALSE;
227 }
228
229 void textbuffer_line_add_colors(TEXT_BUFFER_REC *buffer, LINE_REC **line,
230                                 int fg, int bg, int flags)
231 {
232         unsigned char data[20];
233         int pos;
234
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);
238
239         pos = 0;
240         if (fg != buffer->last_fg) {
241                 buffer->last_fg = fg;
242                 data[pos++] = 0;
243                 data[pos++] = fg == 0 ? LINE_CMD_COLOR0 : fg;
244         }
245         if (bg != buffer->last_bg) {
246                 buffer->last_bg = bg;
247                 data[pos++] = 0;
248                 data[pos++] = bg;
249         }
250
251         if ((flags & GUI_PRINT_FLAG_UNDERLINE) != (buffer->last_flags & GUI_PRINT_FLAG_UNDERLINE)) {
252                 data[pos++] = 0;
253                 data[pos++] = LINE_CMD_UNDERLINE;
254         }
255         if ((flags & GUI_PRINT_FLAG_REVERSE) != (buffer->last_flags & GUI_PRINT_FLAG_REVERSE)) {
256                 data[pos++] = 0;
257                 data[pos++] = LINE_CMD_REVERSE;
258         }
259         if ((flags & GUI_PRINT_FLAG_BLINK) != (buffer->last_flags & GUI_PRINT_FLAG_BLINK)) {
260                 data[pos++] = 0;
261                 data[pos++] = LINE_CMD_BLINK;
262         }
263         if ((flags & GUI_PRINT_FLAG_BOLD) != (buffer->last_flags & GUI_PRINT_FLAG_BOLD)) {
264                 data[pos++] = 0;
265                 data[pos++] = LINE_CMD_BOLD;
266         }
267         if (flags & GUI_PRINT_FLAG_INDENT) {
268                 data[pos++] = 0;
269                 data[pos++] = LINE_CMD_INDENT;
270         }
271
272         if (pos > 0)
273                 *line = textbuffer_insert(buffer, *line, data, pos, NULL);
274
275         buffer->last_flags = flags;
276 }
277
278 LINE_REC *textbuffer_append(TEXT_BUFFER_REC *buffer,
279                             const unsigned char *data, int len,
280                             LINE_INFO_REC *info)
281 {
282         return textbuffer_insert(buffer, buffer->cur_line, data, len, info);
283 }
284
285 LINE_REC *textbuffer_insert(TEXT_BUFFER_REC *buffer, LINE_REC *insert_after,
286                             const unsigned char *data, int len,
287                             LINE_INFO_REC *info)
288 {
289         LINE_REC *line;
290
291         g_return_val_if_fail(buffer != NULL, NULL);
292         g_return_val_if_fail(data != NULL, NULL);
293
294         if (len == 0)
295                 return insert_after;
296
297         line = !buffer->last_eol ? insert_after :
298                 textbuffer_line_insert(buffer, insert_after);
299
300         if (info != NULL)
301                 memcpy(&line->info, info, sizeof(line->info));
302
303         text_chunk_append(buffer, data, len);
304
305         buffer->last_eol = len >= 2 &&
306                 data[len-2] == 0 && data[len-1] == LINE_CMD_EOL;
307
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;
312         }
313
314         return line;
315 }
316
317 void textbuffer_remove(TEXT_BUFFER_REC *buffer, LINE_REC *line)
318 {
319         g_return_if_fail(buffer != NULL);
320         g_return_if_fail(line != NULL);
321
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;
328
329         if (buffer->cur_line == line) {
330                 buffer->cur_line = line->prev;
331         }
332
333         line->prev = line->next = NULL;
334
335         buffer->lines_count--;
336         text_chunk_line_free(buffer, line);
337         g_slice_free(LINE_REC, line);
338 }
339
340 /* Removes all lines from buffer */
341 void textbuffer_remove_all_lines(TEXT_BUFFER_REC *buffer)
342 {
343         GSList *tmp;
344         LINE_REC *line;
345
346         g_return_if_fail(buffer != NULL);
347
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;
352
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;
357         }
358         buffer->lines_count = 0;
359
360         buffer->cur_line = NULL;
361         buffer->cur_text = NULL;
362
363         buffer->last_eol = TRUE;
364 }
365
366 static void set_color(GString *str, int cmd)
367 {
368         int color = -1;
369
370         if (!(cmd & LINE_COLOR_DEFAULT))
371                 color = (cmd & 0x0f)+'0';
372
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);
377         } else {
378                 /* change background color */
379                 g_string_append_printf(str, "\004%c%c",
380                                   FORMAT_COLOR_NOCHANGE, color);
381         }
382 }
383
384 void textbuffer_line2text(LINE_REC *line, int coloring, GString *str)
385 {
386         unsigned char cmd, *ptr, *tmp;
387
388         g_return_if_fail(line != NULL);
389         g_return_if_fail(str != NULL);
390
391         g_string_truncate(str, 0);
392
393         for (ptr = line->text;;) {
394                 if (*ptr != 0) {
395                         g_string_append_c(str, (char) *ptr);
396                         ptr++;
397                         continue;
398                 }
399
400                 ptr++;
401                 cmd = *ptr;
402                 ptr++;
403
404                 if (cmd == LINE_CMD_EOL) {
405                         /* end of line */
406                         break;
407                 }
408
409                 if (cmd == LINE_CMD_CONTINUE) {
410                         /* line continues in another address.. */
411                         memcpy(&tmp, ptr, sizeof(unsigned char *));
412                         ptr = tmp;
413                         continue;
414                 }
415
416                 if (!coloring) {
417                         /* no colors, skip coloring commands */
418                         continue;
419                 }
420
421                 if ((cmd & 0x80) == 0) {
422                         /* set color */
423                         set_color(str, cmd);
424                 } else switch (cmd) {
425                 case LINE_CMD_UNDERLINE:
426                         g_string_append_c(str, 31);
427                         break;
428                 case LINE_CMD_REVERSE:
429                         g_string_append_c(str, 22);
430                         break;
431                 case LINE_CMD_BLINK:
432                         g_string_append_printf(str, "\004%c",
433                                           FORMAT_STYLE_BLINK);
434                         break;
435                 case LINE_CMD_BOLD:
436                         g_string_append_printf(str, "\004%c",
437                                           FORMAT_STYLE_BOLD);
438                         break;
439                 case LINE_CMD_COLOR0:
440                         g_string_append_printf(str, "\004%c%c",
441                                           '0', FORMAT_COLOR_NOCHANGE);
442                         break;
443                 case LINE_CMD_INDENT:
444                         g_string_append_printf(str, "\004%c",
445                                           FORMAT_STYLE_INDENT);
446                         break;
447                 }
448         }
449 }
450
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)
455 {
456 #ifdef HAVE_REGEX_H
457         regex_t preg;
458 #endif
459         LINE_REC *line, *pre_line;
460         GList *matches;
461         GString *str;
462         int i, match_after, line_matched;
463         char * (*match_func)(const char *, const char *);
464
465         g_return_val_if_fail(buffer != NULL, NULL);
466         g_return_val_if_fail(text != NULL, NULL);
467
468         if (regexp) {
469 #ifdef HAVE_REGEX_H
470                 int flags = REG_EXTENDED | REG_NOSUB |
471                         (case_sensitive ? 0 : REG_ICASE);
472                 if (regcomp(&preg, text, flags) != 0)
473                         return NULL;
474 #else
475                 return NULL;
476 #endif
477         }
478
479         matches = NULL; match_after = 0;
480         str = g_string_new(NULL);
481
482         line = startline != NULL ? startline : buffer->first_line;
483
484         if (fullword)
485                 match_func = case_sensitive ? strstr_full : stristr_full;
486         else
487                 match_func = case_sensitive ? strstr : stristr;
488
489         for (; line != NULL; line = line->next) {
490                 line_matched = (line->info.level & level) != 0 &&
491                         (line->info.level & nolevel) == 0;
492
493                 if (*text != '\0') {
494                         textbuffer_line2text(line, FALSE, str);
495
496                         if (line_matched)
497                         line_matched =
498 #ifdef HAVE_REGEX_H
499                         regexp ? regexec(&preg, str->str, 0, NULL, 0) == 0 :
500 #endif
501                         match_func(str->str, text) != NULL;
502                 }
503
504                 if (line_matched) {
505                         /* add the -before lines */
506                         pre_line = line;
507                         for (i = 0; i < before; i++) {
508                                 if (pre_line->prev == NULL ||
509                                     g_list_find(matches, pre_line->prev) != NULL)
510                                         break;
511                                 pre_line = pre_line->prev;
512                         }
513
514                         for (; pre_line != line; pre_line = pre_line->next)
515                                 matches = g_list_append(matches, pre_line);
516
517                         match_after = after;
518                 }
519
520                 if (line_matched || match_after > 0) {
521                         /* matched */
522                         matches = g_list_append(matches, line);
523
524                         if ((!line_matched && --match_after == 0) ||
525                             (line_matched && match_after == 0 && before > 0))
526                                 matches = g_list_append(matches, NULL);
527                 }
528         }
529 #ifdef HAVE_REGEX_H
530         if (regexp) regfree(&preg);
531 #endif
532         g_string_free(str, TRUE);
533         return matches;
534 }
535
536 void textbuffer_init(void)
537 {
538 }
539
540 void textbuffer_deinit(void)
541 {
542 }