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, *last_space_ptr;
156 int xpos, pos, indent_pos, last_space, last_color, color, linecount;
158 g_return_val_if_fail(line->text != NULL, NULL);
160 color = ATTR_RESETFG | ATTR_RESETBG;
161 xpos = 0; indent_pos = view->default_indent;
162 last_space = last_color = 0; last_space_ptr = NULL; sub = NULL;
164 indent_func = view->default_indent_func;
167 for (ptr = line->text;;) {
174 if (cmd == LINE_CMD_EOL || cmd == LINE_CMD_FORMAT)
177 if (cmd == LINE_CMD_CONTINUE) {
180 memcpy(&tmp, ptr, sizeof(char *));
185 if (cmd == LINE_CMD_INDENT) {
186 /* set indentation position here - don't do
187 it if we're too close to right border */
188 if (xpos < view->width-5) indent_pos = xpos;
189 } else if (cmd == LINE_CMD_INDENT_FUNC) {
190 memcpy(&indent_func, ptr, sizeof(INDENT_FUNC));
191 ptr += sizeof(INDENT_FUNC);
192 if (indent_func == NULL)
193 indent_func = view->default_indent_func;
195 update_cmd_color(cmd, &color);
199 if (xpos == view->width && sub != NULL &&
200 (last_space <= indent_pos || last_space <= 10) &&
201 view->longword_noindent) {
202 /* long word, remove the indentation from this line */
207 if (xpos == view->width) {
208 xpos = indent_func == NULL ? indent_pos :
209 indent_func(view, line, -1);
211 sub = g_new0(LINE_CACHE_SUB_REC, 1);
212 if (last_space > indent_pos && last_space > 10) {
213 /* go back to last space */
215 ptr = last_space_ptr;
216 while (*ptr == ' ') ptr++;
217 } else if (view->longword_noindent) {
218 /* long word, no indentation in next line */
220 sub->continues = TRUE;
225 sub->indent_func = indent_func;
228 lines = g_slist_append(lines, sub);
236 get_utf8_char(&ptr, 6);
241 last_space_ptr = ptr;
246 rec = g_malloc(sizeof(LINE_CACHE_REC)-sizeof(LINE_CACHE_SUB_REC) +
247 sizeof(LINE_CACHE_SUB_REC) * (linecount-1));
248 rec->last_access = time(NULL);
249 rec->count = linecount;
251 if (rec->count > 1) {
252 for (pos = 0; lines != NULL; pos++) {
253 memcpy(&rec->lines[pos], lines->data,
254 sizeof(LINE_CACHE_SUB_REC));
257 lines = g_slist_remove(lines, lines->data);
261 g_hash_table_insert(view->cache->line_cache, line, rec);
265 static void view_remove_cache(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
266 unsigned char update_counter)
268 LINE_CACHE_REC *cache;
270 if (view->cache->update_counter == update_counter)
272 view->cache->update_counter = update_counter;
274 cache = g_hash_table_lookup(view->cache->line_cache, line);
277 g_hash_table_remove(view->cache->line_cache, line);
281 static void view_update_cache(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
282 unsigned char update_counter)
284 view_remove_cache(view, line, update_counter);
286 if (view->buffer->cur_line == line)
287 view->cache->last_linecount = view_get_linecount(view, line);
290 static void view_reset_cache(TEXT_BUFFER_VIEW_REC *view)
294 /* destroy line caches - note that you can't do simultaneously
295 unrefs + cache_get()s or it will keep using the old caches */
296 textbuffer_cache_unref(view->cache);
297 g_slist_foreach(view->siblings, (GFunc) textbuffer_cache_unref, NULL);
299 view->cache = textbuffer_cache_get(view->siblings, view->width);
300 for (tmp = view->siblings; tmp != NULL; tmp = tmp->next) {
301 TEXT_BUFFER_VIEW_REC *rec = tmp->data;
303 rec->cache = textbuffer_cache_get(rec->siblings, rec->width);
307 static int view_line_draw(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
308 int subline, int ypos, int max)
310 INDENT_FUNC indent_func;
311 LINE_CACHE_REC *cache;
312 const unsigned char *text, *text_newline;
314 int xpos, color, drawcount, first, need_move, need_clrtoeol;
316 if (view->dirty) /* don't bother drawing anything - redraw is coming */
319 cache = textbuffer_view_get_line_cache(view, line);
320 if (subline >= cache->count)
324 need_move = TRUE; need_clrtoeol = FALSE;
325 xpos = drawcount = 0; first = TRUE;
326 text_newline = text =
327 subline == 0 ? line->text : cache->lines[subline-1].start;
329 if (text == text_newline) {
330 if (need_clrtoeol && xpos < term_width) {
331 term_set_color(view->window, ATTR_RESET);
332 term_clrtoeol(view->window);
344 /* continuing previous line - indent it */
345 indent_func = cache->lines[subline-1].indent_func;
346 xpos = indent_func != NULL ?
347 indent_func(view, line, ypos) :
348 cache->lines[subline-1].indent;
349 color = cache->lines[subline-1].color;
353 need_clrtoeol = TRUE;
355 /* line was indented - need to clear the
356 indented area first */
357 term_set_color(view->window, ATTR_RESET);
358 term_move(view->window, 0, ypos);
359 term_clrtoeol(view->window);
362 if (need_move || xpos > 0)
363 term_move(view->window, xpos, ypos);
365 term_set_color(view->window, color);
367 if (subline == cache->count-1) {
371 /* get the beginning of the next subline */
372 text_newline = cache->lines[subline].start;
373 need_move = !cache->lines[subline].continues;
382 if (*text == LINE_CMD_EOL || *text == LINE_CMD_FORMAT)
385 if (*text == LINE_CMD_CONTINUE) {
386 /* jump to next block */
387 memcpy(&tmp, text+1, sizeof(unsigned char *));
390 } else if (*text == LINE_CMD_INDENT_FUNC) {
391 text += sizeof(INDENT_FUNC);
393 update_cmd_color(*text, &color);
394 term_set_color(view->window, color);
400 if (xpos < term_width) {
401 const unsigned char *end = text;
403 get_utf8_char(&end, 6);
406 (end != text || (*text & 127) >= 32)) {
407 for (; text < end; text++)
408 term_addch(view->window, *text);
409 term_addch(view->window, *text);
412 term_set_color(view->window, ATTR_RESET|ATTR_REVERSE);
413 term_addch(view->window, (*text & 127)+'A'-1);
414 term_set_color(view->window, color);
421 if (need_clrtoeol && xpos < term_width) {
422 term_set_color(view->window, ATTR_RESET);
423 term_clrtoeol(view->window);
429 /* Recalculate view's bottom line information - try to keep the
430 original if possible */
431 static void textbuffer_view_init_bottom(TEXT_BUFFER_VIEW_REC *view)
434 int linecount, total;
436 if (view->empty_linecount == 0) {
437 /* no empty lines in screen, no need to try to keep
438 the old bottom startline */
439 view->bottom_startline = NULL;
443 line = textbuffer_line_last(view->buffer);
444 for (; line != NULL; line = line->prev) {
445 linecount = view_get_linecount(view, line);
446 if (line == view->bottom_startline) {
447 /* keep the old one, make sure that subline is ok */
448 if (view->bottom_subline > linecount)
449 view->bottom_subline = linecount;
450 view->empty_linecount = view->height - total -
451 (linecount-view->bottom_subline);
456 if (total >= view->height) {
457 view->bottom_startline = line;
458 view->bottom_subline = total - view->height;
459 view->empty_linecount = 0;
464 /* not enough lines so we must be at the beginning of the buffer */
465 view->bottom_startline = view->buffer->first_line;
466 view->bottom_subline = 0;
467 view->empty_linecount = view->height - total;
470 static void textbuffer_view_init_ypos(TEXT_BUFFER_VIEW_REC *view)
474 g_return_if_fail(view != NULL);
476 view->ypos = -view->subline-1;
477 for (line = view->startline; line != NULL; line = line->next)
478 view->ypos += view_get_linecount(view, line);
481 /* Create new view. */
482 TEXT_BUFFER_VIEW_REC *textbuffer_view_create(TEXT_BUFFER_REC *buffer,
483 int width, int height,
484 int scroll, int utf8)
486 TEXT_BUFFER_VIEW_REC *view;
488 g_return_val_if_fail(buffer != NULL, NULL);
489 g_return_val_if_fail(width > 0, NULL);
491 view = g_new0(TEXT_BUFFER_VIEW_REC, 1);
492 view->buffer = buffer;
493 view->siblings = textbuffer_get_views(buffer);
496 view->height = height;
497 view->scroll = scroll;
500 view->cache = textbuffer_cache_get(view->siblings, width);
501 textbuffer_view_init_bottom(view);
503 view->startline = view->bottom_startline;
504 view->subline = view->bottom_subline;
507 textbuffer_view_init_ypos(view);
509 view->bookmarks = g_hash_table_new((GHashFunc) g_str_hash,
510 (GCompareFunc) g_str_equal);
512 views = g_slist_append(views, view);
516 /* Destroy the view. */
517 void textbuffer_view_destroy(TEXT_BUFFER_VIEW_REC *view)
521 g_return_if_fail(view != NULL);
523 views = g_slist_remove(views, view);
525 if (view->siblings == NULL) {
526 /* last view for textbuffer, destroy */
527 textbuffer_destroy(view->buffer);
529 /* remove ourself from siblings lists */
530 for (tmp = view->siblings; tmp != NULL; tmp = tmp->next) {
531 TEXT_BUFFER_VIEW_REC *rec = tmp->data;
533 rec->siblings = g_slist_remove(rec->siblings, view);
535 g_slist_free(view->siblings);
538 g_hash_table_foreach(view->bookmarks, (GHFunc) g_free, NULL);
539 g_hash_table_destroy(view->bookmarks);
541 textbuffer_cache_unref(view->cache);
545 /* Change the default indent position */
546 void textbuffer_view_set_default_indent(TEXT_BUFFER_VIEW_REC *view,
548 int longword_noindent,
549 INDENT_FUNC indent_func)
551 if (default_indent != -1)
552 view->default_indent = default_indent;
553 if (longword_noindent != -1)
554 view->longword_noindent = longword_noindent;
556 view->default_indent_func = indent_func;
559 static void view_unregister_indent_func(TEXT_BUFFER_VIEW_REC *view,
560 INDENT_FUNC indent_func)
564 const unsigned char *text, *tmp;
566 if (view->default_indent_func == indent_func)
567 view->default_indent_func = NULL;
569 /* recreate cache so it won't contain references
570 to the indent function */
571 view_reset_cache(view);
572 view->cache = textbuffer_cache_get(view->siblings, view->width);
574 /* remove all references to the indent function from buffer */
575 line = view->buffer->first_line;
576 while (line != NULL) {
579 for (text = line->text;; text++) {
584 if (*text == LINE_CMD_EOL)
587 if (*text == LINE_CMD_INDENT_FUNC) {
589 memcpy(&func, text, sizeof(INDENT_FUNC));
590 if (func == indent_func)
591 memset(&func, 0, sizeof(INDENT_FUNC));
592 text += sizeof(INDENT_FUNC);
593 } else if (*text == LINE_CMD_CONTINUE) {
594 memcpy(&tmp, text+1, sizeof(char *));
603 void textbuffer_views_unregister_indent_func(INDENT_FUNC indent_func)
605 g_slist_foreach(views, (GFunc) view_unregister_indent_func,
606 (void *) indent_func);
609 void textbuffer_view_set_scroll(TEXT_BUFFER_VIEW_REC *view, int scroll)
611 view->scroll = scroll;
614 void textbuffer_view_set_utf8(TEXT_BUFFER_VIEW_REC *view, int utf8)
619 static int view_get_linecount_all(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
624 while (line != NULL) {
625 linecount += view_get_linecount(view, line);
632 static void view_draw(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
633 int subline, int ypos, int lines, int fill_bottom)
637 if (view->dirty) /* don't bother drawing anything - redraw is coming */
640 while (line != NULL && lines > 0) {
641 linecount = view_line_draw(view, line, subline, ypos, lines);
642 ypos += linecount; lines -= linecount;
649 /* clear the rest of the view */
650 term_set_color(view->window, ATTR_RESET);
652 term_move(view->window, 0, ypos);
653 term_clrtoeol(view->window);
659 #define view_draw_top(view, lines, fill_bottom) \
660 view_draw(view, (view)->startline, (view)->subline, \
661 0, lines, fill_bottom)
663 static void view_draw_bottom(TEXT_BUFFER_VIEW_REC *view, int lines)
666 int ypos, maxline, subline, linecount;
668 maxline = view->height-lines;
669 line = view->startline; ypos = -view->subline; subline = 0;
670 while (line != NULL && ypos < maxline) {
671 linecount = view_get_linecount(view, line);
673 if (ypos > maxline) {
674 subline = maxline-(ypos-linecount);
680 view_draw(view, line, subline, maxline, lines, TRUE);
683 /* Returns number of lines actually scrolled */
684 static int view_scroll(TEXT_BUFFER_VIEW_REC *view, LINE_REC **lines,
685 int *subline, int scrollcount, int draw_nonclean)
687 int linecount, realcount, scroll_visible;
693 scroll_visible = lines == &view->startline;
695 realcount = -*subline;
696 scrollcount += *subline;
698 while (scrollcount > 0) {
699 linecount = view_get_linecount(view, *lines);
701 if ((scroll_visible && *lines == view->bottom_startline) &&
702 (scrollcount >= view->bottom_subline)) {
703 *subline = view->bottom_subline;
704 realcount += view->bottom_subline;
709 realcount += linecount;
710 scrollcount -= linecount;
711 if (scrollcount < 0) {
712 realcount += scrollcount;
713 *subline = linecount+scrollcount;
718 if ((*lines)->next == NULL)
721 *lines = (*lines)->next;
725 while (scrollcount < 0 && (*lines)->prev != NULL) {
726 *lines = (*lines)->prev;
727 linecount = view_get_linecount(view, *lines);
729 realcount -= linecount;
730 scrollcount += linecount;
731 if (scrollcount > 0) {
732 realcount += scrollcount;
733 *subline = scrollcount;
738 if (scroll_visible && realcount != 0 && view->window != NULL) {
739 if (realcount <= -view->height || realcount >= view->height) {
740 /* scrolled more than screenful, redraw the
742 textbuffer_view_redraw(view);
744 term_set_color(view->window, ATTR_RESET);
745 term_window_scroll(view->window, realcount);
749 view_draw_top(view, -realcount, TRUE);
751 view_draw_bottom(view, realcount);
754 term_refresh(view->window);
758 return realcount >= 0 ? realcount : -realcount;
761 /* Resize the view. */
762 void textbuffer_view_resize(TEXT_BUFFER_VIEW_REC *view, int width, int height)
766 g_return_if_fail(view != NULL);
767 g_return_if_fail(width > 0);
769 if (view->width != width) {
770 /* line cache needs to be recreated */
771 textbuffer_cache_unref(view->cache);
772 view->cache = textbuffer_cache_get(view->siblings, width);
775 view->width = width > 10 ? width : 10;
776 view->height = height > 1 ? height : 1;
778 if (view->buffer->first_line == NULL) {
779 view->empty_linecount = height;
783 textbuffer_view_init_bottom(view);
785 /* check that we didn't scroll lower than bottom startline.. */
786 if (textbuffer_line_exists_after(view->bottom_startline->next,
788 view->startline = view->bottom_startline;
789 view->subline = view->bottom_subline;
790 } else if (view->startline == view->bottom_startline &&
791 view->subline > view->bottom_subline) {
792 view->subline = view->bottom_subline;
794 /* make sure the subline is still in allowed range */
795 linecount = view_get_linecount(view, view->startline);
796 if (view->subline > linecount)
797 view->subline = linecount;
800 textbuffer_view_init_ypos(view);
801 if (view->bottom && !view_is_bottom(view)) {
802 /* we scrolled to far up, need to get down. go right over
803 the empty lines if there's any */
804 view->startline = view->bottom_startline;
805 view->subline = view->bottom_subline;
806 if (view->empty_linecount > 0) {
807 view_scroll(view, &view->startline, &view->subline,
808 -view->empty_linecount, FALSE);
810 textbuffer_view_init_ypos(view);
813 view->bottom = view_is_bottom(view);
815 /* check if we left empty space at the bottom.. */
816 linecount = view_get_linecount_all(view, view->startline) -
818 if (view->empty_linecount < view->height-linecount)
819 view->empty_linecount = view->height-linecount;
820 view->more_text = FALSE;
826 /* Clear the view, don't actually remove any lines from buffer. */
827 void textbuffer_view_clear(TEXT_BUFFER_VIEW_REC *view)
829 g_return_if_fail(view != NULL);
832 view->bottom_startline = view->startline =
833 textbuffer_line_last(view->buffer);
834 view->bottom_subline = view->subline =
835 view->buffer->cur_line == NULL ? 0 :
836 view_get_linecount(view, view->buffer->cur_line);
837 view->empty_linecount = view->height;
839 view->more_text = FALSE;
841 textbuffer_view_redraw(view);
844 /* Scroll the view up/down */
845 void textbuffer_view_scroll(TEXT_BUFFER_VIEW_REC *view, int lines)
849 g_return_if_fail(view != NULL);
851 count = view_scroll(view, &view->startline, &view->subline,
853 view->ypos += lines < 0 ? count : -count;
854 view->bottom = view_is_bottom(view);
855 if (view->bottom) view->more_text = FALSE;
857 if (view->window != NULL)
858 term_refresh(view->window);
861 /* Scroll to specified line */
862 void textbuffer_view_scroll_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
864 g_return_if_fail(view != NULL);
866 if (textbuffer_line_exists_after(view->bottom_startline->next, line)) {
867 view->startline = view->bottom_startline;
868 view->subline = view->bottom_subline;
870 view->startline = line;
874 textbuffer_view_init_ypos(view);
875 view->bottom = view_is_bottom(view);
876 if (view->bottom) view->more_text = FALSE;
878 textbuffer_view_redraw(view);
881 /* Return line cache */
882 LINE_CACHE_REC *textbuffer_view_get_line_cache(TEXT_BUFFER_VIEW_REC *view,
885 LINE_CACHE_REC *cache;
887 g_assert(view != NULL);
888 g_assert(line != NULL);
890 cache = g_hash_table_lookup(view->cache->line_cache, line);
892 cache = view_update_line_cache(view, line);
894 cache->last_access = time(NULL);
899 static void view_insert_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
901 int linecount, ypos, subline;
904 view->more_text = TRUE;
906 if (view->bottom_startline == NULL) {
907 view->startline = view->bottom_startline =
908 view->buffer->first_line;
911 if (view->buffer->cur_line != line &&
912 !textbuffer_line_exists_after(view->bottom_startline, line))
915 linecount = view->cache->last_linecount;
916 view->ypos += linecount;
917 if (view->empty_linecount > 0) {
918 view->empty_linecount -= linecount;
919 if (view->empty_linecount >= 0)
922 linecount = -view->empty_linecount;
923 view->empty_linecount = 0;
928 view_scroll(view, &view->bottom_startline,
929 &view->bottom_subline, linecount, FALSE);
933 if (view->scroll && view->ypos >= view->height) {
934 linecount = view->ypos-view->height+1;
935 view_scroll(view, &view->startline,
936 &view->subline, linecount, FALSE);
937 view->ypos -= linecount;
939 view->bottom = view_is_bottom(view);
942 if (view->window != NULL) {
943 ypos = view->ypos+1 - view->cache->last_linecount;
950 if (ypos < view->height) {
951 view_line_draw(view, line, subline, ypos,
952 view->height - ypos);
957 if (view->window != NULL)
958 term_refresh(view->window);
961 /* Update some line in the buffer which has been modified using
962 textbuffer_append() or textbuffer_insert(). */
963 void textbuffer_view_insert_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
966 unsigned char update_counter;
968 g_return_if_fail(view != NULL);
969 g_return_if_fail(line != NULL);
971 if (!view->buffer->last_eol)
974 update_counter = view->cache->update_counter+1;
975 view_update_cache(view, line, update_counter);
976 view_insert_line(view, line);
978 for (tmp = view->siblings; tmp != NULL; tmp = tmp->next) {
979 TEXT_BUFFER_VIEW_REC *rec = tmp->data;
981 view_update_cache(rec, line, update_counter);
982 view_insert_line(rec, line);
987 LINE_REC *remove_line;
991 static void bookmark_check_remove(char *key, LINE_REC *line,
992 BOOKMARK_FIND_REC *rec)
994 if (line == rec->remove_line)
995 rec->remove_list = g_slist_append(rec->remove_list, key);
998 static void view_bookmarks_check(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
1000 BOOKMARK_FIND_REC rec;
1004 rec.remove_line = line;
1005 rec.remove_list = NULL;
1006 g_hash_table_foreach(view->bookmarks,
1007 (GHFunc) bookmark_check_remove, &rec);
1009 if (rec.remove_list != NULL) {
1010 new_line = line->prev == NULL ? NULL :
1011 (line->next == NULL ? line->prev : line->next);
1012 for (tmp = rec.remove_list; tmp != NULL; tmp = tmp->next) {
1013 g_hash_table_remove(view->bookmarks, tmp->data);
1014 if (new_line != NULL) {
1015 g_hash_table_insert(view->bookmarks,
1016 tmp->data, new_line);
1019 g_slist_free(rec.remove_list);
1023 /* Return number of real lines `lines' list takes -
1024 stops counting when the height reaches the view height */
1025 static int view_get_lines_height(TEXT_BUFFER_VIEW_REC *view,
1026 LINE_REC *line, int subline,
1027 LINE_REC *skip_line)
1029 int height, linecount;
1032 while (line != NULL && height < view->height) {
1033 if (line != skip_line) {
1034 linecount = view_get_linecount(view, line);
1035 height += linecount;
1040 return height < view->height ? height : view->height;
1043 static void view_remove_line_update_startline(TEXT_BUFFER_VIEW_REC *view,
1044 LINE_REC *line, int linecount)
1048 if (view->startline == line) {
1049 view->startline = view->startline->prev != NULL ?
1050 view->startline->prev : view->startline->next;
1053 scroll = view->height -
1054 view_get_lines_height(view, view->startline,
1055 view->subline, line);
1057 view_scroll(view, &view->startline,
1058 &view->subline, -scroll, FALSE);
1062 /* FIXME: this is slow and unnecessary, but it's easy and
1064 textbuffer_view_init_ypos(view);
1065 if (textbuffer_line_exists_after(view->startline, line))
1066 view->ypos -= linecount;
1069 static void view_remove_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
1074 view_bookmarks_check(view, line);
1076 if (view->buffer->cur_line == line) {
1077 /* the last line is being removed */
1080 prevline = view->buffer->first_line == line ? NULL :
1081 textbuffer_line_last(view->buffer);
1082 view->cache->last_linecount = prevline == NULL ? 0 :
1083 view_get_linecount(view, prevline);
1086 if (view->buffer->first_line == line) {
1087 /* first line in the buffer - this is the most commonly
1089 if (view->bottom_startline == line) {
1090 /* very small scrollback.. */
1091 view->bottom_startline = view->bottom_startline->next;
1092 view->bottom_subline = 0;
1095 if (view->startline == line) {
1096 /* removing the first line in screen */
1097 realcount = view_scroll(view, &view->startline,
1100 view->ypos -= realcount;
1101 view->empty_linecount += linecount-realcount;
1104 if (textbuffer_line_exists_after(view->bottom_startline,
1106 realcount = view_scroll(view, &view->bottom_startline,
1107 &view->bottom_subline,
1109 view->empty_linecount += linecount-realcount;
1112 if (textbuffer_line_exists_after(view->startline,
1114 view_remove_line_update_startline(view, line,
1119 view->bottom = view_is_bottom(view);
1120 if (view->bottom) view->more_text = FALSE;
1121 if (view->window != NULL)
1122 term_refresh(view->window);
1125 /* Remove one line from buffer. */
1126 void textbuffer_view_remove_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
1129 unsigned char update_counter;
1132 g_return_if_fail(view != NULL);
1133 g_return_if_fail(line != NULL);
1135 linecount = view_get_linecount(view, line);
1136 update_counter = view->cache->update_counter+1;
1138 view_remove_line(view, line, linecount);
1139 view_remove_cache(view, line, update_counter);
1141 for (tmp = view->siblings; tmp != NULL; tmp = tmp->next) {
1142 TEXT_BUFFER_VIEW_REC *rec = tmp->data;
1144 view_remove_line(rec, line, linecount);
1145 view_remove_cache(rec, line, update_counter);
1148 textbuffer_remove(view->buffer, line);
1151 static int g_free_true(void *data)
1157 /* Remove all lines from buffer. */
1158 void textbuffer_view_remove_all_lines(TEXT_BUFFER_VIEW_REC *view)
1160 g_return_if_fail(view != NULL);
1162 textbuffer_remove_all_lines(view->buffer);
1164 g_hash_table_foreach_remove(view->bookmarks,
1165 (GHRFunc) g_free_true, NULL);
1167 view_reset_cache(view);
1168 textbuffer_view_clear(view);
1169 g_slist_foreach(view->siblings, (GFunc) textbuffer_view_clear, NULL);
1172 /* Set a bookmark in view */
1173 void textbuffer_view_set_bookmark(TEXT_BUFFER_VIEW_REC *view,
1174 const char *name, LINE_REC *line)
1176 gpointer key, value;
1178 g_return_if_fail(view != NULL);
1179 g_return_if_fail(name != NULL);
1181 if (g_hash_table_lookup_extended(view->bookmarks, name,
1183 g_hash_table_remove(view->bookmarks, key);
1187 g_hash_table_insert(view->bookmarks, g_strdup(name), line);
1190 /* Set a bookmark in view to the bottom line */
1191 void textbuffer_view_set_bookmark_bottom(TEXT_BUFFER_VIEW_REC *view,
1196 g_return_if_fail(view != NULL);
1197 g_return_if_fail(name != NULL);
1199 if (view->bottom_startline != NULL) {
1200 line = textbuffer_line_last(view->buffer);
1201 textbuffer_view_set_bookmark(view, name, line);
1205 /* Return the line for bookmark */
1206 LINE_REC *textbuffer_view_get_bookmark(TEXT_BUFFER_VIEW_REC *view,
1209 g_return_val_if_fail(view != NULL, NULL);
1210 g_return_val_if_fail(name != NULL, NULL);
1212 return g_hash_table_lookup(view->bookmarks, name);
1215 /* Specify window where the changes in view should be drawn,
1216 NULL disables it. */
1217 void textbuffer_view_set_window(TEXT_BUFFER_VIEW_REC *view,
1218 TERM_WINDOW *window)
1220 g_return_if_fail(view != NULL);
1222 if (view->window != window) {
1223 view->window = window;
1229 /* Redraw a view to window */
1230 void textbuffer_view_redraw(TEXT_BUFFER_VIEW_REC *view)
1232 g_return_if_fail(view != NULL);
1234 if (view->window != NULL) {
1235 view->dirty = FALSE;
1236 view_draw_top(view, view->height, TRUE);
1237 term_refresh(view->window);
1241 static int line_cache_check_remove(void *key, LINE_CACHE_REC *cache,
1244 if (cache->last_access+LINE_CACHE_KEEP_TIME > *now)
1247 line_cache_destroy(NULL, cache);
1251 static int sig_check_linecache(void)
1253 GSList *tmp, *caches;
1256 now = time(NULL); caches = NULL;
1257 for (tmp = views; tmp != NULL; tmp = tmp->next) {
1258 TEXT_BUFFER_VIEW_REC *rec = tmp->data;
1260 if (g_slist_find(caches, rec->cache) != NULL)
1263 caches = g_slist_append(caches, rec->cache);
1264 g_hash_table_foreach_remove(rec->cache->line_cache,
1265 (GHRFunc) line_cache_check_remove,
1269 g_slist_free(caches);
1273 void textbuffer_view_init(void)
1275 linecache_tag = g_timeout_add(LINE_CACHE_CHECK_TIME, (GSourceFunc) sig_check_linecache, NULL);
1278 void textbuffer_view_deinit(void)
1280 g_source_remove(linecache_tag);