addition of silc.css
[crypto.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->width != width) {
592                 /* line cache needs to be recreated */
593                 textbuffer_cache_unref(view->cache);
594                 view->cache = textbuffer_cache_get(view->siblings, width);
595         }
596
597         view->width = width;
598         view->height = height;
599
600
601         if (view->buffer->lines == NULL) {
602                  view->empty_linecount = height;
603                  return;
604         }
605
606         textbuffer_view_init_bottom(view);
607
608         /* check that we didn't scroll lower than bottom startline.. */
609         if (g_list_find(view->bottom_startline->next,
610                         view->startline->data) != NULL) {
611                 view->startline = view->bottom_startline;
612                 view->subline = view->bottom_subline;
613         } else if (view->startline == view->bottom_startline &&
614                    view->subline > view->bottom_subline) {
615                 view->subline = view->bottom_subline;
616         } else {
617                 /* make sure the subline is still in allowed range */
618                 linecount = view_get_linecount(view, view->startline->data);
619                 if (view->subline > linecount)
620                         view->subline = linecount;
621         }
622
623         textbuffer_view_init_ypos(view);
624         if (view->bottom && !view_is_bottom(view)) {
625                 /* we scrolled to far up, need to get down. go right over
626                    the empty lines if there's any */
627                 view->startline = view->bottom_startline;
628                 view->subline = view->bottom_subline;
629                 if (view->empty_linecount > 0) {
630                         view_scroll(view, &view->startline, &view->subline,
631                                     -view->empty_linecount, FALSE);
632                 }
633                 textbuffer_view_init_ypos(view);
634         }
635
636         view->bottom = view_is_bottom(view);
637         if (view->bottom) {
638                 /* check if we left empty space at the bottom.. */
639                 linecount = view_get_linecount_all(view, view->startline) -
640                         view->subline;
641                 if (view->empty_linecount < view->height-linecount)
642                         view->empty_linecount = view->height-linecount;
643         }
644
645         textbuffer_view_redraw(view);
646 }
647
648 /* Clear the view, don't actually remove any lines from buffer. */
649 void textbuffer_view_clear(TEXT_BUFFER_VIEW_REC *view)
650 {
651         g_return_if_fail(view != NULL);
652
653         view->ypos = -1;
654         view->bottom_startline = view->startline =
655                 g_list_last(view->buffer->lines);
656         view->bottom_subline = view->subline =
657                 view->buffer->cur_line == NULL ? 0 :
658                 view_get_linecount(view, view->buffer->cur_line);
659         view->empty_linecount = view->height;
660         view->bottom = TRUE;
661
662         textbuffer_view_redraw(view);
663 }
664
665 /* Scroll the view up/down */
666 void textbuffer_view_scroll(TEXT_BUFFER_VIEW_REC *view, int lines)
667 {
668         int count;
669
670         g_return_if_fail(view != NULL);
671
672         count = view_scroll(view, &view->startline, &view->subline,
673                             lines, TRUE);
674         view->ypos += lines < 0 ? count : -count;
675         view->bottom = view_is_bottom(view);
676
677         if (view->window != NULL)
678                 screen_refresh(view->window);
679 }
680
681 /* Scroll to specified line */
682 void textbuffer_view_scroll_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
683 {
684         GList *tmp;
685
686         g_return_if_fail(view != NULL);
687
688         if (g_list_find(view->bottom_startline->next, line) != NULL) {
689                 view->startline = view->bottom_startline;
690                 view->subline = view->bottom_subline;
691         } else {
692                 for (tmp = view->buffer->lines; tmp != NULL; tmp = tmp->next) {
693                         LINE_REC *rec = tmp->data;
694
695                         if (rec == line) {
696                                 view->startline = tmp;
697                                 view->subline = 0;
698                                 break;
699                         }
700                 }
701         }
702
703         textbuffer_view_init_ypos(view);
704         view->bottom = view_is_bottom(view);
705
706         textbuffer_view_redraw(view);
707 }
708
709 /* Return line cache */
710 LINE_CACHE_REC *textbuffer_view_get_line_cache(TEXT_BUFFER_VIEW_REC *view,
711                                                LINE_REC *line)
712 {
713         LINE_CACHE_REC *cache;
714
715         g_return_val_if_fail(view != NULL, NULL);
716         g_return_val_if_fail(line != NULL, NULL);
717
718         cache = g_hash_table_lookup(view->cache->line_cache, line);
719         if (cache == NULL)
720                 cache = view_update_line_cache(view, line);
721         else
722                 cache->last_access = time(NULL);
723
724         return cache;
725 }
726
727 static void view_remove_cache(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
728                               unsigned char update_counter)
729 {
730         LINE_CACHE_REC *cache;
731
732         if (view->cache->update_counter == update_counter)
733                 return;
734         view->cache->update_counter = update_counter;
735
736         cache = g_hash_table_lookup(view->cache->line_cache, line);
737         if (cache != NULL) {
738                 g_free(cache);
739                 g_hash_table_remove(view->cache->line_cache, line);
740         }
741 }
742
743 static void view_update_cache(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
744                               unsigned char update_counter)
745 {
746         view_remove_cache(view, line, update_counter);
747
748         if (view->buffer->cur_line == line)
749                 view->cache->last_linecount = view_get_linecount(view, line);
750 }
751
752 static void view_insert_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
753 {
754         int linecount, ypos, subline;
755
756         if (view->bottom_startline == NULL) {
757                 view->startline = view->bottom_startline =
758                         view->buffer->lines;
759         }
760
761         if (view->buffer->cur_line != line &&
762             g_list_find(view->bottom_startline, line) == NULL)
763                 return;
764
765         linecount = view->cache->last_linecount;
766         view->ypos += linecount;
767         if (view->empty_linecount > 0) {
768                 view->empty_linecount -= linecount;
769                 if (view->empty_linecount >= 0)
770                         linecount = 0;
771                 else {
772                         linecount = -view->empty_linecount;
773                         view->empty_linecount = 0;
774                 }
775         }
776
777         if (linecount > 0) {
778                 view_scroll(view, &view->bottom_startline,
779                             &view->bottom_subline, linecount, FALSE);
780         }
781
782         if (view->bottom) {
783                 if (view->ypos >= view->height) {
784                         linecount = view->ypos-view->height+1;
785                         view_scroll(view, &view->startline,
786                                     &view->subline, linecount, FALSE);
787                         view->ypos -= linecount;
788                 }
789
790                 if (view->window != NULL) {
791                         ypos = view->ypos+1 - view->cache->last_linecount;
792                         if (ypos >= 0)
793                                 subline = 0;
794                         else {
795                                 subline = -ypos;
796                                 ypos = 0;
797                         }
798                         view_line_draw(view, line, subline, ypos,
799                                        view->height - ypos);
800                 }
801         }
802
803         if (view->window != NULL)
804                 screen_refresh(view->window);
805 }
806
807 /* Update some line in the buffer which has been modified using
808    textbuffer_append() or textbuffer_insert(). */
809 void textbuffer_view_insert_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
810 {
811         GSList *tmp;
812         unsigned char update_counter;
813
814         g_return_if_fail(view != NULL);
815         g_return_if_fail(line != NULL);
816
817         if (!view->buffer->last_eol)
818                 return;
819
820         update_counter = view->cache->update_counter+1;
821         view_update_cache(view, line, update_counter);
822         view_insert_line(view, line);
823
824         for (tmp = view->siblings; tmp != NULL; tmp = tmp->next) {
825                 TEXT_BUFFER_VIEW_REC *rec = tmp->data;
826
827                 view_update_cache(rec, line, update_counter);
828                 view_insert_line(rec, line);
829         }
830 }
831
832 typedef struct {
833         LINE_REC *remove_line;
834         GSList *remove_list;
835 } BOOKMARK_FIND_REC;
836
837 static void bookmark_check_remove(char *key, LINE_REC *line,
838                                   BOOKMARK_FIND_REC *rec)
839 {
840         if (line == rec->remove_line)
841                 rec->remove_list = g_slist_append(rec->remove_list, key);
842 }
843
844 static void view_bookmarks_check(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
845 {
846         BOOKMARK_FIND_REC rec;
847         LINE_REC *new_line;
848         GSList *tmp;
849
850         rec.remove_line = line;
851         rec.remove_list = NULL;
852         g_hash_table_foreach(view->bookmarks,
853                              (GHFunc) bookmark_check_remove, &rec);
854
855         if (rec.remove_list != NULL) {
856                 GList *pos = g_list_find(view->buffer->lines, line);
857
858                 new_line = pos == NULL || pos->prev == NULL ? NULL :
859                         (pos->next == NULL ? pos->prev->data :
860                          pos->next->data);
861                 for (tmp = rec.remove_list; tmp != NULL; tmp = tmp->next) {
862                         g_hash_table_remove(view->bookmarks, tmp->data);
863                         if (new_line != NULL) {
864                                 g_hash_table_insert(view->bookmarks,
865                                                     tmp->data, new_line);
866                         }
867                 }
868                 g_slist_free(rec.remove_list);
869         }
870 }
871
872 /* Return number of real lines `lines' list takes -
873    stops counting when the height reaches the view height */
874 static int view_get_lines_height(TEXT_BUFFER_VIEW_REC *view,
875                                  GList *lines, int subline,
876                                  LINE_REC *skip_line)
877 {
878         int height, linecount;
879
880         height = -subline;
881         while (lines != NULL && height < view->height) {
882                 LINE_REC *line = lines->data;
883
884                 if (line != skip_line) {
885                         linecount = view_get_linecount(view, line);
886                         height += linecount;
887                 }
888                 lines = lines->next;
889         }
890
891         return height < view->height ? height : view->height;
892 }
893
894 static void view_remove_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
895                              int linecount)
896 {
897         int realcount, scroll;
898
899         view_bookmarks_check(view, line);
900
901         if (view->buffer->cur_line == line) {
902                 /* the last line is being removed */
903                 LINE_REC *prevline;
904
905                 prevline = view->buffer->lines->data == line ? NULL :
906                         g_list_last(view->bottom_startline)->data;
907                 view->cache->last_linecount = prevline == NULL ? 0 :
908                         view_get_linecount(view, prevline);
909         }
910
911         if (line == view->buffer->lines->data) {
912                 /* first line in the buffer - this is the most commonly
913                    removed line.. */
914                 if (view->bottom_startline->data == line) {
915                         /* very small scrollback.. */
916                         view->bottom_startline = view->bottom_startline->next;
917                         view->bottom_subline = 0;
918                 }
919
920                 if (view->startline->data == line) {
921                         /* removing the first line in screen */
922                         realcount = view_scroll(view, &view->startline,
923                                                 &view->subline,
924                                                 linecount, TRUE);
925                         view->ypos -= realcount;
926                         view->empty_linecount += linecount-realcount;
927                 }
928         } else if (g_list_find(view->bottom_startline, line) != NULL) {
929                 realcount = view_scroll(view, &view->bottom_startline,
930                                         &view->bottom_subline,
931                                         -linecount, FALSE);
932                 if (view->bottom) {
933                         /* we're at the bottom, remove the same amount as
934                            from bottom_startline */
935                         view_scroll(view, &view->startline,
936                                     &view->subline, -linecount, TRUE);
937                         view->ypos -= linecount-realcount;
938                 } else {
939                         if (view->startline->data == line) {
940                                 view->startline =
941                                         view->startline->next != NULL ?
942                                         view->startline->next :
943                                         view->startline->prev;
944                                 view->subline = 0;
945                         }
946                         scroll = view->height -
947                                 view_get_lines_height(view, view->startline,
948                                                       view->subline, line);
949                         if (scroll > 0) {
950                                 view_scroll(view, &view->startline,
951                                             &view->subline, -scroll, TRUE);
952                                 view->ypos -= scroll;
953                         }
954                 }
955                 view->empty_linecount += linecount-realcount;
956         }
957
958         view->bottom = view_is_bottom(view);
959         if (view->window != NULL)
960                 screen_refresh(view->window);
961 }
962
963 /* Remove one line from buffer. */
964 void textbuffer_view_remove_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
965 {
966         GSList *tmp;
967         unsigned char update_counter;
968         int linecount;
969
970         g_return_if_fail(view != NULL);
971         g_return_if_fail(line != NULL);
972
973         linecount = view_get_linecount(view, line);
974         update_counter = view->cache->update_counter+1;
975
976         view_remove_line(view, line, linecount);
977         view_remove_cache(view, line, update_counter);
978
979         for (tmp = view->siblings; tmp != NULL; tmp = tmp->next) {
980                 TEXT_BUFFER_VIEW_REC *rec = tmp->data;
981
982                 view_remove_line(rec, line, linecount);
983                 view_remove_cache(rec, line, update_counter);
984         }
985
986         textbuffer_remove(view->buffer, line);
987 }
988
989 /* Remove all lines from buffer. */
990 void textbuffer_view_remove_all_lines(TEXT_BUFFER_VIEW_REC *view)
991 {
992         GSList *tmp;
993
994         g_return_if_fail(view != NULL);
995
996         textbuffer_remove_all_lines(view->buffer);
997
998         /* destroy line caches - note that you can't do simultaneously
999            unrefs + cache_get()s or it will keep using the old caches */
1000         textbuffer_cache_unref(view->cache);
1001         g_slist_foreach(view->siblings, (GFunc) textbuffer_cache_unref, NULL);
1002
1003         /* recreate caches, clear screens */
1004         view->cache = textbuffer_cache_get(view->siblings, view->width);
1005         textbuffer_view_clear(view);
1006
1007         for (tmp = view->siblings; tmp != NULL; tmp = tmp->next) {
1008                 TEXT_BUFFER_VIEW_REC *rec = tmp->data;
1009
1010                 rec->cache = textbuffer_cache_get(rec->siblings, rec->width);
1011                 textbuffer_view_clear(rec);
1012         }
1013 }
1014
1015 /* Set a bookmark in view */
1016 void textbuffer_view_set_bookmark(TEXT_BUFFER_VIEW_REC *view,
1017                                   const char *name, LINE_REC *line)
1018 {
1019         gpointer key, value;
1020
1021         g_return_if_fail(view != NULL);
1022         g_return_if_fail(name != NULL);
1023
1024         if (g_hash_table_lookup_extended(view->bookmarks, name,
1025                                          &key, &value)) {
1026                 g_hash_table_remove(view->bookmarks, key);
1027                 g_free(key);
1028         }
1029
1030         g_hash_table_insert(view->bookmarks, g_strdup(name), line);
1031 }
1032
1033 /* Set a bookmark in view to the bottom line */
1034 void textbuffer_view_set_bookmark_bottom(TEXT_BUFFER_VIEW_REC *view,
1035                                          const char *name)
1036 {
1037         LINE_REC *line;
1038
1039         g_return_if_fail(view != NULL);
1040         g_return_if_fail(name != NULL);
1041
1042         if (view->bottom_startline != NULL) {
1043                 line = g_list_last(view->bottom_startline)->data;
1044                 textbuffer_view_set_bookmark(view, name, line);
1045         }
1046 }
1047
1048 /* Return the line for bookmark */
1049 LINE_REC *textbuffer_view_get_bookmark(TEXT_BUFFER_VIEW_REC *view,
1050                                        const char *name)
1051 {
1052         g_return_val_if_fail(view != NULL, NULL);
1053         g_return_val_if_fail(name != NULL, NULL);
1054
1055         return g_hash_table_lookup(view->bookmarks, name);
1056 }
1057
1058 /* Specify window where the changes in view should be drawn,
1059    NULL disables it. */
1060 void textbuffer_view_set_window(TEXT_BUFFER_VIEW_REC *view, WINDOW *window)
1061 {
1062         g_return_if_fail(view != NULL);
1063
1064         if (view->window != window) {
1065                 view->window = window;
1066                 if (window != NULL)
1067                         textbuffer_view_redraw(view);
1068         }
1069 }
1070
1071 /* Redraw a view to window */
1072 void textbuffer_view_redraw(TEXT_BUFFER_VIEW_REC *view)
1073 {
1074         g_return_if_fail(view != NULL);
1075
1076         if (view->window != NULL) {
1077                 werase(view->window);
1078                 view_draw_top(view, view->height);
1079                 screen_refresh(view->window);
1080         }
1081 }
1082
1083 static int line_cache_check_remove(void *key, LINE_CACHE_REC *cache,
1084                                    time_t *now)
1085 {
1086         if (cache->last_access+LINE_CACHE_KEEP_TIME > *now)
1087                 return FALSE;
1088
1089         line_cache_destroy(NULL, cache);
1090         return TRUE;
1091 }
1092
1093 static int sig_check_linecache(void)
1094 {
1095         GSList *tmp, *caches;
1096         time_t now;
1097
1098         now = time(NULL); caches = NULL;
1099         for (tmp = views; tmp != NULL; tmp = tmp->next) {
1100                 TEXT_BUFFER_VIEW_REC *rec = tmp->data;
1101
1102                 if (g_slist_find(caches, rec->cache) != NULL)
1103                         continue;
1104
1105                 caches = g_slist_append(caches, rec->cache);
1106                 g_hash_table_foreach_remove(rec->cache->line_cache,
1107                                             (GHRFunc) line_cache_check_remove,
1108                                             &now);
1109         }
1110         return 1;
1111 }
1112
1113 void textbuffer_view_init(void)
1114 {
1115         linecache_tag = g_timeout_add(LINE_CACHE_CHECK_TIME, (GSourceFunc) sig_check_linecache, NULL);
1116 }
1117
1118 void textbuffer_view_deinit(void)
1119 {
1120         g_source_remove(linecache_tag);
1121 }