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"
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)
40 static int linecache_tag;
43 #define view_is_bottom(view) \
44 ((view)->ypos >= -1 && (view)->ypos < (view)->height)
46 #define view_get_linecount(view, line) \
47 textbuffer_view_get_line_cache(view, line)->count
49 static GSList *textbuffer_get_views(TEXT_BUFFER_REC *buffer)
53 for (tmp = views; tmp != NULL; tmp = tmp->next) {
54 TEXT_BUFFER_VIEW_REC *view = tmp->data;
56 if (view->buffer == buffer) {
57 list = g_slist_copy(view->siblings);
58 return g_slist_prepend(list, view);
65 static TEXT_BUFFER_CACHE_REC *
66 textbuffer_cache_get(GSList *views, int width)
68 TEXT_BUFFER_CACHE_REC *cache;
70 /* check if there's existing cache with correct width */
71 while (views != NULL) {
72 TEXT_BUFFER_VIEW_REC *view = views->data;
74 if (view->width == width) {
75 view->cache->refcount++;
81 /* create new cache */
82 cache = g_new0(TEXT_BUFFER_CACHE_REC, 1);
85 cache->line_cache = g_hash_table_new((GHashFunc) g_direct_hash,
86 (GCompareFunc) g_direct_equal);
90 static int line_cache_destroy(void *key, LINE_CACHE_REC *cache)
96 static void textbuffer_cache_destroy(TEXT_BUFFER_CACHE_REC *cache)
98 g_hash_table_foreach(cache->line_cache,
99 (GHFunc) line_cache_destroy, NULL);
100 g_hash_table_destroy(cache->line_cache);
104 static void textbuffer_cache_unref(TEXT_BUFFER_CACHE_REC *cache)
106 if (--cache->refcount == 0)
107 textbuffer_cache_destroy(cache);
110 #define FGATTR (ATTR_NOCOLORS | ATTR_RESETFG | ATTR_BOLD | 0x0f)
111 #define BGATTR (ATTR_NOCOLORS | ATTR_RESETBG | ATTR_BLINK | 0xf0)
113 static void update_cmd_color(unsigned char cmd, int *color)
115 if ((cmd & 0x80) == 0) {
116 if (cmd & LINE_COLOR_BG) {
117 /* set background color */
119 if ((cmd & LINE_COLOR_DEFAULT) == 0)
120 *color |= (cmd & 0x0f) << 4;
122 *color = (*color & FGATTR) | ATTR_RESETBG;
123 if (cmd & LINE_COLOR_BLINK)
124 *color |= ATTR_BLINK;
127 /* set foreground color */
129 if ((cmd & LINE_COLOR_DEFAULT) == 0)
130 *color |= cmd & 0x0f;
132 *color = (*color & BGATTR) | ATTR_RESETFG;
133 if (cmd & LINE_COLOR_BOLD)
137 } else switch (cmd) {
138 case LINE_CMD_UNDERLINE:
139 *color ^= ATTR_UNDERLINE;
141 case LINE_CMD_REVERSE:
142 *color ^= ATTR_REVERSE;
144 case LINE_CMD_COLOR0:
150 static LINE_CACHE_REC *
151 view_update_line_cache(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
153 INDENT_FUNC indent_func;
155 LINE_CACHE_SUB_REC *sub;
158 const unsigned char *ptr, *next_ptr, *last_space_ptr;
159 int xpos, pos, indent_pos, last_space, last_color, color, linecount;
163 g_return_val_if_fail(line->text != NULL, NULL);
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;
169 indent_func = view->default_indent_func;
172 for (ptr = line->text;;) {
179 if (cmd == LINE_CMD_EOL || cmd == LINE_CMD_FORMAT)
182 if (cmd == LINE_CMD_CONTINUE) {
185 memcpy(&tmp, ptr, sizeof(char *));
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;
200 update_cmd_color(cmd, &color);
206 if (term_type != TERM_TYPE_BIG5 ||
207 ptr[1] == '\0' || !is_big5(ptr[0], ptr[1]))
211 next_ptr = ptr+char_len;
214 while (ptr[char_len] != '\0' && char_len < 6)
218 if (get_utf8_char(&next_ptr, char_len, &chr) < 0)
221 char_len = utf8_width(chr);
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 */
233 if (xpos + char_len > view->width) {
234 xpos = indent_func == NULL ? indent_pos :
235 indent_func(view, line, -1);
237 sub = g_new0(LINE_CACHE_SUB_REC, 1);
238 if (last_space > indent_pos && last_space > 10) {
239 /* go back to last space */
241 ptr = last_space_ptr;
242 while (*ptr == ' ') ptr++;
243 } else if (view->longword_noindent) {
244 /* long word, no indentation in next line */
246 sub->continues = TRUE;
251 sub->indent_func = indent_func;
254 lines = g_slist_append(lines, sub);
261 if (!view->utf8 && char_len > 1) {
263 last_space_ptr = next_ptr;
265 } else if (*ptr == ' ') {
267 last_space_ptr = ptr;
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;
280 if (rec->count > 1) {
281 for (pos = 0; lines != NULL; pos++) {
282 void *data = lines->data;
284 memcpy(&rec->lines[pos], data,
285 sizeof(LINE_CACHE_SUB_REC));
287 lines = g_slist_remove(lines, data);
292 g_hash_table_insert(view->cache->line_cache, line, rec);
296 static void view_remove_cache(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
297 unsigned char update_counter)
299 LINE_CACHE_REC *cache;
301 if (view->cache->update_counter == update_counter)
303 view->cache->update_counter = update_counter;
305 cache = g_hash_table_lookup(view->cache->line_cache, line);
308 g_hash_table_remove(view->cache->line_cache, line);
312 static void view_update_cache(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
313 unsigned char update_counter)
315 view_remove_cache(view, line, update_counter);
317 if (view->buffer->cur_line == line)
318 view->cache->last_linecount = view_get_linecount(view, line);
321 static void view_reset_cache(TEXT_BUFFER_VIEW_REC *view)
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);
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;
334 rec->cache = textbuffer_cache_get(rec->siblings, rec->width);
338 static int view_line_draw(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
339 int subline, int ypos, int max)
341 INDENT_FUNC indent_func;
342 LINE_CACHE_REC *cache;
343 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 || *text == LINE_CMD_FORMAT)
420 if (*text == LINE_CMD_CONTINUE) {
421 /* jump to next block */
422 memcpy(&tmp, text+1, sizeof(unsigned char *));
425 } else if (*text == LINE_CMD_INDENT_FUNC) {
426 text += sizeof(INDENT_FUNC);
428 update_cmd_color(*text, &color);
429 term_set_color(view->window, color);
438 if (get_utf8_char(&end, 6, &chr)<0)
441 char_width = utf8_width(chr);
443 if (term_type == TERM_TYPE_BIG5 &&
444 is_big5(end[0], end[1]))
452 if (xpos <= term_width) {
454 (end != text || (*text & 127) >= 32)) {
455 for (; text < end; text++)
456 term_addch(view->window, *text);
457 term_addch(view->window, *text);
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);
468 if (need_clrtoeol && xpos < term_width) {
469 term_set_color(view->window, ATTR_RESET);
470 term_clrtoeol(view->window);
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)
481 int linecount, total;
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;
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);
503 if (total >= view->height) {
504 view->bottom_startline = line;
505 view->bottom_subline = total - view->height;
506 view->empty_linecount = 0;
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;
517 static void textbuffer_view_init_ypos(TEXT_BUFFER_VIEW_REC *view)
521 g_return_if_fail(view != NULL);
523 view->ypos = -view->subline-1;
524 for (line = view->startline; line != NULL; line = line->next)
525 view->ypos += view_get_linecount(view, line);
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)
533 TEXT_BUFFER_VIEW_REC *view;
535 g_return_val_if_fail(buffer != NULL, NULL);
536 g_return_val_if_fail(width > 0, NULL);
538 view = g_new0(TEXT_BUFFER_VIEW_REC, 1);
539 view->buffer = buffer;
540 view->siblings = textbuffer_get_views(buffer);
543 view->height = height;
544 view->scroll = scroll;
547 view->cache = textbuffer_cache_get(view->siblings, width);
548 textbuffer_view_init_bottom(view);
550 view->startline = view->bottom_startline;
551 view->subline = view->bottom_subline;
554 textbuffer_view_init_ypos(view);
556 view->bookmarks = g_hash_table_new((GHashFunc) g_str_hash,
557 (GCompareFunc) g_str_equal);
559 views = g_slist_append(views, view);
563 /* Destroy the view. */
564 void textbuffer_view_destroy(TEXT_BUFFER_VIEW_REC *view)
568 g_return_if_fail(view != NULL);
570 views = g_slist_remove(views, view);
572 if (view->siblings == NULL) {
573 /* last view for textbuffer, destroy */
574 textbuffer_destroy(view->buffer);
576 /* remove ourself from siblings lists */
577 for (tmp = view->siblings; tmp != NULL; tmp = tmp->next) {
578 TEXT_BUFFER_VIEW_REC *rec = tmp->data;
580 rec->siblings = g_slist_remove(rec->siblings, view);
582 g_slist_free(view->siblings);
585 g_hash_table_foreach(view->bookmarks, (GHFunc) g_free, NULL);
586 g_hash_table_destroy(view->bookmarks);
588 textbuffer_cache_unref(view->cache);
592 /* Change the default indent position */
593 void textbuffer_view_set_default_indent(TEXT_BUFFER_VIEW_REC *view,
595 int longword_noindent,
596 INDENT_FUNC indent_func)
598 if (default_indent != -1)
599 view->default_indent = default_indent;
600 if (longword_noindent != -1)
601 view->longword_noindent = longword_noindent;
603 view->default_indent_func = indent_func;
606 static void view_unregister_indent_func(TEXT_BUFFER_VIEW_REC *view,
607 INDENT_FUNC indent_func)
611 const unsigned char *text, *tmp;
613 if (view->default_indent_func == indent_func)
614 view->default_indent_func = NULL;
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);
621 /* remove all references to the indent function from buffer */
622 line = view->buffer->first_line;
623 while (line != NULL) {
626 for (text = line->text;; text++) {
631 if (*text == LINE_CMD_EOL)
634 if (*text == LINE_CMD_INDENT_FUNC) {
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 *));
650 void textbuffer_views_unregister_indent_func(INDENT_FUNC indent_func)
652 g_slist_foreach(views, (GFunc) view_unregister_indent_func,
653 (void *) indent_func);
656 void textbuffer_view_set_scroll(TEXT_BUFFER_VIEW_REC *view, int scroll)
658 view->scroll = scroll;
661 void textbuffer_view_set_utf8(TEXT_BUFFER_VIEW_REC *view, int utf8)
666 static int view_get_linecount_all(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
671 while (line != NULL) {
672 linecount += view_get_linecount(view, line);
679 static void view_draw(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
680 int subline, int ypos, int lines, int fill_bottom)
684 if (view->dirty) /* don't bother drawing anything - redraw is coming */
687 while (line != NULL && lines > 0) {
688 linecount = view_line_draw(view, line, subline, ypos, lines);
689 ypos += linecount; lines -= linecount;
696 /* clear the rest of the view */
697 term_set_color(view->window, ATTR_RESET);
699 term_move(view->window, 0, ypos);
700 term_clrtoeol(view->window);
706 #define view_draw_top(view, lines, fill_bottom) \
707 view_draw(view, (view)->startline, (view)->subline, \
708 0, lines, fill_bottom)
710 static void view_draw_bottom(TEXT_BUFFER_VIEW_REC *view, int lines)
713 int ypos, maxline, subline, linecount;
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);
720 if (ypos > maxline) {
721 subline = maxline-(ypos-linecount);
727 view_draw(view, line, subline, maxline, lines, TRUE);
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)
734 int linecount, realcount, scroll_visible;
740 scroll_visible = lines == &view->startline;
742 realcount = -*subline;
743 scrollcount += *subline;
745 while (scrollcount > 0) {
746 linecount = view_get_linecount(view, *lines);
748 if ((scroll_visible && *lines == view->bottom_startline) &&
749 (scrollcount >= view->bottom_subline)) {
750 *subline = view->bottom_subline;
751 realcount += view->bottom_subline;
756 realcount += linecount;
757 scrollcount -= linecount;
758 if (scrollcount < 0) {
759 realcount += scrollcount;
760 *subline = linecount+scrollcount;
765 if ((*lines)->next == NULL)
768 *lines = (*lines)->next;
772 while (scrollcount < 0 && (*lines)->prev != NULL) {
773 *lines = (*lines)->prev;
774 linecount = view_get_linecount(view, *lines);
776 realcount -= linecount;
777 scrollcount += linecount;
778 if (scrollcount > 0) {
779 realcount += scrollcount;
780 *subline = scrollcount;
785 if (scroll_visible && realcount != 0 && view->window != NULL) {
786 if (realcount <= -view->height || realcount >= view->height) {
787 /* scrolled more than screenful, redraw the
789 textbuffer_view_redraw(view);
791 term_set_color(view->window, ATTR_RESET);
792 term_window_scroll(view->window, realcount);
796 view_draw_top(view, -realcount, TRUE);
798 view_draw_bottom(view, realcount);
801 term_refresh(view->window);
805 return realcount >= 0 ? realcount : -realcount;
808 /* Resize the view. */
809 void textbuffer_view_resize(TEXT_BUFFER_VIEW_REC *view, int width, int height)
813 g_return_if_fail(view != NULL);
814 g_return_if_fail(width > 0);
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);
822 view->width = width > 10 ? width : 10;
823 view->height = height > 1 ? height : 1;
825 if (view->buffer->first_line == NULL) {
826 view->empty_linecount = height;
830 textbuffer_view_init_bottom(view);
832 /* check that we didn't scroll lower than bottom startline.. */
833 if (textbuffer_line_exists_after(view->bottom_startline->next,
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;
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;
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);
857 textbuffer_view_init_ypos(view);
860 view->bottom = view_is_bottom(view);
862 /* check if we left empty space at the bottom.. */
863 linecount = view_get_linecount_all(view, view->startline) -
865 if (view->empty_linecount < view->height-linecount)
866 view->empty_linecount = view->height-linecount;
867 view->more_text = FALSE;
873 /* Clear the view, don't actually remove any lines from buffer. */
874 void textbuffer_view_clear(TEXT_BUFFER_VIEW_REC *view)
876 g_return_if_fail(view != NULL);
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;
886 view->more_text = FALSE;
888 textbuffer_view_redraw(view);
891 /* Scroll the view up/down */
892 void textbuffer_view_scroll(TEXT_BUFFER_VIEW_REC *view, int lines)
896 g_return_if_fail(view != NULL);
898 count = view_scroll(view, &view->startline, &view->subline,
900 view->ypos += lines < 0 ? count : -count;
901 view->bottom = view_is_bottom(view);
902 if (view->bottom) view->more_text = FALSE;
904 if (view->window != NULL)
905 term_refresh(view->window);
908 /* Scroll to specified line */
909 void textbuffer_view_scroll_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
911 g_return_if_fail(view != NULL);
913 if (textbuffer_line_exists_after(view->bottom_startline->next, line)) {
914 view->startline = view->bottom_startline;
915 view->subline = view->bottom_subline;
917 view->startline = line;
921 textbuffer_view_init_ypos(view);
922 view->bottom = view_is_bottom(view);
923 if (view->bottom) view->more_text = FALSE;
925 textbuffer_view_redraw(view);
928 /* Return line cache */
929 LINE_CACHE_REC *textbuffer_view_get_line_cache(TEXT_BUFFER_VIEW_REC *view,
932 LINE_CACHE_REC *cache;
934 g_assert(view != NULL);
935 g_assert(line != NULL);
937 cache = g_hash_table_lookup(view->cache->line_cache, line);
939 cache = view_update_line_cache(view, line);
941 cache->last_access = time(NULL);
946 static void view_insert_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
948 int linecount, ypos, subline;
951 view->more_text = TRUE;
953 if (view->bottom_startline == NULL) {
954 view->startline = view->bottom_startline =
955 view->buffer->first_line;
958 if (view->buffer->cur_line != line &&
959 !textbuffer_line_exists_after(view->bottom_startline, line))
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)
969 linecount = -view->empty_linecount;
970 view->empty_linecount = 0;
975 view_scroll(view, &view->bottom_startline,
976 &view->bottom_subline, linecount, FALSE);
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;
986 view->bottom = view_is_bottom(view);
989 if (view->window != NULL) {
990 ypos = view->ypos+1 - view->cache->last_linecount;
997 if (ypos < view->height) {
998 view_line_draw(view, line, subline, ypos,
999 view->height - ypos);
1004 if (view->window != NULL)
1005 term_refresh(view->window);
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)
1013 unsigned char update_counter;
1015 g_return_if_fail(view != NULL);
1016 g_return_if_fail(line != NULL);
1018 if (!view->buffer->last_eol)
1021 update_counter = view->cache->update_counter+1;
1022 view_update_cache(view, line, update_counter);
1023 view_insert_line(view, line);
1025 for (tmp = view->siblings; tmp != NULL; tmp = tmp->next) {
1026 TEXT_BUFFER_VIEW_REC *rec = tmp->data;
1028 view_update_cache(rec, line, update_counter);
1029 view_insert_line(rec, line);
1034 LINE_REC *remove_line;
1035 GSList *remove_list;
1036 } BOOKMARK_FIND_REC;
1038 static void bookmark_check_remove(char *key, LINE_REC *line,
1039 BOOKMARK_FIND_REC *rec)
1041 if (line == rec->remove_line)
1042 rec->remove_list = g_slist_append(rec->remove_list, key);
1045 static void view_bookmarks_check(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
1047 BOOKMARK_FIND_REC rec;
1051 rec.remove_line = line;
1052 rec.remove_list = NULL;
1053 g_hash_table_foreach(view->bookmarks,
1054 (GHFunc) bookmark_check_remove, &rec);
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);
1068 g_slist_free(rec.remove_list);
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)
1078 int height, linecount;
1081 while (line != NULL && height < view->height) {
1082 if (line != skip_line) {
1083 linecount = view_get_linecount(view, line);
1084 height += linecount;
1089 return height < view->height ? height : view->height;
1092 static void view_remove_line_update_startline(TEXT_BUFFER_VIEW_REC *view,
1093 LINE_REC *line, int linecount)
1097 if (view->startline == line) {
1098 view->startline = view->startline->prev != NULL ?
1099 view->startline->prev : view->startline->next;
1102 scroll = view->height -
1103 view_get_lines_height(view, view->startline,
1104 view->subline, line);
1106 view_scroll(view, &view->startline,
1107 &view->subline, -scroll, FALSE);
1111 /* FIXME: this is slow and unnecessary, but it's easy and
1113 textbuffer_view_init_ypos(view);
1114 if (textbuffer_line_exists_after(view->startline, line))
1115 view->ypos -= linecount;
1118 static void view_remove_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
1123 view_bookmarks_check(view, line);
1125 if (view->buffer->cur_line == line) {
1126 /* the last line is being removed */
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);
1135 if (view->buffer->first_line == line) {
1136 /* first line in the buffer - this is the most commonly
1138 if (view->bottom_startline == line) {
1139 /* very small scrollback.. */
1140 view->bottom_startline = view->bottom_startline->next;
1141 view->bottom_subline = 0;
1144 if (view->startline == line) {
1145 /* removing the first line in screen */
1146 int is_last = view->startline->next == NULL;
1148 realcount = view_scroll(view, &view->startline,
1151 view->ypos -= realcount;
1152 view->empty_linecount += linecount-realcount;
1154 view->startline = NULL;
1157 if (textbuffer_line_exists_after(view->bottom_startline,
1159 realcount = view_scroll(view, &view->bottom_startline,
1160 &view->bottom_subline,
1162 view->empty_linecount += linecount-realcount;
1165 if (textbuffer_line_exists_after(view->startline,
1167 view_remove_line_update_startline(view, line,
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);
1178 /* Remove one line from buffer. */
1179 void textbuffer_view_remove_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
1182 unsigned char update_counter;
1185 g_return_if_fail(view != NULL);
1186 g_return_if_fail(line != NULL);
1188 linecount = view_get_linecount(view, line);
1189 update_counter = view->cache->update_counter+1;
1191 view_remove_line(view, line, linecount);
1192 view_remove_cache(view, line, update_counter);
1194 for (tmp = view->siblings; tmp != NULL; tmp = tmp->next) {
1195 TEXT_BUFFER_VIEW_REC *rec = tmp->data;
1197 view_remove_line(rec, line, linecount);
1198 view_remove_cache(rec, line, update_counter);
1201 textbuffer_remove(view->buffer, line);
1204 static int g_free_true(void *data)
1210 /* Remove all lines from buffer. */
1211 void textbuffer_view_remove_all_lines(TEXT_BUFFER_VIEW_REC *view)
1213 g_return_if_fail(view != NULL);
1215 textbuffer_remove_all_lines(view->buffer);
1217 g_hash_table_foreach_remove(view->bookmarks,
1218 (GHRFunc) g_free_true, NULL);
1220 view_reset_cache(view);
1221 textbuffer_view_clear(view);
1222 g_slist_foreach(view->siblings, (GFunc) textbuffer_view_clear, NULL);
1225 /* Set a bookmark in view */
1226 void textbuffer_view_set_bookmark(TEXT_BUFFER_VIEW_REC *view,
1227 const char *name, LINE_REC *line)
1229 gpointer key, value;
1231 g_return_if_fail(view != NULL);
1232 g_return_if_fail(name != NULL);
1234 if (g_hash_table_lookup_extended(view->bookmarks, name,
1236 g_hash_table_remove(view->bookmarks, key);
1240 g_hash_table_insert(view->bookmarks, g_strdup(name), line);
1243 /* Set a bookmark in view to the bottom line */
1244 void textbuffer_view_set_bookmark_bottom(TEXT_BUFFER_VIEW_REC *view,
1249 g_return_if_fail(view != NULL);
1250 g_return_if_fail(name != NULL);
1252 if (view->bottom_startline != NULL) {
1253 line = textbuffer_line_last(view->buffer);
1254 textbuffer_view_set_bookmark(view, name, line);
1258 /* Return the line for bookmark */
1259 LINE_REC *textbuffer_view_get_bookmark(TEXT_BUFFER_VIEW_REC *view,
1262 g_return_val_if_fail(view != NULL, NULL);
1263 g_return_val_if_fail(name != NULL, NULL);
1265 return g_hash_table_lookup(view->bookmarks, name);
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)
1273 g_return_if_fail(view != NULL);
1275 if (view->window != window) {
1276 view->window = window;
1282 /* Redraw a view to window */
1283 void textbuffer_view_redraw(TEXT_BUFFER_VIEW_REC *view)
1285 g_return_if_fail(view != NULL);
1287 if (view->window != NULL) {
1288 view->dirty = FALSE;
1289 view_draw_top(view, view->height, TRUE);
1290 term_refresh(view->window);
1294 static int line_cache_check_remove(void *key, LINE_CACHE_REC *cache,
1297 if (cache->last_access+LINE_CACHE_KEEP_TIME > *now)
1300 line_cache_destroy(NULL, cache);
1304 static int sig_check_linecache(void)
1306 GSList *tmp, *caches;
1309 now = time(NULL); caches = NULL;
1310 for (tmp = views; tmp != NULL; tmp = tmp->next) {
1311 TEXT_BUFFER_VIEW_REC *rec = tmp->data;
1313 if (g_slist_find(caches, rec->cache) != NULL)
1316 caches = g_slist_append(caches, rec->cache);
1317 g_hash_table_foreach_remove(rec->cache->line_cache,
1318 (GHRFunc) line_cache_check_remove,
1322 g_slist_free(caches);
1326 void textbuffer_view_init(void)
1328 linecache_tag = g_timeout_add(LINE_CACHE_CHECK_TIME, (GSourceFunc) sig_check_linecache, NULL);
1334 void textbuffer_view_deinit(void)
1336 g_source_remove(linecache_tag);