2 textbuffer-view.c : Text buffer handling
4 Copyright (C) 1999-2001 Timo Sirainen
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.
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.
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
21 #define G_LOG_DOMAIN "TextBufferView"
24 #include "textbuffer-view.h"
32 /* how often to scan line cache for lines not accessed for a while (ms) */
33 #define LINE_CACHE_CHECK_TIME (5*60*1000)
34 /* how long to keep line cache in memory (seconds) */
35 #define LINE_CACHE_KEEP_TIME (10*60)
37 static int linecache_tag;
40 #define view_is_bottom(view) \
41 ((view)->ypos >= -1 && (view)->ypos < (view)->height)
43 #define view_get_linecount(view, line) \
44 textbuffer_view_get_line_cache(view, line)->count
46 static GSList *textbuffer_get_views(TEXT_BUFFER_REC *buffer)
50 for (tmp = views; tmp != NULL; tmp = tmp->next) {
51 TEXT_BUFFER_VIEW_REC *view = tmp->data;
53 if (view->buffer == buffer) {
54 list = g_slist_copy(view->siblings);
55 return g_slist_prepend(list, view);
62 static TEXT_BUFFER_CACHE_REC *
63 textbuffer_cache_get(GSList *views, int width)
65 TEXT_BUFFER_CACHE_REC *cache;
67 /* check if there's existing cache with correct width */
68 while (views != NULL) {
69 TEXT_BUFFER_VIEW_REC *view = views->data;
71 if (view->width == width) {
72 view->cache->refcount++;
78 /* create new cache */
79 cache = g_new0(TEXT_BUFFER_CACHE_REC, 1);
82 cache->line_cache = g_hash_table_new((GHashFunc) g_direct_hash,
83 (GCompareFunc) g_direct_equal);
87 static int line_cache_destroy(void *key, LINE_CACHE_REC *cache)
93 static void textbuffer_cache_destroy(TEXT_BUFFER_CACHE_REC *cache)
95 g_hash_table_foreach(cache->line_cache,
96 (GHFunc) line_cache_destroy, NULL);
97 g_hash_table_destroy(cache->line_cache);
101 static void textbuffer_cache_unref(TEXT_BUFFER_CACHE_REC *cache)
103 if (--cache->refcount == 0)
104 textbuffer_cache_destroy(cache);
107 #define FGATTR (ATTR_NOCOLORS | ATTR_RESETFG | ATTR_BOLD | 0x0f)
108 #define BGATTR (ATTR_NOCOLORS | ATTR_RESETBG | ATTR_BLINK | 0xf0)
110 static void update_cmd_color(unsigned char cmd, int *color)
112 if ((cmd & 0x80) == 0) {
113 if (cmd & LINE_COLOR_BG) {
114 /* set background color */
116 if ((cmd & LINE_COLOR_DEFAULT) == 0)
117 *color |= (cmd & 0x0f) << 4;
119 *color = (*color & FGATTR) | ATTR_RESETBG;
120 if (cmd & LINE_COLOR_BLINK)
121 *color |= ATTR_BLINK;
124 /* set foreground color */
126 if ((cmd & LINE_COLOR_DEFAULT) == 0)
127 *color |= cmd & 0x0f;
129 *color = (*color & BGATTR) | ATTR_RESETFG;
130 if (cmd & LINE_COLOR_BOLD)
134 } else switch (cmd) {
135 case LINE_CMD_UNDERLINE:
136 *color ^= ATTR_UNDERLINE;
138 case LINE_CMD_REVERSE:
139 *color ^= ATTR_REVERSE;
141 case LINE_CMD_COLOR0:
147 static LINE_CACHE_REC *
148 view_update_line_cache(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
150 INDENT_FUNC indent_func;
152 LINE_CACHE_SUB_REC *sub;
155 const unsigned char *ptr, *next_ptr, *last_space_ptr;
156 int xpos, pos, indent_pos, last_space, last_color, color, linecount;
160 g_return_val_if_fail(line->text != NULL, NULL);
162 color = ATTR_RESETFG | ATTR_RESETBG;
163 xpos = 0; indent_pos = view->default_indent;
164 last_space = last_color = 0; last_space_ptr = NULL; sub = NULL;
166 indent_func = view->default_indent_func;
169 for (ptr = line->text;;) {
176 if (cmd == LINE_CMD_EOL || cmd == LINE_CMD_FORMAT)
179 if (cmd == LINE_CMD_CONTINUE) {
182 memcpy(&tmp, ptr, sizeof(char *));
187 if (cmd == LINE_CMD_INDENT) {
188 /* set indentation position here - don't do
189 it if we're too close to right border */
190 if (xpos < view->width-5) indent_pos = xpos;
191 } else if (cmd == LINE_CMD_INDENT_FUNC) {
192 memcpy(&indent_func, ptr, sizeof(INDENT_FUNC));
193 ptr += sizeof(INDENT_FUNC);
194 if (indent_func == NULL)
195 indent_func = view->default_indent_func;
197 update_cmd_color(cmd, &color);
206 while (ptr[char_len] != '\0' && char_len < 6)
210 chr = get_utf8_char(&next_ptr, char_len);
214 char_len = utf8_width(chr);
218 if (xpos + char_len > view->width && sub != NULL &&
219 (last_space <= indent_pos || last_space <= 10) &&
220 view->longword_noindent) {
221 /* long word, remove the indentation from this line */
226 if (xpos + char_len > view->width) {
227 xpos = indent_func == NULL ? indent_pos :
228 indent_func(view, line, -1);
230 sub = g_new0(LINE_CACHE_SUB_REC, 1);
231 if (last_space > indent_pos && last_space > 10) {
232 /* go back to last space */
234 ptr = last_space_ptr;
235 while (*ptr == ' ') ptr++;
236 } else if (view->longword_noindent) {
237 /* long word, no indentation in next line */
239 sub->continues = TRUE;
244 sub->indent_func = indent_func;
247 lines = g_slist_append(lines, sub);
256 last_space_ptr = ptr;
264 rec = g_malloc(sizeof(LINE_CACHE_REC)-sizeof(LINE_CACHE_SUB_REC) +
265 sizeof(LINE_CACHE_SUB_REC) * (linecount-1));
266 rec->last_access = time(NULL);
267 rec->count = linecount;
269 if (rec->count > 1) {
270 for (pos = 0; lines != NULL; pos++) {
271 memcpy(&rec->lines[pos], lines->data,
272 sizeof(LINE_CACHE_SUB_REC));
275 lines = g_slist_remove(lines, lines->data);
279 g_hash_table_insert(view->cache->line_cache, line, rec);
283 static void view_remove_cache(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
284 unsigned char update_counter)
286 LINE_CACHE_REC *cache;
288 if (view->cache->update_counter == update_counter)
290 view->cache->update_counter = update_counter;
292 cache = g_hash_table_lookup(view->cache->line_cache, line);
295 g_hash_table_remove(view->cache->line_cache, line);
299 static void view_update_cache(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
300 unsigned char update_counter)
302 view_remove_cache(view, line, update_counter);
304 if (view->buffer->cur_line == line)
305 view->cache->last_linecount = view_get_linecount(view, line);
308 static void view_reset_cache(TEXT_BUFFER_VIEW_REC *view)
312 /* destroy line caches - note that you can't do simultaneously
313 unrefs + cache_get()s or it will keep using the old caches */
314 textbuffer_cache_unref(view->cache);
315 g_slist_foreach(view->siblings, (GFunc) textbuffer_cache_unref, NULL);
317 view->cache = textbuffer_cache_get(view->siblings, view->width);
318 for (tmp = view->siblings; tmp != NULL; tmp = tmp->next) {
319 TEXT_BUFFER_VIEW_REC *rec = tmp->data;
321 rec->cache = textbuffer_cache_get(rec->siblings, rec->width);
325 static int view_line_draw(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
326 int subline, int ypos, int max)
328 INDENT_FUNC indent_func;
329 LINE_CACHE_REC *cache;
330 const unsigned char *text, *end, *text_newline;
332 int xpos, color, drawcount, first, need_move, need_clrtoeol, char_width;
334 if (view->dirty) /* don't bother drawing anything - redraw is coming */
337 cache = textbuffer_view_get_line_cache(view, line);
338 if (subline >= cache->count)
342 need_move = TRUE; need_clrtoeol = FALSE;
343 xpos = drawcount = 0; first = TRUE;
344 text_newline = text =
345 subline == 0 ? line->text : cache->lines[subline-1].start;
347 if (text == text_newline) {
348 if (need_clrtoeol && xpos < term_width) {
349 term_set_color(view->window, ATTR_RESET);
350 term_clrtoeol(view->window);
362 /* continuing previous line - indent it */
363 indent_func = cache->lines[subline-1].indent_func;
364 if (indent_func == NULL)
365 xpos = cache->lines[subline-1].indent;
366 color = cache->lines[subline-1].color;
371 if (xpos == 0 && indent_func == NULL)
372 need_clrtoeol = TRUE;
374 /* line was indented - need to clear the
375 indented area first */
376 term_set_color(view->window, ATTR_RESET);
377 term_move(view->window, 0, ypos);
378 term_clrtoeol(view->window);
380 if (indent_func != NULL)
381 xpos = indent_func(view, line, ypos);
384 if (need_move || xpos > 0)
385 term_move(view->window, xpos, ypos);
387 term_set_color(view->window, color);
389 if (subline == cache->count-1) {
393 /* get the beginning of the next subline */
394 text_newline = cache->lines[subline].start;
395 need_move = !cache->lines[subline].continues;
404 if (*text == LINE_CMD_EOL || *text == LINE_CMD_FORMAT)
407 if (*text == LINE_CMD_CONTINUE) {
408 /* jump to next block */
409 memcpy(&tmp, text+1, sizeof(unsigned char *));
412 } else if (*text == LINE_CMD_INDENT_FUNC) {
413 text += sizeof(INDENT_FUNC);
415 update_cmd_color(*text, &color);
416 term_set_color(view->window, color);
424 unichar chr = get_utf8_char(&end, 6);
425 char_width = utf8_width(chr);
431 if (xpos <= term_width) {
433 (end != text || (*text & 127) >= 32)) {
434 for (; text < end; text++)
435 term_addch(view->window, *text);
436 term_addch(view->window, *text);
439 term_set_color(view->window, ATTR_RESET|ATTR_REVERSE);
440 term_addch(view->window, (*text & 127)+'A'-1);
441 term_set_color(view->window, color);
447 if (need_clrtoeol && xpos < term_width) {
448 term_set_color(view->window, ATTR_RESET);
449 term_clrtoeol(view->window);
455 /* Recalculate view's bottom line information - try to keep the
456 original if possible */
457 static void textbuffer_view_init_bottom(TEXT_BUFFER_VIEW_REC *view)
460 int linecount, total;
462 if (view->empty_linecount == 0) {
463 /* no empty lines in screen, no need to try to keep
464 the old bottom startline */
465 view->bottom_startline = NULL;
469 line = textbuffer_line_last(view->buffer);
470 for (; line != NULL; line = line->prev) {
471 linecount = view_get_linecount(view, line);
472 if (line == view->bottom_startline) {
473 /* keep the old one, make sure that subline is ok */
474 if (view->bottom_subline > linecount)
475 view->bottom_subline = linecount;
476 view->empty_linecount = view->height - total -
477 (linecount-view->bottom_subline);
482 if (total >= view->height) {
483 view->bottom_startline = line;
484 view->bottom_subline = total - view->height;
485 view->empty_linecount = 0;
490 /* not enough lines so we must be at the beginning of the buffer */
491 view->bottom_startline = view->buffer->first_line;
492 view->bottom_subline = 0;
493 view->empty_linecount = view->height - total;
496 static void textbuffer_view_init_ypos(TEXT_BUFFER_VIEW_REC *view)
500 g_return_if_fail(view != NULL);
502 view->ypos = -view->subline-1;
503 for (line = view->startline; line != NULL; line = line->next)
504 view->ypos += view_get_linecount(view, line);
507 /* Create new view. */
508 TEXT_BUFFER_VIEW_REC *textbuffer_view_create(TEXT_BUFFER_REC *buffer,
509 int width, int height,
510 int scroll, int utf8)
512 TEXT_BUFFER_VIEW_REC *view;
514 g_return_val_if_fail(buffer != NULL, NULL);
515 g_return_val_if_fail(width > 0, NULL);
517 view = g_new0(TEXT_BUFFER_VIEW_REC, 1);
518 view->buffer = buffer;
519 view->siblings = textbuffer_get_views(buffer);
522 view->height = height;
523 view->scroll = scroll;
526 view->cache = textbuffer_cache_get(view->siblings, width);
527 textbuffer_view_init_bottom(view);
529 view->startline = view->bottom_startline;
530 view->subline = view->bottom_subline;
533 textbuffer_view_init_ypos(view);
535 view->bookmarks = g_hash_table_new((GHashFunc) g_str_hash,
536 (GCompareFunc) g_str_equal);
538 views = g_slist_append(views, view);
542 /* Destroy the view. */
543 void textbuffer_view_destroy(TEXT_BUFFER_VIEW_REC *view)
547 g_return_if_fail(view != NULL);
549 views = g_slist_remove(views, view);
551 if (view->siblings == NULL) {
552 /* last view for textbuffer, destroy */
553 textbuffer_destroy(view->buffer);
555 /* remove ourself from siblings lists */
556 for (tmp = view->siblings; tmp != NULL; tmp = tmp->next) {
557 TEXT_BUFFER_VIEW_REC *rec = tmp->data;
559 rec->siblings = g_slist_remove(rec->siblings, view);
561 g_slist_free(view->siblings);
564 g_hash_table_foreach(view->bookmarks, (GHFunc) g_free, NULL);
565 g_hash_table_destroy(view->bookmarks);
567 textbuffer_cache_unref(view->cache);
571 /* Change the default indent position */
572 void textbuffer_view_set_default_indent(TEXT_BUFFER_VIEW_REC *view,
574 int longword_noindent,
575 INDENT_FUNC indent_func)
577 if (default_indent != -1)
578 view->default_indent = default_indent;
579 if (longword_noindent != -1)
580 view->longword_noindent = longword_noindent;
582 view->default_indent_func = indent_func;
585 static void view_unregister_indent_func(TEXT_BUFFER_VIEW_REC *view,
586 INDENT_FUNC indent_func)
590 const unsigned char *text, *tmp;
592 if (view->default_indent_func == indent_func)
593 view->default_indent_func = NULL;
595 /* recreate cache so it won't contain references
596 to the indent function */
597 view_reset_cache(view);
598 view->cache = textbuffer_cache_get(view->siblings, view->width);
600 /* remove all references to the indent function from buffer */
601 line = view->buffer->first_line;
602 while (line != NULL) {
605 for (text = line->text;; text++) {
610 if (*text == LINE_CMD_EOL)
613 if (*text == LINE_CMD_INDENT_FUNC) {
615 memcpy(&func, text, sizeof(INDENT_FUNC));
616 if (func == indent_func)
617 memset(&func, 0, sizeof(INDENT_FUNC));
618 text += sizeof(INDENT_FUNC);
619 } else if (*text == LINE_CMD_CONTINUE) {
620 memcpy(&tmp, text+1, sizeof(char *));
629 void textbuffer_views_unregister_indent_func(INDENT_FUNC indent_func)
631 g_slist_foreach(views, (GFunc) view_unregister_indent_func,
632 (void *) indent_func);
635 void textbuffer_view_set_scroll(TEXT_BUFFER_VIEW_REC *view, int scroll)
637 view->scroll = scroll;
640 void textbuffer_view_set_utf8(TEXT_BUFFER_VIEW_REC *view, int utf8)
645 static int view_get_linecount_all(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
650 while (line != NULL) {
651 linecount += view_get_linecount(view, line);
658 static void view_draw(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
659 int subline, int ypos, int lines, int fill_bottom)
663 if (view->dirty) /* don't bother drawing anything - redraw is coming */
666 while (line != NULL && lines > 0) {
667 linecount = view_line_draw(view, line, subline, ypos, lines);
668 ypos += linecount; lines -= linecount;
675 /* clear the rest of the view */
676 term_set_color(view->window, ATTR_RESET);
678 term_move(view->window, 0, ypos);
679 term_clrtoeol(view->window);
685 #define view_draw_top(view, lines, fill_bottom) \
686 view_draw(view, (view)->startline, (view)->subline, \
687 0, lines, fill_bottom)
689 static void view_draw_bottom(TEXT_BUFFER_VIEW_REC *view, int lines)
692 int ypos, maxline, subline, linecount;
694 maxline = view->height-lines;
695 line = view->startline; ypos = -view->subline; subline = 0;
696 while (line != NULL && ypos < maxline) {
697 linecount = view_get_linecount(view, line);
699 if (ypos > maxline) {
700 subline = maxline-(ypos-linecount);
706 view_draw(view, line, subline, maxline, lines, TRUE);
709 /* Returns number of lines actually scrolled */
710 static int view_scroll(TEXT_BUFFER_VIEW_REC *view, LINE_REC **lines,
711 int *subline, int scrollcount, int draw_nonclean)
713 int linecount, realcount, scroll_visible;
719 scroll_visible = lines == &view->startline;
721 realcount = -*subline;
722 scrollcount += *subline;
724 while (scrollcount > 0) {
725 linecount = view_get_linecount(view, *lines);
727 if ((scroll_visible && *lines == view->bottom_startline) &&
728 (scrollcount >= view->bottom_subline)) {
729 *subline = view->bottom_subline;
730 realcount += view->bottom_subline;
735 realcount += linecount;
736 scrollcount -= linecount;
737 if (scrollcount < 0) {
738 realcount += scrollcount;
739 *subline = linecount+scrollcount;
744 if ((*lines)->next == NULL)
747 *lines = (*lines)->next;
751 while (scrollcount < 0 && (*lines)->prev != NULL) {
752 *lines = (*lines)->prev;
753 linecount = view_get_linecount(view, *lines);
755 realcount -= linecount;
756 scrollcount += linecount;
757 if (scrollcount > 0) {
758 realcount += scrollcount;
759 *subline = scrollcount;
764 if (scroll_visible && realcount != 0 && view->window != NULL) {
765 if (realcount <= -view->height || realcount >= view->height) {
766 /* scrolled more than screenful, redraw the
768 textbuffer_view_redraw(view);
770 term_set_color(view->window, ATTR_RESET);
771 term_window_scroll(view->window, realcount);
775 view_draw_top(view, -realcount, TRUE);
777 view_draw_bottom(view, realcount);
780 term_refresh(view->window);
784 return realcount >= 0 ? realcount : -realcount;
787 /* Resize the view. */
788 void textbuffer_view_resize(TEXT_BUFFER_VIEW_REC *view, int width, int height)
792 g_return_if_fail(view != NULL);
793 g_return_if_fail(width > 0);
795 if (view->width != width) {
796 /* line cache needs to be recreated */
797 textbuffer_cache_unref(view->cache);
798 view->cache = textbuffer_cache_get(view->siblings, width);
801 view->width = width > 10 ? width : 10;
802 view->height = height > 1 ? height : 1;
804 if (view->buffer->first_line == NULL) {
805 view->empty_linecount = height;
809 textbuffer_view_init_bottom(view);
811 /* check that we didn't scroll lower than bottom startline.. */
812 if (textbuffer_line_exists_after(view->bottom_startline->next,
814 view->startline = view->bottom_startline;
815 view->subline = view->bottom_subline;
816 } else if (view->startline == view->bottom_startline &&
817 view->subline > view->bottom_subline) {
818 view->subline = view->bottom_subline;
820 /* make sure the subline is still in allowed range */
821 linecount = view_get_linecount(view, view->startline);
822 if (view->subline > linecount)
823 view->subline = linecount;
826 textbuffer_view_init_ypos(view);
827 if (view->bottom && !view_is_bottom(view)) {
828 /* we scrolled to far up, need to get down. go right over
829 the empty lines if there's any */
830 view->startline = view->bottom_startline;
831 view->subline = view->bottom_subline;
832 if (view->empty_linecount > 0) {
833 view_scroll(view, &view->startline, &view->subline,
834 -view->empty_linecount, FALSE);
836 textbuffer_view_init_ypos(view);
839 view->bottom = view_is_bottom(view);
841 /* check if we left empty space at the bottom.. */
842 linecount = view_get_linecount_all(view, view->startline) -
844 if (view->empty_linecount < view->height-linecount)
845 view->empty_linecount = view->height-linecount;
846 view->more_text = FALSE;
852 /* Clear the view, don't actually remove any lines from buffer. */
853 void textbuffer_view_clear(TEXT_BUFFER_VIEW_REC *view)
855 g_return_if_fail(view != NULL);
858 view->bottom_startline = view->startline =
859 textbuffer_line_last(view->buffer);
860 view->bottom_subline = view->subline =
861 view->buffer->cur_line == NULL ? 0 :
862 view_get_linecount(view, view->buffer->cur_line);
863 view->empty_linecount = view->height;
865 view->more_text = FALSE;
867 textbuffer_view_redraw(view);
870 /* Scroll the view up/down */
871 void textbuffer_view_scroll(TEXT_BUFFER_VIEW_REC *view, int lines)
875 g_return_if_fail(view != NULL);
877 count = view_scroll(view, &view->startline, &view->subline,
879 view->ypos += lines < 0 ? count : -count;
880 view->bottom = view_is_bottom(view);
881 if (view->bottom) view->more_text = FALSE;
883 if (view->window != NULL)
884 term_refresh(view->window);
887 /* Scroll to specified line */
888 void textbuffer_view_scroll_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
890 g_return_if_fail(view != NULL);
892 if (textbuffer_line_exists_after(view->bottom_startline->next, line)) {
893 view->startline = view->bottom_startline;
894 view->subline = view->bottom_subline;
896 view->startline = line;
900 textbuffer_view_init_ypos(view);
901 view->bottom = view_is_bottom(view);
902 if (view->bottom) view->more_text = FALSE;
904 textbuffer_view_redraw(view);
907 /* Return line cache */
908 LINE_CACHE_REC *textbuffer_view_get_line_cache(TEXT_BUFFER_VIEW_REC *view,
911 LINE_CACHE_REC *cache;
913 g_assert(view != NULL);
914 g_assert(line != NULL);
916 cache = g_hash_table_lookup(view->cache->line_cache, line);
918 cache = view_update_line_cache(view, line);
920 cache->last_access = time(NULL);
925 static void view_insert_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
927 int linecount, ypos, subline;
930 view->more_text = TRUE;
932 if (view->bottom_startline == NULL) {
933 view->startline = view->bottom_startline =
934 view->buffer->first_line;
937 if (view->buffer->cur_line != line &&
938 !textbuffer_line_exists_after(view->bottom_startline, line))
941 linecount = view->cache->last_linecount;
942 view->ypos += linecount;
943 if (view->empty_linecount > 0) {
944 view->empty_linecount -= linecount;
945 if (view->empty_linecount >= 0)
948 linecount = -view->empty_linecount;
949 view->empty_linecount = 0;
954 view_scroll(view, &view->bottom_startline,
955 &view->bottom_subline, linecount, FALSE);
959 if (view->scroll && view->ypos >= view->height) {
960 linecount = view->ypos-view->height+1;
961 view_scroll(view, &view->startline,
962 &view->subline, linecount, FALSE);
963 view->ypos -= linecount;
965 view->bottom = view_is_bottom(view);
968 if (view->window != NULL) {
969 ypos = view->ypos+1 - view->cache->last_linecount;
976 if (ypos < view->height) {
977 view_line_draw(view, line, subline, ypos,
978 view->height - ypos);
983 if (view->window != NULL)
984 term_refresh(view->window);
987 /* Update some line in the buffer which has been modified using
988 textbuffer_append() or textbuffer_insert(). */
989 void textbuffer_view_insert_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
992 unsigned char update_counter;
994 g_return_if_fail(view != NULL);
995 g_return_if_fail(line != NULL);
997 if (!view->buffer->last_eol)
1000 update_counter = view->cache->update_counter+1;
1001 view_update_cache(view, line, update_counter);
1002 view_insert_line(view, line);
1004 for (tmp = view->siblings; tmp != NULL; tmp = tmp->next) {
1005 TEXT_BUFFER_VIEW_REC *rec = tmp->data;
1007 view_update_cache(rec, line, update_counter);
1008 view_insert_line(rec, line);
1013 LINE_REC *remove_line;
1014 GSList *remove_list;
1015 } BOOKMARK_FIND_REC;
1017 static void bookmark_check_remove(char *key, LINE_REC *line,
1018 BOOKMARK_FIND_REC *rec)
1020 if (line == rec->remove_line)
1021 rec->remove_list = g_slist_append(rec->remove_list, key);
1024 static void view_bookmarks_check(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
1026 BOOKMARK_FIND_REC rec;
1030 rec.remove_line = line;
1031 rec.remove_list = NULL;
1032 g_hash_table_foreach(view->bookmarks,
1033 (GHFunc) bookmark_check_remove, &rec);
1035 if (rec.remove_list != NULL) {
1036 new_line = line->prev == NULL ? NULL :
1037 (line->next == NULL ? line->prev : line->next);
1038 for (tmp = rec.remove_list; tmp != NULL; tmp = tmp->next) {
1039 g_hash_table_remove(view->bookmarks, tmp->data);
1040 if (new_line != NULL) {
1041 g_hash_table_insert(view->bookmarks,
1042 tmp->data, new_line);
1045 g_slist_free(rec.remove_list);
1049 /* Return number of real lines `lines' list takes -
1050 stops counting when the height reaches the view height */
1051 static int view_get_lines_height(TEXT_BUFFER_VIEW_REC *view,
1052 LINE_REC *line, int subline,
1053 LINE_REC *skip_line)
1055 int height, linecount;
1058 while (line != NULL && height < view->height) {
1059 if (line != skip_line) {
1060 linecount = view_get_linecount(view, line);
1061 height += linecount;
1066 return height < view->height ? height : view->height;
1069 static void view_remove_line_update_startline(TEXT_BUFFER_VIEW_REC *view,
1070 LINE_REC *line, int linecount)
1074 if (view->startline == line) {
1075 view->startline = view->startline->prev != NULL ?
1076 view->startline->prev : view->startline->next;
1079 scroll = view->height -
1080 view_get_lines_height(view, view->startline,
1081 view->subline, line);
1083 view_scroll(view, &view->startline,
1084 &view->subline, -scroll, FALSE);
1088 /* FIXME: this is slow and unnecessary, but it's easy and
1090 textbuffer_view_init_ypos(view);
1091 if (textbuffer_line_exists_after(view->startline, line))
1092 view->ypos -= linecount;
1095 static void view_remove_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
1100 view_bookmarks_check(view, line);
1102 if (view->buffer->cur_line == line) {
1103 /* the last line is being removed */
1106 prevline = view->buffer->first_line == line ? NULL :
1107 textbuffer_line_last(view->buffer);
1108 view->cache->last_linecount = prevline == NULL ? 0 :
1109 view_get_linecount(view, prevline);
1112 if (view->buffer->first_line == line) {
1113 /* first line in the buffer - this is the most commonly
1115 if (view->bottom_startline == line) {
1116 /* very small scrollback.. */
1117 view->bottom_startline = view->bottom_startline->next;
1118 view->bottom_subline = 0;
1121 if (view->startline == line) {
1122 /* removing the first line in screen */
1123 realcount = view_scroll(view, &view->startline,
1126 view->ypos -= realcount;
1127 view->empty_linecount += linecount-realcount;
1130 if (textbuffer_line_exists_after(view->bottom_startline,
1132 realcount = view_scroll(view, &view->bottom_startline,
1133 &view->bottom_subline,
1135 view->empty_linecount += linecount-realcount;
1138 if (textbuffer_line_exists_after(view->startline,
1140 view_remove_line_update_startline(view, line,
1145 view->bottom = view_is_bottom(view);
1146 if (view->bottom) view->more_text = FALSE;
1147 if (view->window != NULL)
1148 term_refresh(view->window);
1151 /* Remove one line from buffer. */
1152 void textbuffer_view_remove_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
1155 unsigned char update_counter;
1158 g_return_if_fail(view != NULL);
1159 g_return_if_fail(line != NULL);
1161 linecount = view_get_linecount(view, line);
1162 update_counter = view->cache->update_counter+1;
1164 view_remove_line(view, line, linecount);
1165 view_remove_cache(view, line, update_counter);
1167 for (tmp = view->siblings; tmp != NULL; tmp = tmp->next) {
1168 TEXT_BUFFER_VIEW_REC *rec = tmp->data;
1170 view_remove_line(rec, line, linecount);
1171 view_remove_cache(rec, line, update_counter);
1174 textbuffer_remove(view->buffer, line);
1177 static int g_free_true(void *data)
1183 /* Remove all lines from buffer. */
1184 void textbuffer_view_remove_all_lines(TEXT_BUFFER_VIEW_REC *view)
1186 g_return_if_fail(view != NULL);
1188 textbuffer_remove_all_lines(view->buffer);
1190 g_hash_table_foreach_remove(view->bookmarks,
1191 (GHRFunc) g_free_true, NULL);
1193 view_reset_cache(view);
1194 textbuffer_view_clear(view);
1195 g_slist_foreach(view->siblings, (GFunc) textbuffer_view_clear, NULL);
1198 /* Set a bookmark in view */
1199 void textbuffer_view_set_bookmark(TEXT_BUFFER_VIEW_REC *view,
1200 const char *name, LINE_REC *line)
1202 gpointer key, value;
1204 g_return_if_fail(view != NULL);
1205 g_return_if_fail(name != NULL);
1207 if (g_hash_table_lookup_extended(view->bookmarks, name,
1209 g_hash_table_remove(view->bookmarks, key);
1213 g_hash_table_insert(view->bookmarks, g_strdup(name), line);
1216 /* Set a bookmark in view to the bottom line */
1217 void textbuffer_view_set_bookmark_bottom(TEXT_BUFFER_VIEW_REC *view,
1222 g_return_if_fail(view != NULL);
1223 g_return_if_fail(name != NULL);
1225 if (view->bottom_startline != NULL) {
1226 line = textbuffer_line_last(view->buffer);
1227 textbuffer_view_set_bookmark(view, name, line);
1231 /* Return the line for bookmark */
1232 LINE_REC *textbuffer_view_get_bookmark(TEXT_BUFFER_VIEW_REC *view,
1235 g_return_val_if_fail(view != NULL, NULL);
1236 g_return_val_if_fail(name != NULL, NULL);
1238 return g_hash_table_lookup(view->bookmarks, name);
1241 /* Specify window where the changes in view should be drawn,
1242 NULL disables it. */
1243 void textbuffer_view_set_window(TEXT_BUFFER_VIEW_REC *view,
1244 TERM_WINDOW *window)
1246 g_return_if_fail(view != NULL);
1248 if (view->window != window) {
1249 view->window = window;
1255 /* Redraw a view to window */
1256 void textbuffer_view_redraw(TEXT_BUFFER_VIEW_REC *view)
1258 g_return_if_fail(view != NULL);
1260 if (view->window != NULL) {
1261 view->dirty = FALSE;
1262 view_draw_top(view, view->height, TRUE);
1263 term_refresh(view->window);
1267 static int line_cache_check_remove(void *key, LINE_CACHE_REC *cache,
1270 if (cache->last_access+LINE_CACHE_KEEP_TIME > *now)
1273 line_cache_destroy(NULL, cache);
1277 static int sig_check_linecache(void)
1279 GSList *tmp, *caches;
1282 now = time(NULL); caches = NULL;
1283 for (tmp = views; tmp != NULL; tmp = tmp->next) {
1284 TEXT_BUFFER_VIEW_REC *rec = tmp->data;
1286 if (g_slist_find(caches, rec->cache) != NULL)
1289 caches = g_slist_append(caches, rec->cache);
1290 g_hash_table_foreach_remove(rec->cache->line_cache,
1291 (GHRFunc) line_cache_check_remove,
1295 g_slist_free(caches);
1299 void textbuffer_view_init(void)
1301 linecache_tag = g_timeout_add(LINE_CACHE_CHECK_TIME, (GSourceFunc) sig_check_linecache, NULL);
1304 void textbuffer_view_deinit(void)
1306 g_source_remove(linecache_tag);