Merge Irssi 0.8.16-rc1
[silc.git] / apps / irssi / src / fe-text / textbuffer-view.c
1 /*
2  textbuffer-view.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 "TextBufferView"
22
23 #include "module.h"
24 #include "textbuffer-view.h"
25 #include "utf8.h"
26
27 typedef struct {
28         char *name;
29         LINE_REC *line;
30 } BOOKMARK_REC;
31
32 /* how often to scan line cache for lines not accessed for a while (ms) */
33 #define LINE_CACHE_CHECK_TIME (5*60*1000)
34 /* how long to keep line cache in memory (seconds) */
35 #define LINE_CACHE_KEEP_TIME (10*60)
36
37 static int linecache_tag;
38 static GSList *views;
39
40 #define view_is_bottom(view) \
41         ((view)->ypos >= -1 && (view)->ypos < (view)->height)
42
43 #define view_get_linecount(view, line) \
44         textbuffer_view_get_line_cache(view, line)->count
45
46 static GSList *textbuffer_get_views(TEXT_BUFFER_REC *buffer)
47 {
48         GSList *tmp, *list;
49
50         for (tmp = views; tmp != NULL; tmp = tmp->next) {
51                 TEXT_BUFFER_VIEW_REC *view = tmp->data;
52
53                 if (view->buffer == buffer) {
54                         list = g_slist_copy(view->siblings);
55                         return g_slist_prepend(list, view);
56                 }
57         }
58
59         return NULL;
60 }
61
62 static TEXT_BUFFER_CACHE_REC *
63 textbuffer_cache_get(GSList *views, int width)
64 {
65         TEXT_BUFFER_CACHE_REC *cache;
66
67         /* check if there's existing cache with correct width */
68         while (views != NULL) {
69                 TEXT_BUFFER_VIEW_REC *view = views->data;
70
71                 if (view->width == width) {
72                         view->cache->refcount++;
73                         return view->cache;
74                 }
75                 views = views->next;
76         }
77
78         /* create new cache */
79         cache = g_new0(TEXT_BUFFER_CACHE_REC, 1);
80         cache->refcount = 1;
81         cache->width = width;
82         cache->line_cache = g_hash_table_new((GHashFunc) g_direct_hash,
83                                              (GCompareFunc) g_direct_equal);
84         return cache;
85 }
86
87 static int line_cache_destroy(void *key, LINE_CACHE_REC *cache)
88 {
89         g_free(cache);
90         return TRUE;
91 }
92
93 static void textbuffer_cache_destroy(TEXT_BUFFER_CACHE_REC *cache)
94 {
95         g_hash_table_foreach(cache->line_cache,
96                              (GHFunc) line_cache_destroy, NULL);
97         g_hash_table_destroy(cache->line_cache);
98         g_free(cache);
99 }
100
101 static void textbuffer_cache_unref(TEXT_BUFFER_CACHE_REC *cache)
102 {
103         if (--cache->refcount == 0)
104                 textbuffer_cache_destroy(cache);
105 }
106
107 #define FGATTR (ATTR_NOCOLORS | ATTR_RESETFG | 0x0f)
108 #define BGATTR (ATTR_NOCOLORS | ATTR_RESETBG | 0xf0)
109
110 static void update_cmd_color(unsigned char cmd, int *color)
111 {
112         if ((cmd & 0x80) == 0) {
113                 if (cmd & LINE_COLOR_BG) {
114                         /* set background color */
115                         *color &= FGATTR;
116                         if ((cmd & LINE_COLOR_DEFAULT) == 0)
117                                 *color |= (cmd & 0x0f) << 4;
118                         else {
119                                 *color = (*color & FGATTR) | ATTR_RESETBG;
120                         }
121                 } else {
122                         /* set foreground color */
123                         *color &= BGATTR;
124                         if ((cmd & LINE_COLOR_DEFAULT) == 0)
125                                 *color |= cmd & 0x0f;
126                         else {
127                                 *color = (*color & BGATTR) | ATTR_RESETFG;
128                         }
129                 }
130         } else switch (cmd) {
131         case LINE_CMD_UNDERLINE:
132                 *color ^= ATTR_UNDERLINE;
133                 break;
134         case LINE_CMD_REVERSE:
135                 *color ^= ATTR_REVERSE;
136                 break;
137         case LINE_CMD_BLINK:
138                 *color ^= ATTR_BLINK;
139                 break;
140         case LINE_CMD_BOLD:
141                 *color ^= ATTR_BOLD;
142                 break;
143         case LINE_CMD_COLOR0:
144                 *color &= BGATTR;
145                 break;
146         }
147 }
148
149 static inline unichar read_unichar(const unsigned char *data, const unsigned char **next, int *width)
150 {
151         unichar chr = g_utf8_get_char_validated(data, -1);
152
153         if (chr & 0x80000000) {
154                 chr = 0xfffd;
155                 *next = data + 1;
156                 *width = 1;
157         } else {
158                 *next = g_utf8_next_char(data);
159                 *width = unichar_isprint(chr) ? mk_wcwidth(chr) : 1;
160         }
161         return chr;
162 }
163
164 static LINE_CACHE_REC *
165 view_update_line_cache(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
166 {
167         INDENT_FUNC indent_func;
168         LINE_CACHE_REC *rec;
169         LINE_CACHE_SUB_REC *sub;
170         GSList *lines;
171         unsigned char cmd;
172         const unsigned char *ptr, *next_ptr, *last_space_ptr;
173         int xpos, pos, indent_pos, last_space, last_color, color, linecount;
174         int char_width;
175
176         g_return_val_if_fail(line->text != NULL, NULL);
177
178         color = ATTR_RESETFG | ATTR_RESETBG;
179         xpos = 0; indent_pos = view->default_indent;
180         last_space = last_color = 0; last_space_ptr = NULL; sub = NULL;
181
182         indent_func = view->default_indent_func;
183         linecount = 1;
184         lines = NULL;
185         for (ptr = line->text;;) {
186                 if (*ptr == '\0') {
187                         /* command */
188                         ptr++;
189                         cmd = *ptr;
190                         ptr++;
191
192                         if (cmd == LINE_CMD_EOL)
193                                 break;
194
195                         if (cmd == LINE_CMD_CONTINUE) {
196                                 unsigned char *tmp;
197
198                                 memcpy(&tmp, ptr, sizeof(char *));
199                                 ptr = tmp;
200                                 continue;
201                         }
202
203                         if (cmd == LINE_CMD_INDENT) {
204                                 /* set indentation position here - don't do
205                                    it if we're too close to right border */
206                                 if (xpos < view->width-5) indent_pos = xpos;
207                         } else
208                                 update_cmd_color(cmd, &color);
209                         continue;
210                 }
211
212                 if (!view->utf8) {
213                         /* MH */
214                         if (term_type != TERM_TYPE_BIG5 ||
215                             ptr[1] == '\0' || !is_big5(ptr[0], ptr[1]))
216                                 char_width = 1;
217                         else
218                                 char_width = 2;
219                         next_ptr = ptr+char_width;
220                 } else {
221                         read_unichar(ptr, &next_ptr, &char_width);
222                 }
223
224                 if (xpos + char_width > view->width && sub != NULL &&
225                     (last_space <= indent_pos || last_space <= 10) &&
226                     view->longword_noindent) {
227                         /* long word, remove the indentation from this line */
228                         xpos -= sub->indent;
229                         sub->indent = 0;
230                 }
231
232                 if (xpos + char_width > view->width) {
233                         xpos = indent_func == NULL ? indent_pos :
234                                 indent_func(view, line, -1);
235
236                         sub = g_new0(LINE_CACHE_SUB_REC, 1);
237                         if (last_space > indent_pos && last_space > 10) {
238                                 /* go back to last space */
239                                 color = last_color;
240                                 ptr = last_space_ptr;
241                                 while (*ptr == ' ') ptr++;
242                         } else if (view->longword_noindent) {
243                                 /* long word, no indentation in next line */
244                                 xpos = 0;
245                                 sub->continues = TRUE;
246                         }
247
248                         sub->start = ptr;
249                         sub->indent = xpos;
250                         sub->indent_func = indent_func;
251                         sub->color = color;
252
253                         lines = g_slist_append(lines, sub);
254                         linecount++;
255
256                         last_space = 0;
257                         continue;
258                 }
259
260                 if (!view->utf8 && char_width > 1) {
261                         last_space = xpos;
262                         last_space_ptr = next_ptr;
263                         last_color = color;
264                 } else if (*ptr == ' ') {
265                         last_space = xpos;
266                         last_space_ptr = ptr;
267                         last_color = color;
268                 }
269
270                 xpos += char_width;
271                 ptr = next_ptr;
272         }
273
274         rec = g_malloc(sizeof(LINE_CACHE_REC)-sizeof(LINE_CACHE_SUB_REC) +
275                        sizeof(LINE_CACHE_SUB_REC) * (linecount-1));
276         rec->last_access = time(NULL);
277         rec->count = linecount;
278
279         if (rec->count > 1) {
280                 for (pos = 0; lines != NULL; pos++) {
281                         void *data = lines->data;
282
283                         memcpy(&rec->lines[pos], data,
284                                sizeof(LINE_CACHE_SUB_REC));
285
286                         lines = g_slist_remove(lines, data);
287                         g_free(data);
288                 }
289         }
290
291         g_hash_table_insert(view->cache->line_cache, line, rec);
292         return rec;
293 }
294
295 static void view_remove_cache(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
296                               unsigned char update_counter)
297 {
298         LINE_CACHE_REC *cache;
299
300         if (view->cache->update_counter == update_counter)
301                 return;
302         view->cache->update_counter = update_counter;
303
304         cache = g_hash_table_lookup(view->cache->line_cache, line);
305         if (cache != NULL) {
306                 g_free(cache);
307                 g_hash_table_remove(view->cache->line_cache, line);
308         }
309 }
310
311 static void view_update_cache(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
312                               unsigned char update_counter)
313 {
314         view_remove_cache(view, line, update_counter);
315
316         if (view->buffer->cur_line == line)
317                 view->cache->last_linecount = view_get_linecount(view, line);
318 }
319
320 static void view_reset_cache(TEXT_BUFFER_VIEW_REC *view)
321 {
322         GSList *tmp;
323
324         /* destroy line caches - note that you can't do simultaneously
325            unrefs + cache_get()s or it will keep using the old caches */
326         textbuffer_cache_unref(view->cache);
327         g_slist_foreach(view->siblings, (GFunc) textbuffer_cache_unref, NULL);
328
329         view->cache = textbuffer_cache_get(view->siblings, view->width);
330         for (tmp = view->siblings; tmp != NULL; tmp = tmp->next) {
331                 TEXT_BUFFER_VIEW_REC *rec = tmp->data;
332
333                 rec->cache = textbuffer_cache_get(rec->siblings, rec->width);
334         }
335 }
336
337 static int view_line_draw(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
338                           int subline, int ypos, int max)
339 {
340         INDENT_FUNC indent_func;
341         LINE_CACHE_REC *cache;
342         const unsigned char *text, *end, *text_newline;
343         unsigned char *tmp;
344         unichar chr;
345         int xpos, color, drawcount, first, need_move, need_clrtoeol, char_width;
346
347         if (view->dirty) /* don't bother drawing anything - redraw is coming */
348                 return 0;
349
350         cache = textbuffer_view_get_line_cache(view, line);
351         if (subline >= cache->count)
352                 return 0;
353
354         color = ATTR_RESET;
355         need_move = TRUE; need_clrtoeol = FALSE;
356         xpos = drawcount = 0; first = TRUE;
357         text_newline = text =
358                 subline == 0 ? line->text : cache->lines[subline-1].start;
359         for (;;) {
360                 if (text == text_newline) {
361                         if (need_clrtoeol && xpos < term_width) {
362                                 term_set_color(view->window, ATTR_RESET);
363                                 term_clrtoeol(view->window);
364                         }
365
366                         if (first)
367                                 first = FALSE;
368                         else {
369                                 ypos++;
370                                 if (--max == 0)
371                                         break;
372                         }
373
374                         if (subline > 0) {
375                                 /* continuing previous line - indent it */
376                                 indent_func = cache->lines[subline-1].indent_func;
377                                 if (indent_func == NULL)
378                                         xpos = cache->lines[subline-1].indent;
379                                 color = cache->lines[subline-1].color;
380                         } else {
381                                 indent_func = NULL;
382                         }
383
384                         if (xpos == 0 && indent_func == NULL)
385                                 need_clrtoeol = TRUE;
386                         else {
387                                 /* line was indented - need to clear the
388                                    indented area first */
389                                 term_set_color(view->window, ATTR_RESET);
390                                 term_move(view->window, 0, ypos);
391                                 term_clrtoeol(view->window);
392
393                                 if (indent_func != NULL)
394                                         xpos = indent_func(view, line, ypos);
395                         }
396
397                         if (need_move || xpos > 0)
398                                 term_move(view->window, xpos, ypos);
399
400                         term_set_color(view->window, color);
401
402                         if (subline == cache->count-1) {
403                                 text_newline = NULL;
404                                 need_move = FALSE;
405                         } else {
406                                 /* get the beginning of the next subline */
407                                 text_newline = cache->lines[subline].start;
408                                 need_move = !cache->lines[subline].continues;
409                         }
410                         drawcount++;
411                         subline++;
412                 }
413
414                 if (*text == '\0') {
415                         /* command */
416                         text++;
417                         if (*text == LINE_CMD_EOL)
418                                 break;
419
420                         if (*text == LINE_CMD_CONTINUE) {
421                                 /* jump to next block */
422                                 memcpy(&tmp, text+1, sizeof(unsigned char *));
423                                 text = tmp;
424                                 continue;
425                         } else {
426                                 update_cmd_color(*text, &color);
427                                 term_set_color(view->window, color);
428                         }
429                         text++;
430                         continue;
431                 }
432
433                 if (view->utf8) {
434                         chr = read_unichar(text, &end, &char_width);
435                 } else {
436                         chr = *text;
437                         end = text;
438                         if (term_type == TERM_TYPE_BIG5 &&
439                             is_big5(end[0], end[1]))
440                                 char_width = 2;
441                         else
442                                 char_width = 1;
443                         end += char_width;
444                 }
445
446                 xpos += char_width;
447                 if (xpos <= term_width) {
448                         if (unichar_isprint(chr)) {
449                                 if (view->utf8)
450                                 term_add_unichar(view->window, chr);
451                                 else
452                                 for (; text < end; text++)
453                                         term_addch(view->window, *text);
454                         } else {
455                                 /* low-ascii */
456                                 term_set_color(view->window, ATTR_RESET|ATTR_REVERSE);
457                                 term_addch(view->window, (chr & 127)+'A'-1);
458                                 term_set_color(view->window, color);
459                         }
460                 }
461                 text = end;
462         }
463
464         if (need_clrtoeol && xpos < term_width) {
465                 term_set_color(view->window, ATTR_RESET);
466                 term_clrtoeol(view->window);
467         }
468
469         return drawcount;
470 }
471
472 /* Recalculate view's bottom line information - try to keep the
473    original if possible */
474 static void textbuffer_view_init_bottom(TEXT_BUFFER_VIEW_REC *view)
475 {
476         LINE_REC *line;
477         int linecount, total;
478
479         if (view->empty_linecount == 0) {
480                 /* no empty lines in screen, no need to try to keep
481                    the old bottom startline */
482                 view->bottom_startline = NULL;
483         }
484
485         total = 0;
486         line = textbuffer_line_last(view->buffer);
487         for (; line != NULL; line = line->prev) {
488                 linecount = view_get_linecount(view, line);
489                 if (line == view->bottom_startline) {
490                         /* keep the old one, make sure that subline is ok */
491                         if (view->bottom_subline > linecount)
492                                 view->bottom_subline = linecount;
493                         view->empty_linecount = view->height - total -
494                                 (linecount-view->bottom_subline);
495                         return;
496                 }
497
498                 total += linecount;
499                 if (total >= view->height) {
500                         view->bottom_startline = line;
501                         view->bottom_subline = total - view->height;
502                         view->empty_linecount = 0;
503                         return;
504                 }
505         }
506
507         /* not enough lines so we must be at the beginning of the buffer */
508         view->bottom_startline = view->buffer->first_line;
509         view->bottom_subline = 0;
510         view->empty_linecount = view->height - total;
511 }
512
513 static void textbuffer_view_init_ypos(TEXT_BUFFER_VIEW_REC *view)
514 {
515         LINE_REC *line;
516
517         g_return_if_fail(view != NULL);
518
519         view->ypos = -view->subline-1;
520         for (line = view->startline; line != NULL; line = line->next)
521                 view->ypos += view_get_linecount(view, line);
522 }
523
524 /* Create new view. */
525 TEXT_BUFFER_VIEW_REC *textbuffer_view_create(TEXT_BUFFER_REC *buffer,
526                                              int width, int height,
527                                              int scroll, int utf8)
528 {
529         TEXT_BUFFER_VIEW_REC *view;
530
531         g_return_val_if_fail(buffer != NULL, NULL);
532         g_return_val_if_fail(width > 0, NULL);
533
534         view = g_new0(TEXT_BUFFER_VIEW_REC, 1);
535         view->buffer = buffer;
536         view->siblings = textbuffer_get_views(buffer);
537
538         view->width = width;
539         view->height = height;
540         view->scroll = scroll;
541         view->utf8 = utf8;
542
543         view->cache = textbuffer_cache_get(view->siblings, width);
544         textbuffer_view_init_bottom(view);
545
546         view->startline = view->bottom_startline;
547         view->subline = view->bottom_subline;
548         view->bottom = TRUE;
549
550         textbuffer_view_init_ypos(view);
551
552         view->bookmarks = g_hash_table_new((GHashFunc) g_str_hash,
553                                            (GCompareFunc) g_str_equal);
554
555         views = g_slist_append(views, view);
556         return view;
557 }
558
559 /* Destroy the view. */
560 void textbuffer_view_destroy(TEXT_BUFFER_VIEW_REC *view)
561 {
562         GSList *tmp;
563
564         g_return_if_fail(view != NULL);
565
566         views = g_slist_remove(views, view);
567
568         if (view->siblings == NULL) {
569                 /* last view for textbuffer, destroy */
570                 textbuffer_destroy(view->buffer);
571         } else {
572                 /* remove ourself from siblings lists */
573                 for (tmp = view->siblings; tmp != NULL; tmp = tmp->next) {
574                         TEXT_BUFFER_VIEW_REC *rec = tmp->data;
575
576                         rec->siblings = g_slist_remove(rec->siblings, view);
577                 }
578                 g_slist_free(view->siblings);
579         }
580
581         g_hash_table_foreach(view->bookmarks, (GHFunc) g_free, NULL);
582         g_hash_table_destroy(view->bookmarks);
583
584         textbuffer_cache_unref(view->cache);
585         g_free(view);
586 }
587
588 /* Change the default indent position */
589 void textbuffer_view_set_default_indent(TEXT_BUFFER_VIEW_REC *view,
590                                         int default_indent,
591                                         int longword_noindent,
592                                         INDENT_FUNC indent_func)
593 {
594         if (default_indent != -1)
595                 view->default_indent = default_indent;
596         if (longword_noindent != -1)
597                 view->longword_noindent = longword_noindent;
598
599         view->default_indent_func = indent_func;
600 }
601
602 static void view_unregister_indent_func(TEXT_BUFFER_VIEW_REC *view,
603                                         INDENT_FUNC indent_func)
604 {
605         if (view->default_indent_func == indent_func)
606                 view->default_indent_func = NULL;
607
608         /* recreate cache so it won't contain references
609            to the indent function */
610         view_reset_cache(view);
611         view->cache = textbuffer_cache_get(view->siblings, view->width);
612 }
613
614 void textbuffer_views_unregister_indent_func(INDENT_FUNC indent_func)
615 {
616         g_slist_foreach(views, (GFunc) view_unregister_indent_func,
617                         (void *) indent_func);
618 }
619
620 void textbuffer_view_set_scroll(TEXT_BUFFER_VIEW_REC *view, int scroll)
621 {
622         view->scroll = scroll;
623 }
624
625 void textbuffer_view_set_utf8(TEXT_BUFFER_VIEW_REC *view, int utf8)
626 {
627         view->utf8 = utf8;
628 }
629
630 static int view_get_linecount_all(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
631 {
632         int linecount;
633
634         linecount = 0;
635         while (line != NULL) {
636                 linecount += view_get_linecount(view, line);
637                 line = line->next;
638         }
639
640         return linecount;
641 }
642
643 static void view_draw(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
644                       int subline, int ypos, int lines, int fill_bottom)
645 {
646         int linecount;
647
648         if (view->dirty) /* don't bother drawing anything - redraw is coming */
649                 return;
650
651         while (line != NULL && lines > 0) {
652                 linecount = view_line_draw(view, line, subline, ypos, lines);
653                 ypos += linecount; lines -= linecount;
654
655                 subline = 0;
656                 line = line->next;
657         }
658
659         if (fill_bottom) {
660                 /* clear the rest of the view */
661                 term_set_color(view->window, ATTR_RESET);
662                 while (lines > 0) {
663                         term_move(view->window, 0, ypos);
664                         term_clrtoeol(view->window);
665                         ypos++; lines--;
666                 }
667         }
668 }
669
670 #define view_draw_top(view, lines, fill_bottom) \
671         view_draw(view, (view)->startline, (view)->subline, \
672                   0, lines, fill_bottom)
673
674 static void view_draw_bottom(TEXT_BUFFER_VIEW_REC *view, int lines)
675 {
676         LINE_REC *line;
677         int ypos, maxline, subline, linecount;
678
679         maxline = view->height-lines;
680         line = view->startline; ypos = -view->subline; subline = 0;
681         while (line != NULL && ypos < maxline) {
682                 linecount = view_get_linecount(view, line);
683                 ypos += linecount;
684                 if (ypos > maxline) {
685                         subline = maxline-(ypos-linecount);
686                         break;
687                 }
688                 line = line->next;
689         }
690
691         view_draw(view, line, subline, maxline, lines, TRUE);
692 }
693
694 /* Returns number of lines actually scrolled */
695 static int view_scroll(TEXT_BUFFER_VIEW_REC *view, LINE_REC **lines,
696                        int *subline, int scrollcount, int draw_nonclean)
697 {
698         int linecount, realcount, scroll_visible;
699
700         if (*lines == NULL)
701                 return 0;
702
703         /* scroll down */
704         scroll_visible = lines == &view->startline;
705
706         realcount = -*subline;
707         scrollcount += *subline;
708         *subline = 0;
709         while (scrollcount > 0) {
710                 linecount = view_get_linecount(view, *lines);
711
712                 if ((scroll_visible && *lines == view->bottom_startline) &&
713                     (scrollcount >= view->bottom_subline)) {
714                         *subline = view->bottom_subline;
715                         realcount += view->bottom_subline;
716                         scrollcount = 0;
717                         break;
718                 }
719
720                 realcount += linecount;
721                 scrollcount -= linecount;
722                 if (scrollcount < 0) {
723                         realcount += scrollcount;
724                         *subline = linecount+scrollcount;
725                         scrollcount = 0;
726                         break;
727                 }
728
729                 if ((*lines)->next == NULL)
730                         break;
731
732                 *lines = (*lines)->next;
733         }
734
735         /* scroll up */
736         while (scrollcount < 0 && (*lines)->prev != NULL) {
737                 *lines = (*lines)->prev;
738                 linecount = view_get_linecount(view, *lines);
739
740                 realcount -= linecount;
741                 scrollcount += linecount;
742                 if (scrollcount > 0) {
743                         realcount += scrollcount;
744                         *subline = scrollcount;
745                         break;
746                 }
747         }
748
749         if (scroll_visible && realcount != 0 && view->window != NULL) {
750                 if (realcount <= -view->height || realcount >= view->height) {
751                         /* scrolled more than screenful, redraw the
752                            whole view */
753                         textbuffer_view_redraw(view);
754                 } else {
755                         term_set_color(view->window, ATTR_RESET);
756                         term_window_scroll(view->window, realcount);
757
758                         if (draw_nonclean) {
759                                 if (realcount < 0)
760                                         view_draw_top(view, -realcount, TRUE);
761                                 else
762                                         view_draw_bottom(view, realcount);
763                         }
764
765                         term_refresh(view->window);
766                 }
767         }
768
769         return realcount >= 0 ? realcount : -realcount;
770 }
771
772 /* Resize the view. */
773 void textbuffer_view_resize(TEXT_BUFFER_VIEW_REC *view, int width, int height)
774 {
775         int linecount;
776
777         g_return_if_fail(view != NULL);
778         g_return_if_fail(width > 0);
779
780         if (view->width != width) {
781                 /* line cache needs to be recreated */
782                 textbuffer_cache_unref(view->cache);
783                 view->cache = textbuffer_cache_get(view->siblings, width);
784         }
785
786         view->width = width > 10 ? width : 10;
787         view->height = height > 1 ? height : 1;
788
789         if (view->buffer->first_line == NULL) {
790                 view->empty_linecount = height;
791                 return;
792         }
793
794         textbuffer_view_init_bottom(view);
795
796         /* check that we didn't scroll lower than bottom startline.. */
797         if (textbuffer_line_exists_after(view->bottom_startline->next,
798                                          view->startline)) {
799                 view->startline = view->bottom_startline;
800                 view->subline = view->bottom_subline;
801         } else if (view->startline == view->bottom_startline &&
802                    view->subline > view->bottom_subline) {
803                 view->subline = view->bottom_subline;
804         } else {
805                 /* make sure the subline is still in allowed range */
806                 linecount = view_get_linecount(view, view->startline);
807                 if (view->subline > linecount)
808                         view->subline = linecount;
809         }
810
811         textbuffer_view_init_ypos(view);
812         if (view->bottom && !view_is_bottom(view)) {
813                 /* we scrolled to far up, need to get down. go right over
814                    the empty lines if there's any */
815                 view->startline = view->bottom_startline;
816                 view->subline = view->bottom_subline;
817                 if (view->empty_linecount > 0) {
818                         view_scroll(view, &view->startline, &view->subline,
819                                     -view->empty_linecount, FALSE);
820                 }
821                 textbuffer_view_init_ypos(view);
822         }
823
824         view->bottom = view_is_bottom(view);
825         if (view->bottom) {
826                 /* check if we left empty space at the bottom.. */
827                 linecount = view_get_linecount_all(view, view->startline) -
828                         view->subline;
829                 if (view->empty_linecount < view->height-linecount)
830                         view->empty_linecount = view->height-linecount;
831                 view->more_text = FALSE;
832         }
833
834         view->dirty = TRUE;
835 }
836
837 /* Clear the view, don't actually remove any lines from buffer. */
838 void textbuffer_view_clear(TEXT_BUFFER_VIEW_REC *view)
839 {
840         g_return_if_fail(view != NULL);
841
842         view->ypos = -1;
843         view->bottom_startline = view->startline =
844                 textbuffer_line_last(view->buffer);
845         view->bottom_subline = view->subline =
846                 view->buffer->cur_line == NULL ? 0 :
847                 view_get_linecount(view, view->buffer->cur_line);
848         view->empty_linecount = view->height;
849         view->bottom = TRUE;
850         view->more_text = FALSE;
851
852         textbuffer_view_redraw(view);
853 }
854
855 /* Scroll the view up/down */
856 void textbuffer_view_scroll(TEXT_BUFFER_VIEW_REC *view, int lines)
857 {
858         int count;
859
860         g_return_if_fail(view != NULL);
861
862         count = view_scroll(view, &view->startline, &view->subline,
863                             lines, TRUE);
864         view->ypos += lines < 0 ? count : -count;
865         view->bottom = view_is_bottom(view);
866         if (view->bottom) view->more_text = FALSE;
867
868         if (view->window != NULL)
869                 term_refresh(view->window);
870 }
871
872 /* Scroll to specified line */
873 void textbuffer_view_scroll_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
874 {
875         g_return_if_fail(view != NULL);
876
877         if (textbuffer_line_exists_after(view->bottom_startline->next, line)) {
878                 view->startline = view->bottom_startline;
879                 view->subline = view->bottom_subline;
880         } else {
881                 view->startline = line;
882                 view->subline = 0;
883         }
884
885         textbuffer_view_init_ypos(view);
886         view->bottom = view_is_bottom(view);
887         if (view->bottom) view->more_text = FALSE;
888
889         textbuffer_view_redraw(view);
890 }
891
892 /* Return line cache */
893 LINE_CACHE_REC *textbuffer_view_get_line_cache(TEXT_BUFFER_VIEW_REC *view,
894                                                LINE_REC *line)
895 {
896         LINE_CACHE_REC *cache;
897
898         g_assert(view != NULL);
899         g_assert(line != NULL);
900
901         cache = g_hash_table_lookup(view->cache->line_cache, line);
902         if (cache == NULL)
903                 cache = view_update_line_cache(view, line);
904         else
905                 cache->last_access = time(NULL);
906
907         return cache;
908 }
909
910 static void view_insert_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
911 {
912         int linecount, ypos, subline;
913
914         if (!view->bottom)
915                 view->more_text = TRUE;
916
917         if (view->bottom_startline == NULL) {
918                 view->startline = view->bottom_startline =
919                         view->buffer->first_line;
920         }
921
922         if (view->buffer->cur_line != line &&
923             !textbuffer_line_exists_after(view->bottom_startline, line))
924                 return;
925
926         linecount = view->cache->last_linecount;
927         view->ypos += linecount;
928         if (view->empty_linecount > 0) {
929                 view->empty_linecount -= linecount;
930                 if (view->empty_linecount >= 0)
931                         linecount = 0;
932                 else {
933                         linecount = -view->empty_linecount;
934                         view->empty_linecount = 0;
935                 }
936         }
937
938         if (linecount > 0) {
939                 view_scroll(view, &view->bottom_startline,
940                             &view->bottom_subline, linecount, FALSE);
941         }
942
943         if (view->bottom) {
944                 if (view->scroll && view->ypos >= view->height) {
945                         linecount = view->ypos-view->height+1;
946                         view_scroll(view, &view->startline,
947                                     &view->subline, linecount, FALSE);
948                         view->ypos -= linecount;
949                 } else {
950                         view->bottom = view_is_bottom(view);
951                 }
952
953                 if (view->window != NULL) {
954                         ypos = view->ypos+1 - view->cache->last_linecount;
955                         if (ypos >= 0)
956                                 subline = 0;
957                         else {
958                                 subline = -ypos;
959                                 ypos = 0;
960                         }
961                         if (ypos < view->height) {
962                                 view_line_draw(view, line, subline, ypos,
963                                                view->height - ypos);
964                         }
965                 }
966         }
967
968         if (view->window != NULL)
969                 term_refresh(view->window);
970 }
971
972 /* Update some line in the buffer which has been modified using
973    textbuffer_append() or textbuffer_insert(). */
974 void textbuffer_view_insert_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
975 {
976         GSList *tmp;
977         unsigned char update_counter;
978
979         g_return_if_fail(view != NULL);
980         g_return_if_fail(line != NULL);
981
982         if (!view->buffer->last_eol)
983                 return;
984
985         update_counter = view->cache->update_counter+1;
986         view_update_cache(view, line, update_counter);
987         view_insert_line(view, line);
988
989         for (tmp = view->siblings; tmp != NULL; tmp = tmp->next) {
990                 TEXT_BUFFER_VIEW_REC *rec = tmp->data;
991
992                 view_update_cache(rec, line, update_counter);
993                 view_insert_line(rec, line);
994         }
995 }
996
997 typedef struct {
998         LINE_REC *remove_line;
999         GSList *remove_list;
1000 } BOOKMARK_FIND_REC;
1001
1002 static void bookmark_check_remove(char *key, LINE_REC *line,
1003                                   BOOKMARK_FIND_REC *rec)
1004 {
1005         if (line == rec->remove_line)
1006                 rec->remove_list = g_slist_append(rec->remove_list, key);
1007 }
1008
1009 static void view_bookmarks_check(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
1010 {
1011         BOOKMARK_FIND_REC rec;
1012         GSList *tmp;
1013
1014         rec.remove_line = line;
1015         rec.remove_list = NULL;
1016         g_hash_table_foreach(view->bookmarks,
1017                              (GHFunc) bookmark_check_remove, &rec);
1018
1019         if (rec.remove_list != NULL) {
1020                 for (tmp = rec.remove_list; tmp != NULL; tmp = tmp->next) {
1021                         g_hash_table_remove(view->bookmarks, tmp->data);
1022                         g_free(tmp->data);
1023                 }
1024                 g_slist_free(rec.remove_list);
1025         }
1026 }
1027
1028 /* Return number of real lines `lines' list takes -
1029    stops counting when the height reaches the view height */
1030 static int view_get_lines_height(TEXT_BUFFER_VIEW_REC *view,
1031                                  LINE_REC *line, int subline,
1032                                  LINE_REC *skip_line)
1033 {
1034         int height, linecount;
1035
1036         height = -subline;
1037         while (line != NULL && height < view->height) {
1038                 if (line != skip_line) {
1039                         linecount = view_get_linecount(view, line);
1040                         height += linecount;
1041                 }
1042                 line = line->next;
1043         }
1044
1045         return height < view->height ? height : view->height;
1046 }
1047
1048 static void view_remove_line_update_startline(TEXT_BUFFER_VIEW_REC *view,
1049                                               LINE_REC *line, int linecount)
1050 {
1051         int scroll;
1052
1053         if (view->startline == line) {
1054                 view->startline = view->startline->prev != NULL ?
1055                         view->startline->prev : view->startline->next;
1056                 view->subline = 0;
1057         } else {
1058                 scroll = view->height -
1059                         view_get_lines_height(view, view->startline,
1060                                               view->subline, line);
1061                 if (scroll > 0) {
1062                         view_scroll(view, &view->startline,
1063                                     &view->subline, -scroll, FALSE);
1064                 }
1065         }
1066
1067         /* FIXME: this is slow and unnecessary, but it's easy and
1068            really works :) */
1069         textbuffer_view_init_ypos(view);
1070         if (textbuffer_line_exists_after(view->startline, line))
1071                 view->ypos -= linecount;
1072 }
1073
1074 static void view_remove_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
1075                              int linecount)
1076 {
1077         int realcount;
1078
1079         view_bookmarks_check(view, line);
1080
1081         if (view->buffer->cur_line == line) {
1082                 /* the last line is being removed */
1083                 LINE_REC *prevline;
1084
1085                 prevline = view->buffer->first_line == line ? NULL :
1086                         textbuffer_line_last(view->buffer);
1087                 view->cache->last_linecount = prevline == NULL ? 0 :
1088                         view_get_linecount(view, prevline);
1089         }
1090
1091         if (view->buffer->first_line == line) {
1092                 /* first line in the buffer - this is the most commonly
1093                    removed line.. */
1094                 if (view->bottom_startline == line) {
1095                         /* very small scrollback.. */
1096                         view->bottom_startline = view->bottom_startline->next;
1097                         view->bottom_subline = 0;
1098                 }
1099
1100                 if (view->startline == line) {
1101                         /* removing the first line in screen */
1102                         int is_last = view->startline->next == NULL;
1103
1104                         realcount = view_scroll(view, &view->startline,
1105                                                 &view->subline,
1106                                                 linecount, FALSE);
1107                         view->ypos -= realcount;
1108                         view->empty_linecount += linecount-realcount;
1109                         if (is_last == 1)
1110                                 view->startline = NULL;
1111                 }
1112         } else {
1113                 if (textbuffer_line_exists_after(view->bottom_startline,
1114                                                  line)) {
1115                         realcount = view_scroll(view, &view->bottom_startline,
1116                                                 &view->bottom_subline,
1117                                                 -linecount, FALSE);
1118                         view->empty_linecount += linecount-realcount;
1119                 }
1120
1121                 if (textbuffer_line_exists_after(view->startline,
1122                                                  line)) {
1123                         view_remove_line_update_startline(view, line,
1124                                                           linecount);
1125                 }
1126         }
1127
1128         view->bottom = view_is_bottom(view);
1129         if (view->bottom) view->more_text = FALSE;
1130         if (view->window != NULL)
1131                 term_refresh(view->window);
1132 }
1133
1134 /* Remove one line from buffer. */
1135 void textbuffer_view_remove_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
1136 {
1137         GSList *tmp;
1138         unsigned char update_counter;
1139         int linecount;
1140
1141         g_return_if_fail(view != NULL);
1142         g_return_if_fail(line != NULL);
1143
1144         linecount = view_get_linecount(view, line);
1145         update_counter = view->cache->update_counter+1;
1146
1147         view_remove_line(view, line, linecount);
1148         view_remove_cache(view, line, update_counter);
1149
1150         for (tmp = view->siblings; tmp != NULL; tmp = tmp->next) {
1151                 TEXT_BUFFER_VIEW_REC *rec = tmp->data;
1152
1153                 view_remove_line(rec, line, linecount);
1154                 view_remove_cache(rec, line, update_counter);
1155         }
1156
1157         textbuffer_remove(view->buffer, line);
1158 }
1159
1160 void textbuffer_view_remove_lines_by_level(TEXT_BUFFER_VIEW_REC *view, int level)
1161 {
1162         LINE_REC *line, *next;
1163         
1164         term_refresh_freeze();
1165         line = textbuffer_view_get_lines(view);
1166
1167         while (line != NULL) {
1168                 next = line->next;
1169
1170                 if (line->info.level & level)
1171                         textbuffer_view_remove_line(view, line);
1172                 line = next;
1173         }
1174         textbuffer_view_redraw(view);
1175         term_refresh_thaw();
1176 }
1177
1178 static int g_free_true(void *data)
1179 {
1180         g_free(data);
1181         return TRUE;
1182 }
1183
1184 /* Remove all lines from buffer. */
1185 void textbuffer_view_remove_all_lines(TEXT_BUFFER_VIEW_REC *view)
1186 {
1187         g_return_if_fail(view != NULL);
1188
1189         textbuffer_remove_all_lines(view->buffer);
1190
1191         g_hash_table_foreach_remove(view->bookmarks,
1192                                     (GHRFunc) g_free_true, NULL);
1193
1194         view_reset_cache(view);
1195         textbuffer_view_clear(view);
1196         g_slist_foreach(view->siblings, (GFunc) textbuffer_view_clear, NULL);
1197 }
1198
1199 /* Set a bookmark in view */
1200 void textbuffer_view_set_bookmark(TEXT_BUFFER_VIEW_REC *view,
1201                                   const char *name, LINE_REC *line)
1202 {
1203         gpointer key, value;
1204
1205         g_return_if_fail(view != NULL);
1206         g_return_if_fail(name != NULL);
1207
1208         if (g_hash_table_lookup_extended(view->bookmarks, name,
1209                                          &key, &value)) {
1210                 g_hash_table_remove(view->bookmarks, key);
1211                 g_free(key);
1212         }
1213
1214         g_hash_table_insert(view->bookmarks, g_strdup(name), line);
1215 }
1216
1217 /* Set a bookmark in view to the bottom line */
1218 void textbuffer_view_set_bookmark_bottom(TEXT_BUFFER_VIEW_REC *view,
1219                                          const char *name)
1220 {
1221         LINE_REC *line;
1222
1223         g_return_if_fail(view != NULL);
1224         g_return_if_fail(name != NULL);
1225
1226         if (view->bottom_startline != NULL) {
1227                 line = textbuffer_line_last(view->buffer);
1228                 textbuffer_view_set_bookmark(view, name, line);
1229         }
1230 }
1231
1232 /* Return the line for bookmark */
1233 LINE_REC *textbuffer_view_get_bookmark(TEXT_BUFFER_VIEW_REC *view,
1234                                        const char *name)
1235 {
1236         g_return_val_if_fail(view != NULL, NULL);
1237         g_return_val_if_fail(name != NULL, NULL);
1238
1239         return g_hash_table_lookup(view->bookmarks, name);
1240 }
1241
1242 /* Specify window where the changes in view should be drawn,
1243    NULL disables it. */
1244 void textbuffer_view_set_window(TEXT_BUFFER_VIEW_REC *view,
1245                                 TERM_WINDOW *window)
1246 {
1247         g_return_if_fail(view != NULL);
1248
1249         if (view->window != window) {
1250                 view->window = window;
1251                 if (window != NULL)
1252                         view->dirty = TRUE;
1253         }
1254 }
1255
1256 /* Redraw a view to window */
1257 void textbuffer_view_redraw(TEXT_BUFFER_VIEW_REC *view)
1258 {
1259         g_return_if_fail(view != NULL);
1260
1261         if (view->window != NULL) {
1262                 view->dirty = FALSE;
1263                 view_draw_top(view, view->height, TRUE);
1264                 term_refresh(view->window);
1265         }
1266 }
1267
1268 static int line_cache_check_remove(void *key, LINE_CACHE_REC *cache,
1269                                    time_t *now)
1270 {
1271         if (cache->last_access+LINE_CACHE_KEEP_TIME > *now)
1272                 return FALSE;
1273
1274         line_cache_destroy(NULL, cache);
1275         return TRUE;
1276 }
1277
1278 static int sig_check_linecache(void)
1279 {
1280         GSList *tmp, *caches;
1281         time_t now;
1282
1283         now = time(NULL); caches = NULL;
1284         for (tmp = views; tmp != NULL; tmp = tmp->next) {
1285                 TEXT_BUFFER_VIEW_REC *rec = tmp->data;
1286
1287                 if (g_slist_find(caches, rec->cache) != NULL)
1288                         continue;
1289
1290                 caches = g_slist_append(caches, rec->cache);
1291                 g_hash_table_foreach_remove(rec->cache->line_cache,
1292                                             (GHRFunc) line_cache_check_remove,
1293                                             &now);
1294         }
1295
1296         g_slist_free(caches);
1297         return 1;
1298 }
1299
1300 void textbuffer_view_init(void)
1301 {
1302         linecache_tag = g_timeout_add(LINE_CACHE_CHECK_TIME, (GSourceFunc) sig_check_linecache, NULL);
1303 }
1304
1305 void textbuffer_view_deinit(void)
1306 {
1307         g_source_remove(linecache_tag);
1308 }