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 along
17 with this program; if not, write to the Free Software Foundation, Inc.,
18 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 | 0x0f)
108 #define BGATTR (ATTR_NOCOLORS | ATTR_RESETBG | 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;
122 /* set foreground color */
124 if ((cmd & LINE_COLOR_DEFAULT) == 0)
125 *color |= cmd & 0x0f;
127 *color = (*color & BGATTR) | ATTR_RESETFG;
130 } else switch (cmd) {
131 case LINE_CMD_UNDERLINE:
132 *color ^= ATTR_UNDERLINE;
134 case LINE_CMD_REVERSE:
135 *color ^= ATTR_REVERSE;
138 *color ^= ATTR_BLINK;
143 case LINE_CMD_COLOR0:
149 static inline unichar read_unichar(const unsigned char *data, const unsigned char **next, int *width)
151 unichar chr = g_utf8_get_char_validated(data, -1);
153 if (chr & 0x80000000) {
158 *next = g_utf8_next_char(data);
159 *width = unichar_isprint(chr) ? mk_wcwidth(chr) : 1;
164 static LINE_CACHE_REC *
165 view_update_line_cache(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
167 INDENT_FUNC indent_func;
169 LINE_CACHE_SUB_REC *sub;
172 const unsigned char *ptr, *next_ptr, *last_space_ptr;
173 int xpos, pos, indent_pos, last_space, last_color, color, linecount;
176 g_return_val_if_fail(line->text != NULL, NULL);
178 color = ATTR_RESETFG | ATTR_RESETBG;
179 xpos = 0; indent_pos = view->default_indent;
180 last_space = last_color = 0; last_space_ptr = NULL; sub = NULL;
182 indent_func = view->default_indent_func;
185 for (ptr = line->text;;) {
192 if (cmd == LINE_CMD_EOL)
195 if (cmd == LINE_CMD_CONTINUE) {
198 memcpy(&tmp, ptr, sizeof(char *));
203 if (cmd == LINE_CMD_INDENT) {
204 /* set indentation position here - don't do
205 it if we're too close to right border */
206 if (xpos < view->width-5) indent_pos = xpos;
208 update_cmd_color(cmd, &color);
214 if (term_type != TERM_TYPE_BIG5 ||
215 ptr[1] == '\0' || !is_big5(ptr[0], ptr[1]))
219 next_ptr = ptr+char_width;
221 read_unichar(ptr, &next_ptr, &char_width);
224 if (xpos + char_width > view->width && sub != NULL &&
225 (last_space <= indent_pos || last_space <= 10) &&
226 view->longword_noindent) {
227 /* long word, remove the indentation from this line */
232 if (xpos + char_width > view->width) {
233 xpos = indent_func == NULL ? indent_pos :
234 indent_func(view, line, -1);
236 sub = g_new0(LINE_CACHE_SUB_REC, 1);
237 if (last_space > indent_pos && last_space > 10) {
238 /* go back to last space */
240 ptr = last_space_ptr;
241 while (*ptr == ' ') ptr++;
242 } else if (view->longword_noindent) {
243 /* long word, no indentation in next line */
245 sub->continues = TRUE;
250 sub->indent_func = indent_func;
253 lines = g_slist_append(lines, sub);
260 if (!view->utf8 && char_width > 1) {
262 last_space_ptr = next_ptr;
264 } else if (*ptr == ' ') {
266 last_space_ptr = ptr;
274 rec = g_malloc(sizeof(LINE_CACHE_REC)-sizeof(LINE_CACHE_SUB_REC) +
275 sizeof(LINE_CACHE_SUB_REC) * (linecount-1));
276 rec->last_access = time(NULL);
277 rec->count = linecount;
279 if (rec->count > 1) {
280 for (pos = 0; lines != NULL; pos++) {
281 void *data = lines->data;
283 memcpy(&rec->lines[pos], data,
284 sizeof(LINE_CACHE_SUB_REC));
286 lines = g_slist_remove(lines, data);
291 g_hash_table_insert(view->cache->line_cache, line, rec);
295 static void view_remove_cache(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
296 unsigned char update_counter)
298 LINE_CACHE_REC *cache;
300 if (view->cache->update_counter == update_counter)
302 view->cache->update_counter = update_counter;
304 cache = g_hash_table_lookup(view->cache->line_cache, line);
307 g_hash_table_remove(view->cache->line_cache, line);
311 static void view_update_cache(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
312 unsigned char update_counter)
314 view_remove_cache(view, line, update_counter);
316 if (view->buffer->cur_line == line)
317 view->cache->last_linecount = view_get_linecount(view, line);
320 static void view_reset_cache(TEXT_BUFFER_VIEW_REC *view)
324 /* destroy line caches - note that you can't do simultaneously
325 unrefs + cache_get()s or it will keep using the old caches */
326 textbuffer_cache_unref(view->cache);
327 g_slist_foreach(view->siblings, (GFunc) textbuffer_cache_unref, NULL);
329 view->cache = textbuffer_cache_get(view->siblings, view->width);
330 for (tmp = view->siblings; tmp != NULL; tmp = tmp->next) {
331 TEXT_BUFFER_VIEW_REC *rec = tmp->data;
333 rec->cache = textbuffer_cache_get(rec->siblings, rec->width);
337 static int view_line_draw(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
338 int subline, int ypos, int max)
340 INDENT_FUNC indent_func;
341 LINE_CACHE_REC *cache;
342 const unsigned char *text, *end, *text_newline;
345 int xpos, color, drawcount, first, need_move, need_clrtoeol, char_width;
347 if (view->dirty) /* don't bother drawing anything - redraw is coming */
350 cache = textbuffer_view_get_line_cache(view, line);
351 if (subline >= cache->count)
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;
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);
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;
384 if (xpos == 0 && indent_func == NULL)
385 need_clrtoeol = TRUE;
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);
393 if (indent_func != NULL)
394 xpos = indent_func(view, line, ypos);
397 if (need_move || xpos > 0)
398 term_move(view->window, xpos, ypos);
400 term_set_color(view->window, color);
402 if (subline == cache->count-1) {
406 /* get the beginning of the next subline */
407 text_newline = cache->lines[subline].start;
408 need_move = !cache->lines[subline].continues;
417 if (*text == LINE_CMD_EOL)
420 if (*text == LINE_CMD_CONTINUE) {
421 /* jump to next block */
422 memcpy(&tmp, text+1, sizeof(unsigned char *));
426 update_cmd_color(*text, &color);
427 term_set_color(view->window, color);
434 chr = read_unichar(text, &end, &char_width);
438 if (term_type == TERM_TYPE_BIG5 &&
439 is_big5(end[0], end[1]))
447 if (xpos <= term_width) {
448 if (unichar_isprint(chr)) {
450 term_add_unichar(view->window, chr);
452 for (; text < end; text++)
453 term_addch(view->window, *text);
456 term_set_color(view->window, ATTR_RESET|ATTR_REVERSE);
457 term_addch(view->window, (chr & 127)+'A'-1);
458 term_set_color(view->window, color);
464 if (need_clrtoeol && xpos < term_width) {
465 term_set_color(view->window, ATTR_RESET);
466 term_clrtoeol(view->window);
472 /* Recalculate view's bottom line information - try to keep the
473 original if possible */
474 static void textbuffer_view_init_bottom(TEXT_BUFFER_VIEW_REC *view)
477 int linecount, total;
479 if (view->empty_linecount == 0) {
480 /* no empty lines in screen, no need to try to keep
481 the old bottom startline */
482 view->bottom_startline = NULL;
486 line = textbuffer_line_last(view->buffer);
487 for (; line != NULL; line = line->prev) {
488 linecount = view_get_linecount(view, line);
489 if (line == view->bottom_startline) {
490 /* keep the old one, make sure that subline is ok */
491 if (view->bottom_subline > linecount)
492 view->bottom_subline = linecount;
493 view->empty_linecount = view->height - total -
494 (linecount-view->bottom_subline);
499 if (total >= view->height) {
500 view->bottom_startline = line;
501 view->bottom_subline = total - view->height;
502 view->empty_linecount = 0;
507 /* not enough lines so we must be at the beginning of the buffer */
508 view->bottom_startline = view->buffer->first_line;
509 view->bottom_subline = 0;
510 view->empty_linecount = view->height - total;
513 static void textbuffer_view_init_ypos(TEXT_BUFFER_VIEW_REC *view)
517 g_return_if_fail(view != NULL);
519 view->ypos = -view->subline-1;
520 for (line = view->startline; line != NULL; line = line->next)
521 view->ypos += view_get_linecount(view, line);
524 /* Create new view. */
525 TEXT_BUFFER_VIEW_REC *textbuffer_view_create(TEXT_BUFFER_REC *buffer,
526 int width, int height,
527 int scroll, int utf8)
529 TEXT_BUFFER_VIEW_REC *view;
531 g_return_val_if_fail(buffer != NULL, NULL);
532 g_return_val_if_fail(width > 0, NULL);
534 view = g_new0(TEXT_BUFFER_VIEW_REC, 1);
535 view->buffer = buffer;
536 view->siblings = textbuffer_get_views(buffer);
539 view->height = height;
540 view->scroll = scroll;
543 view->cache = textbuffer_cache_get(view->siblings, width);
544 textbuffer_view_init_bottom(view);
546 view->startline = view->bottom_startline;
547 view->subline = view->bottom_subline;
550 textbuffer_view_init_ypos(view);
552 view->bookmarks = g_hash_table_new((GHashFunc) g_str_hash,
553 (GCompareFunc) g_str_equal);
555 views = g_slist_append(views, view);
559 /* Destroy the view. */
560 void textbuffer_view_destroy(TEXT_BUFFER_VIEW_REC *view)
564 g_return_if_fail(view != NULL);
566 views = g_slist_remove(views, view);
568 if (view->siblings == NULL) {
569 /* last view for textbuffer, destroy */
570 textbuffer_destroy(view->buffer);
572 /* remove ourself from siblings lists */
573 for (tmp = view->siblings; tmp != NULL; tmp = tmp->next) {
574 TEXT_BUFFER_VIEW_REC *rec = tmp->data;
576 rec->siblings = g_slist_remove(rec->siblings, view);
578 g_slist_free(view->siblings);
581 g_hash_table_foreach(view->bookmarks, (GHFunc) g_free, NULL);
582 g_hash_table_destroy(view->bookmarks);
584 textbuffer_cache_unref(view->cache);
588 /* Change the default indent position */
589 void textbuffer_view_set_default_indent(TEXT_BUFFER_VIEW_REC *view,
591 int longword_noindent,
592 INDENT_FUNC indent_func)
594 if (default_indent != -1)
595 view->default_indent = default_indent;
596 if (longword_noindent != -1)
597 view->longword_noindent = longword_noindent;
599 view->default_indent_func = indent_func;
602 static void view_unregister_indent_func(TEXT_BUFFER_VIEW_REC *view,
603 INDENT_FUNC indent_func)
605 if (view->default_indent_func == indent_func)
606 view->default_indent_func = NULL;
608 /* recreate cache so it won't contain references
609 to the indent function */
610 view_reset_cache(view);
611 view->cache = textbuffer_cache_get(view->siblings, view->width);
614 void textbuffer_views_unregister_indent_func(INDENT_FUNC indent_func)
616 g_slist_foreach(views, (GFunc) view_unregister_indent_func,
617 (void *) indent_func);
620 void textbuffer_view_set_scroll(TEXT_BUFFER_VIEW_REC *view, int scroll)
622 view->scroll = scroll;
625 void textbuffer_view_set_utf8(TEXT_BUFFER_VIEW_REC *view, int utf8)
630 static int view_get_linecount_all(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
635 while (line != NULL) {
636 linecount += view_get_linecount(view, line);
643 static void view_draw(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
644 int subline, int ypos, int lines, int fill_bottom)
648 if (view->dirty) /* don't bother drawing anything - redraw is coming */
651 while (line != NULL && lines > 0) {
652 linecount = view_line_draw(view, line, subline, ypos, lines);
653 ypos += linecount; lines -= linecount;
660 /* clear the rest of the view */
661 term_set_color(view->window, ATTR_RESET);
663 term_move(view->window, 0, ypos);
664 term_clrtoeol(view->window);
670 #define view_draw_top(view, lines, fill_bottom) \
671 view_draw(view, (view)->startline, (view)->subline, \
672 0, lines, fill_bottom)
674 static void view_draw_bottom(TEXT_BUFFER_VIEW_REC *view, int lines)
677 int ypos, maxline, subline, linecount;
679 maxline = view->height-lines;
680 line = view->startline; ypos = -view->subline; subline = 0;
681 while (line != NULL && ypos < maxline) {
682 linecount = view_get_linecount(view, line);
684 if (ypos > maxline) {
685 subline = maxline-(ypos-linecount);
691 view_draw(view, line, subline, maxline, lines, TRUE);
694 /* Returns number of lines actually scrolled */
695 static int view_scroll(TEXT_BUFFER_VIEW_REC *view, LINE_REC **lines,
696 int *subline, int scrollcount, int draw_nonclean)
698 int linecount, realcount, scroll_visible;
704 scroll_visible = lines == &view->startline;
706 realcount = -*subline;
707 scrollcount += *subline;
709 while (scrollcount > 0) {
710 linecount = view_get_linecount(view, *lines);
712 if ((scroll_visible && *lines == view->bottom_startline) &&
713 (scrollcount >= view->bottom_subline)) {
714 *subline = view->bottom_subline;
715 realcount += view->bottom_subline;
720 realcount += linecount;
721 scrollcount -= linecount;
722 if (scrollcount < 0) {
723 realcount += scrollcount;
724 *subline = linecount+scrollcount;
729 if ((*lines)->next == NULL)
732 *lines = (*lines)->next;
736 while (scrollcount < 0 && (*lines)->prev != NULL) {
737 *lines = (*lines)->prev;
738 linecount = view_get_linecount(view, *lines);
740 realcount -= linecount;
741 scrollcount += linecount;
742 if (scrollcount > 0) {
743 realcount += scrollcount;
744 *subline = scrollcount;
749 if (scroll_visible && realcount != 0 && view->window != NULL) {
750 if (realcount <= -view->height || realcount >= view->height) {
751 /* scrolled more than screenful, redraw the
753 textbuffer_view_redraw(view);
755 term_set_color(view->window, ATTR_RESET);
756 term_window_scroll(view->window, realcount);
760 view_draw_top(view, -realcount, TRUE);
762 view_draw_bottom(view, realcount);
765 term_refresh(view->window);
769 return realcount >= 0 ? realcount : -realcount;
772 /* Resize the view. */
773 void textbuffer_view_resize(TEXT_BUFFER_VIEW_REC *view, int width, int height)
777 g_return_if_fail(view != NULL);
778 g_return_if_fail(width > 0);
780 if (view->width != width) {
781 /* line cache needs to be recreated */
782 textbuffer_cache_unref(view->cache);
783 view->cache = textbuffer_cache_get(view->siblings, width);
786 view->width = width > 10 ? width : 10;
787 view->height = height > 1 ? height : 1;
789 if (view->buffer->first_line == NULL) {
790 view->empty_linecount = height;
794 textbuffer_view_init_bottom(view);
796 /* check that we didn't scroll lower than bottom startline.. */
797 if (textbuffer_line_exists_after(view->bottom_startline->next,
799 view->startline = view->bottom_startline;
800 view->subline = view->bottom_subline;
801 } else if (view->startline == view->bottom_startline &&
802 view->subline > view->bottom_subline) {
803 view->subline = view->bottom_subline;
805 /* make sure the subline is still in allowed range */
806 linecount = view_get_linecount(view, view->startline);
807 if (view->subline > linecount)
808 view->subline = linecount;
811 textbuffer_view_init_ypos(view);
812 if (view->bottom && !view_is_bottom(view)) {
813 /* we scrolled to far up, need to get down. go right over
814 the empty lines if there's any */
815 view->startline = view->bottom_startline;
816 view->subline = view->bottom_subline;
817 if (view->empty_linecount > 0) {
818 view_scroll(view, &view->startline, &view->subline,
819 -view->empty_linecount, FALSE);
821 textbuffer_view_init_ypos(view);
824 view->bottom = view_is_bottom(view);
826 /* check if we left empty space at the bottom.. */
827 linecount = view_get_linecount_all(view, view->startline) -
829 if (view->empty_linecount < view->height-linecount)
830 view->empty_linecount = view->height-linecount;
831 view->more_text = FALSE;
837 /* Clear the view, don't actually remove any lines from buffer. */
838 void textbuffer_view_clear(TEXT_BUFFER_VIEW_REC *view)
840 g_return_if_fail(view != NULL);
843 view->bottom_startline = view->startline =
844 textbuffer_line_last(view->buffer);
845 view->bottom_subline = view->subline =
846 view->buffer->cur_line == NULL ? 0 :
847 view_get_linecount(view, view->buffer->cur_line);
848 view->empty_linecount = view->height;
850 view->more_text = FALSE;
852 textbuffer_view_redraw(view);
855 /* Scroll the view up/down */
856 void textbuffer_view_scroll(TEXT_BUFFER_VIEW_REC *view, int lines)
860 g_return_if_fail(view != NULL);
862 count = view_scroll(view, &view->startline, &view->subline,
864 view->ypos += lines < 0 ? count : -count;
865 view->bottom = view_is_bottom(view);
866 if (view->bottom) view->more_text = FALSE;
868 if (view->window != NULL)
869 term_refresh(view->window);
872 /* Scroll to specified line */
873 void textbuffer_view_scroll_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
875 g_return_if_fail(view != NULL);
877 if (textbuffer_line_exists_after(view->bottom_startline->next, line)) {
878 view->startline = view->bottom_startline;
879 view->subline = view->bottom_subline;
881 view->startline = line;
885 textbuffer_view_init_ypos(view);
886 view->bottom = view_is_bottom(view);
887 if (view->bottom) view->more_text = FALSE;
889 textbuffer_view_redraw(view);
892 /* Return line cache */
893 LINE_CACHE_REC *textbuffer_view_get_line_cache(TEXT_BUFFER_VIEW_REC *view,
896 LINE_CACHE_REC *cache;
898 g_assert(view != NULL);
899 g_assert(line != NULL);
901 cache = g_hash_table_lookup(view->cache->line_cache, line);
903 cache = view_update_line_cache(view, line);
905 cache->last_access = time(NULL);
910 static void view_insert_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
912 int linecount, ypos, subline;
915 view->more_text = TRUE;
917 if (view->bottom_startline == NULL) {
918 view->startline = view->bottom_startline =
919 view->buffer->first_line;
922 if (view->buffer->cur_line != line &&
923 !textbuffer_line_exists_after(view->bottom_startline, line))
926 linecount = view->cache->last_linecount;
927 view->ypos += linecount;
928 if (view->empty_linecount > 0) {
929 view->empty_linecount -= linecount;
930 if (view->empty_linecount >= 0)
933 linecount = -view->empty_linecount;
934 view->empty_linecount = 0;
939 view_scroll(view, &view->bottom_startline,
940 &view->bottom_subline, linecount, FALSE);
944 if (view->scroll && view->ypos >= view->height) {
945 linecount = view->ypos-view->height+1;
946 view_scroll(view, &view->startline,
947 &view->subline, linecount, FALSE);
948 view->ypos -= linecount;
950 view->bottom = view_is_bottom(view);
953 if (view->window != NULL) {
954 ypos = view->ypos+1 - view->cache->last_linecount;
961 if (ypos < view->height) {
962 view_line_draw(view, line, subline, ypos,
963 view->height - ypos);
968 if (view->window != NULL)
969 term_refresh(view->window);
972 /* Update some line in the buffer which has been modified using
973 textbuffer_append() or textbuffer_insert(). */
974 void textbuffer_view_insert_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
977 unsigned char update_counter;
979 g_return_if_fail(view != NULL);
980 g_return_if_fail(line != NULL);
982 if (!view->buffer->last_eol)
985 update_counter = view->cache->update_counter+1;
986 view_update_cache(view, line, update_counter);
987 view_insert_line(view, line);
989 for (tmp = view->siblings; tmp != NULL; tmp = tmp->next) {
990 TEXT_BUFFER_VIEW_REC *rec = tmp->data;
992 view_update_cache(rec, line, update_counter);
993 view_insert_line(rec, line);
998 LINE_REC *remove_line;
1000 } BOOKMARK_FIND_REC;
1002 static void bookmark_check_remove(char *key, LINE_REC *line,
1003 BOOKMARK_FIND_REC *rec)
1005 if (line == rec->remove_line)
1006 rec->remove_list = g_slist_append(rec->remove_list, key);
1009 static void view_bookmarks_check(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
1011 BOOKMARK_FIND_REC rec;
1014 rec.remove_line = line;
1015 rec.remove_list = NULL;
1016 g_hash_table_foreach(view->bookmarks,
1017 (GHFunc) bookmark_check_remove, &rec);
1019 if (rec.remove_list != NULL) {
1020 for (tmp = rec.remove_list; tmp != NULL; tmp = tmp->next) {
1021 g_hash_table_remove(view->bookmarks, tmp->data);
1024 g_slist_free(rec.remove_list);
1028 /* Return number of real lines `lines' list takes -
1029 stops counting when the height reaches the view height */
1030 static int view_get_lines_height(TEXT_BUFFER_VIEW_REC *view,
1031 LINE_REC *line, int subline,
1032 LINE_REC *skip_line)
1034 int height, linecount;
1037 while (line != NULL && height < view->height) {
1038 if (line != skip_line) {
1039 linecount = view_get_linecount(view, line);
1040 height += linecount;
1045 return height < view->height ? height : view->height;
1048 static void view_remove_line_update_startline(TEXT_BUFFER_VIEW_REC *view,
1049 LINE_REC *line, int linecount)
1053 if (view->startline == line) {
1054 view->startline = view->startline->prev != NULL ?
1055 view->startline->prev : view->startline->next;
1058 scroll = view->height -
1059 view_get_lines_height(view, view->startline,
1060 view->subline, line);
1062 view_scroll(view, &view->startline,
1063 &view->subline, -scroll, FALSE);
1067 /* FIXME: this is slow and unnecessary, but it's easy and
1069 textbuffer_view_init_ypos(view);
1070 if (textbuffer_line_exists_after(view->startline, line))
1071 view->ypos -= linecount;
1074 static void view_remove_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
1079 view_bookmarks_check(view, line);
1081 if (view->buffer->cur_line == line) {
1082 /* the last line is being removed */
1085 prevline = view->buffer->first_line == line ? NULL :
1086 textbuffer_line_last(view->buffer);
1087 view->cache->last_linecount = prevline == NULL ? 0 :
1088 view_get_linecount(view, prevline);
1091 if (view->buffer->first_line == line) {
1092 /* first line in the buffer - this is the most commonly
1094 if (view->bottom_startline == line) {
1095 /* very small scrollback.. */
1096 view->bottom_startline = view->bottom_startline->next;
1097 view->bottom_subline = 0;
1100 if (view->startline == line) {
1101 /* removing the first line in screen */
1102 int is_last = view->startline->next == NULL;
1104 realcount = view_scroll(view, &view->startline,
1107 view->ypos -= realcount;
1108 view->empty_linecount += linecount-realcount;
1110 view->startline = NULL;
1113 if (textbuffer_line_exists_after(view->bottom_startline,
1115 realcount = view_scroll(view, &view->bottom_startline,
1116 &view->bottom_subline,
1118 view->empty_linecount += linecount-realcount;
1121 if (textbuffer_line_exists_after(view->startline,
1123 view_remove_line_update_startline(view, line,
1128 view->bottom = view_is_bottom(view);
1129 if (view->bottom) view->more_text = FALSE;
1130 if (view->window != NULL)
1131 term_refresh(view->window);
1134 /* Remove one line from buffer. */
1135 void textbuffer_view_remove_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
1138 unsigned char update_counter;
1141 g_return_if_fail(view != NULL);
1142 g_return_if_fail(line != NULL);
1144 linecount = view_get_linecount(view, line);
1145 update_counter = view->cache->update_counter+1;
1147 view_remove_line(view, line, linecount);
1148 view_remove_cache(view, line, update_counter);
1150 for (tmp = view->siblings; tmp != NULL; tmp = tmp->next) {
1151 TEXT_BUFFER_VIEW_REC *rec = tmp->data;
1153 view_remove_line(rec, line, linecount);
1154 view_remove_cache(rec, line, update_counter);
1157 textbuffer_remove(view->buffer, line);
1160 void textbuffer_view_remove_lines_by_level(TEXT_BUFFER_VIEW_REC *view, int level)
1162 LINE_REC *line, *next;
1164 term_refresh_freeze();
1165 line = textbuffer_view_get_lines(view);
1167 while (line != NULL) {
1170 if (line->info.level & level)
1171 textbuffer_view_remove_line(view, line);
1174 textbuffer_view_redraw(view);
1175 term_refresh_thaw();
1178 static int g_free_true(void *data)
1184 /* Remove all lines from buffer. */
1185 void textbuffer_view_remove_all_lines(TEXT_BUFFER_VIEW_REC *view)
1187 g_return_if_fail(view != NULL);
1189 textbuffer_remove_all_lines(view->buffer);
1191 g_hash_table_foreach_remove(view->bookmarks,
1192 (GHRFunc) g_free_true, NULL);
1194 view_reset_cache(view);
1195 textbuffer_view_clear(view);
1196 g_slist_foreach(view->siblings, (GFunc) textbuffer_view_clear, NULL);
1199 /* Set a bookmark in view */
1200 void textbuffer_view_set_bookmark(TEXT_BUFFER_VIEW_REC *view,
1201 const char *name, LINE_REC *line)
1203 gpointer key, value;
1205 g_return_if_fail(view != NULL);
1206 g_return_if_fail(name != NULL);
1208 if (g_hash_table_lookup_extended(view->bookmarks, name,
1210 g_hash_table_remove(view->bookmarks, key);
1214 g_hash_table_insert(view->bookmarks, g_strdup(name), line);
1217 /* Set a bookmark in view to the bottom line */
1218 void textbuffer_view_set_bookmark_bottom(TEXT_BUFFER_VIEW_REC *view,
1223 g_return_if_fail(view != NULL);
1224 g_return_if_fail(name != NULL);
1226 if (view->bottom_startline != NULL) {
1227 line = textbuffer_line_last(view->buffer);
1228 textbuffer_view_set_bookmark(view, name, line);
1232 /* Return the line for bookmark */
1233 LINE_REC *textbuffer_view_get_bookmark(TEXT_BUFFER_VIEW_REC *view,
1236 g_return_val_if_fail(view != NULL, NULL);
1237 g_return_val_if_fail(name != NULL, NULL);
1239 return g_hash_table_lookup(view->bookmarks, name);
1242 /* Specify window where the changes in view should be drawn,
1243 NULL disables it. */
1244 void textbuffer_view_set_window(TEXT_BUFFER_VIEW_REC *view,
1245 TERM_WINDOW *window)
1247 g_return_if_fail(view != NULL);
1249 if (view->window != window) {
1250 view->window = window;
1256 /* Redraw a view to window */
1257 void textbuffer_view_redraw(TEXT_BUFFER_VIEW_REC *view)
1259 g_return_if_fail(view != NULL);
1261 if (view->window != NULL) {
1262 view->dirty = FALSE;
1263 view_draw_top(view, view->height, TRUE);
1264 term_refresh(view->window);
1268 static int line_cache_check_remove(void *key, LINE_CACHE_REC *cache,
1271 if (cache->last_access+LINE_CACHE_KEEP_TIME > *now)
1274 line_cache_destroy(NULL, cache);
1278 static int sig_check_linecache(void)
1280 GSList *tmp, *caches;
1283 now = time(NULL); caches = NULL;
1284 for (tmp = views; tmp != NULL; tmp = tmp->next) {
1285 TEXT_BUFFER_VIEW_REC *rec = tmp->data;
1287 if (g_slist_find(caches, rec->cache) != NULL)
1290 caches = g_slist_append(caches, rec->cache);
1291 g_hash_table_foreach_remove(rec->cache->line_cache,
1292 (GHRFunc) line_cache_check_remove,
1296 g_slist_free(caches);
1300 void textbuffer_view_init(void)
1302 linecache_tag = g_timeout_add(LINE_CACHE_CHECK_TIME, (GSourceFunc) sig_check_linecache, NULL);
1305 void textbuffer_view_deinit(void)
1307 g_source_remove(linecache_tag);