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