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