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