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