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