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
22 #include "textbuffer-view.h"
30 /* how often to scan line cache for lines not accessed for a while (ms) */
31 #define LINE_CACHE_CHECK_TIME (5*60*1000)
32 /* how long to keep line cache in memory (seconds) */
33 #define LINE_CACHE_KEEP_TIME (10*60)
35 static int linecache_tag;
38 #define view_is_bottom(view) \
39 ((view)->ypos >= -1 && (view)->ypos < (view)->height)
41 #define view_get_linecount(view, line) \
42 textbuffer_view_get_line_cache(view, line)->count
44 static GSList *textbuffer_get_views(TEXT_BUFFER_REC *buffer)
48 for (tmp = views; tmp != NULL; tmp = tmp->next) {
49 TEXT_BUFFER_VIEW_REC *view = tmp->data;
51 if (view->buffer == buffer) {
52 list = g_slist_copy(view->siblings);
53 return g_slist_prepend(list, view);
60 static TEXT_BUFFER_CACHE_REC *
61 textbuffer_cache_get(GSList *views, int width)
63 TEXT_BUFFER_CACHE_REC *cache;
65 /* check if there's existing cache with correct width */
66 while (views != NULL) {
67 TEXT_BUFFER_VIEW_REC *view = views->data;
69 if (view->width == width) {
70 view->cache->refcount++;
76 /* create new cache */
77 cache = g_new0(TEXT_BUFFER_CACHE_REC, 1);
80 cache->line_cache = g_hash_table_new((GHashFunc) g_direct_hash,
81 (GCompareFunc) g_direct_equal);
85 static int line_cache_destroy(void *key, LINE_CACHE_REC *cache)
91 static void textbuffer_cache_destroy(TEXT_BUFFER_CACHE_REC *cache)
93 g_hash_table_foreach(cache->line_cache,
94 (GHFunc) line_cache_destroy, NULL);
95 g_hash_table_destroy(cache->line_cache);
99 static void textbuffer_cache_unref(TEXT_BUFFER_CACHE_REC *cache)
101 if (--cache->refcount == 0)
102 textbuffer_cache_destroy(cache);
105 #define FGATTR (ATTR_NOCOLORS | ATTR_RESETFG | ATTR_BOLD | 0x0f)
106 #define BGATTR (ATTR_NOCOLORS | ATTR_RESETBG | ATTR_BLINK | 0xf0)
108 static void update_cmd_color(unsigned char cmd, int *color)
110 if ((cmd & 0x80) == 0) {
111 if (cmd & LINE_COLOR_BG) {
112 /* set background color */
114 if ((cmd & LINE_COLOR_DEFAULT) == 0)
115 *color |= (cmd & 0x0f) << 4;
117 *color = (*color & FGATTR) | ATTR_RESETBG;
118 if (cmd & LINE_COLOR_BLINK)
119 *color |= ATTR_BLINK;
122 /* set foreground color */
124 if ((cmd & LINE_COLOR_DEFAULT) == 0)
125 *color |= cmd & 0x0f;
127 *color = (*color & BGATTR) | ATTR_RESETFG;
128 if (cmd & LINE_COLOR_BOLD)
132 } else switch (cmd) {
133 case LINE_CMD_UNDERLINE:
134 *color ^= ATTR_UNDERLINE;
136 case LINE_CMD_REVERSE:
137 *color ^= ATTR_REVERSE;
139 case LINE_CMD_COLOR0:
145 static LINE_CACHE_REC *
146 view_update_line_cache(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
148 INDENT_FUNC indent_func;
150 LINE_CACHE_SUB_REC *sub;
153 const unsigned char *ptr, *last_space_ptr;
154 int xpos, pos, indent_pos, last_space, last_color, color, linecount;
156 g_return_val_if_fail(line->text != NULL, NULL);
158 color = ATTR_RESETFG | ATTR_RESETBG;
159 xpos = 0; indent_pos = view->default_indent;
160 last_space = last_color = 0; last_space_ptr = NULL; sub = NULL;
162 indent_func = view->default_indent_func;
165 for (ptr = line->text;;) {
172 if (cmd == LINE_CMD_EOL || cmd == LINE_CMD_FORMAT)
175 if (cmd == LINE_CMD_CONTINUE) {
178 memcpy(&tmp, ptr, sizeof(char *));
183 if (cmd == LINE_CMD_INDENT) {
184 /* set indentation position here - don't do
185 it if we're too close to right border */
186 if (xpos < view->width-5) indent_pos = xpos;
187 } else if (cmd == LINE_CMD_INDENT_FUNC) {
188 memcpy(&indent_func, ptr, sizeof(INDENT_FUNC));
189 ptr += sizeof(INDENT_FUNC);
190 if (indent_func == NULL)
191 indent_func = view->default_indent_func;
193 update_cmd_color(cmd, &color);
197 if (xpos == view->width && sub != NULL &&
198 (last_space <= indent_pos || last_space <= 10) &&
199 view->longword_noindent) {
200 /* long word, remove the indentation from this line */
205 if (xpos == view->width) {
206 xpos = indent_func == NULL ? indent_pos :
207 indent_func(view, line, -1);
209 sub = g_new0(LINE_CACHE_SUB_REC, 1);
210 if (last_space > indent_pos && last_space > 10) {
211 /* go back to last space */
213 ptr = last_space_ptr;
214 while (*ptr == ' ') ptr++;
215 } else if (view->longword_noindent) {
216 /* long word, no indentation in next line */
218 sub->continues = TRUE;
223 sub->indent_func = indent_func;
226 lines = g_slist_append(lines, sub);
234 get_utf8_char(&ptr, 6);
239 last_space_ptr = ptr;
244 rec = g_malloc(sizeof(LINE_CACHE_REC)-sizeof(LINE_CACHE_SUB_REC) +
245 sizeof(LINE_CACHE_SUB_REC) * (linecount-1));
246 rec->last_access = time(NULL);
247 rec->count = linecount;
249 if (rec->count > 1) {
250 for (pos = 0; lines != NULL; pos++) {
251 memcpy(&rec->lines[pos], lines->data,
252 sizeof(LINE_CACHE_SUB_REC));
255 lines = g_slist_remove(lines, lines->data);
259 g_hash_table_insert(view->cache->line_cache, line, rec);
263 static void view_remove_cache(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
264 unsigned char update_counter)
266 LINE_CACHE_REC *cache;
268 if (view->cache->update_counter == update_counter)
270 view->cache->update_counter = update_counter;
272 cache = g_hash_table_lookup(view->cache->line_cache, line);
275 g_hash_table_remove(view->cache->line_cache, line);
279 static void view_update_cache(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
280 unsigned char update_counter)
282 view_remove_cache(view, line, update_counter);
284 if (view->buffer->cur_line == line)
285 view->cache->last_linecount = view_get_linecount(view, line);
288 static void view_reset_cache(TEXT_BUFFER_VIEW_REC *view)
292 /* destroy line caches - note that you can't do simultaneously
293 unrefs + cache_get()s or it will keep using the old caches */
294 textbuffer_cache_unref(view->cache);
295 g_slist_foreach(view->siblings, (GFunc) textbuffer_cache_unref, NULL);
297 view->cache = textbuffer_cache_get(view->siblings, view->width);
298 for (tmp = view->siblings; tmp != NULL; tmp = tmp->next) {
299 TEXT_BUFFER_VIEW_REC *rec = tmp->data;
301 rec->cache = textbuffer_cache_get(rec->siblings, rec->width);
305 static int view_line_draw(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
306 int subline, int ypos, int max)
308 INDENT_FUNC indent_func;
309 LINE_CACHE_REC *cache;
310 const unsigned char *text, *text_newline;
312 int xpos, color, drawcount, first, need_move, need_clrtoeol;
314 if (view->dirty) /* don't bother drawing anything - redraw is coming */
317 cache = textbuffer_view_get_line_cache(view, line);
318 if (subline >= cache->count)
322 need_move = TRUE; need_clrtoeol = FALSE;
323 xpos = drawcount = 0; first = TRUE;
324 text_newline = text =
325 subline == 0 ? line->text : cache->lines[subline-1].start;
327 if (text == text_newline) {
328 if (need_clrtoeol && xpos < term_width) {
329 term_set_color(view->window, ATTR_RESET);
330 term_clrtoeol(view->window);
342 /* continuing previous line - indent it */
343 indent_func = cache->lines[subline-1].indent_func;
344 xpos = indent_func != NULL ?
345 indent_func(view, line, ypos) :
346 cache->lines[subline-1].indent;
347 color = cache->lines[subline-1].color;
351 need_clrtoeol = TRUE;
353 /* line was indented - need to clear the
354 indented area first */
355 term_set_color(view->window, ATTR_RESET);
356 term_move(view->window, 0, ypos);
357 term_clrtoeol(view->window);
360 if (need_move || xpos > 0)
361 term_move(view->window, xpos, ypos);
363 term_set_color(view->window, color);
365 if (subline == cache->count-1) {
369 /* get the beginning of the next subline */
370 text_newline = cache->lines[subline].start;
371 need_move = !cache->lines[subline].continues;
380 if (*text == LINE_CMD_EOL || *text == LINE_CMD_FORMAT)
383 if (*text == LINE_CMD_CONTINUE) {
384 /* jump to next block */
385 memcpy(&tmp, text+1, sizeof(unsigned char *));
388 } else if (*text == LINE_CMD_INDENT_FUNC) {
389 text += sizeof(INDENT_FUNC);
391 update_cmd_color(*text, &color);
392 term_set_color(view->window, color);
398 if (xpos < term_width) {
399 const unsigned char *end = text;
401 get_utf8_char(&end, 6);
404 (end != text || (*text & 127) >= 32)) {
405 for (; text < end; text++)
406 term_addch(view->window, *text);
407 term_addch(view->window, *text);
410 term_set_color(view->window, ATTR_RESET|ATTR_REVERSE);
411 term_addch(view->window, (*text & 127)+'A'-1);
412 term_set_color(view->window, color);
419 if (need_clrtoeol && xpos < term_width) {
420 term_set_color(view->window, ATTR_RESET);
421 term_clrtoeol(view->window);
427 /* Recalculate view's bottom line information - try to keep the
428 original if possible */
429 static void textbuffer_view_init_bottom(TEXT_BUFFER_VIEW_REC *view)
432 int linecount, total;
434 if (view->empty_linecount == 0) {
435 /* no empty lines in screen, no need to try to keep
436 the old bottom startline */
437 view->bottom_startline = NULL;
441 line = textbuffer_line_last(view->buffer);
442 for (; line != NULL; line = line->prev) {
443 linecount = view_get_linecount(view, line);
444 if (line == view->bottom_startline) {
445 /* keep the old one, make sure that subline is ok */
446 if (view->bottom_subline > linecount)
447 view->bottom_subline = linecount;
448 view->empty_linecount = view->height - total -
449 (linecount-view->bottom_subline);
454 if (total >= view->height) {
455 view->bottom_startline = line;
456 view->bottom_subline = total - view->height;
457 view->empty_linecount = 0;
462 /* not enough lines so we must be at the beginning of the buffer */
463 view->bottom_startline = view->buffer->first_line;
464 view->bottom_subline = 0;
465 view->empty_linecount = view->height - total;
468 static void textbuffer_view_init_ypos(TEXT_BUFFER_VIEW_REC *view)
472 g_return_if_fail(view != NULL);
474 view->ypos = -view->subline-1;
475 for (line = view->startline; line != NULL; line = line->next)
476 view->ypos += view_get_linecount(view, line);
479 /* Create new view. */
480 TEXT_BUFFER_VIEW_REC *textbuffer_view_create(TEXT_BUFFER_REC *buffer,
481 int width, int height,
482 int scroll, int utf8)
484 TEXT_BUFFER_VIEW_REC *view;
486 g_return_val_if_fail(buffer != NULL, NULL);
487 g_return_val_if_fail(width > 0, NULL);
489 view = g_new0(TEXT_BUFFER_VIEW_REC, 1);
490 view->buffer = buffer;
491 view->siblings = textbuffer_get_views(buffer);
494 view->height = height;
495 view->scroll = scroll;
498 view->cache = textbuffer_cache_get(view->siblings, width);
499 textbuffer_view_init_bottom(view);
501 view->startline = view->bottom_startline;
502 view->subline = view->bottom_subline;
505 textbuffer_view_init_ypos(view);
507 view->bookmarks = g_hash_table_new((GHashFunc) g_str_hash,
508 (GCompareFunc) g_str_equal);
510 views = g_slist_append(views, view);
514 /* Destroy the view. */
515 void textbuffer_view_destroy(TEXT_BUFFER_VIEW_REC *view)
519 g_return_if_fail(view != NULL);
521 views = g_slist_remove(views, view);
523 if (view->siblings == NULL) {
524 /* last view for textbuffer, destroy */
525 textbuffer_destroy(view->buffer);
527 /* remove ourself from siblings lists */
528 for (tmp = view->siblings; tmp != NULL; tmp = tmp->next) {
529 TEXT_BUFFER_VIEW_REC *rec = tmp->data;
531 rec->siblings = g_slist_remove(rec->siblings, view);
533 g_slist_free(view->siblings);
536 g_hash_table_foreach(view->bookmarks, (GHFunc) g_free, NULL);
537 g_hash_table_destroy(view->bookmarks);
539 textbuffer_cache_unref(view->cache);
543 /* Change the default indent position */
544 void textbuffer_view_set_default_indent(TEXT_BUFFER_VIEW_REC *view,
546 int longword_noindent,
547 INDENT_FUNC indent_func)
549 if (default_indent != -1)
550 view->default_indent = default_indent;
551 if (longword_noindent != -1)
552 view->longword_noindent = longword_noindent;
554 view->default_indent_func = indent_func;
557 static void view_unregister_indent_func(TEXT_BUFFER_VIEW_REC *view,
558 INDENT_FUNC indent_func)
562 const unsigned char *text, *tmp;
564 if (view->default_indent_func == indent_func)
565 view->default_indent_func = NULL;
567 /* recreate cache so it won't contain references
568 to the indent function */
569 view_reset_cache(view);
570 view->cache = textbuffer_cache_get(view->siblings, view->width);
572 /* remove all references to the indent function from buffer */
573 line = view->buffer->first_line;
574 while (line != NULL) {
577 for (text = line->text;; text++) {
582 if (*text == LINE_CMD_EOL)
585 if (*text == LINE_CMD_INDENT_FUNC) {
587 memcpy(&func, text, sizeof(INDENT_FUNC));
588 if (func == indent_func)
589 memset(&func, 0, sizeof(INDENT_FUNC));
590 text += sizeof(INDENT_FUNC);
591 } else if (*text == LINE_CMD_CONTINUE) {
592 memcpy(&tmp, text+1, sizeof(char *));
601 void textbuffer_views_unregister_indent_func(INDENT_FUNC indent_func)
603 g_slist_foreach(views, (GFunc) view_unregister_indent_func,
604 (void *) indent_func);
607 void textbuffer_view_set_scroll(TEXT_BUFFER_VIEW_REC *view, int scroll)
609 view->scroll = scroll;
612 void textbuffer_view_set_utf8(TEXT_BUFFER_VIEW_REC *view, int utf8)
617 static int view_get_linecount_all(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
622 while (line != NULL) {
623 linecount += view_get_linecount(view, line);
630 static void view_draw(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
631 int subline, int ypos, int lines, int fill_bottom)
635 if (view->dirty) /* don't bother drawing anything - redraw is coming */
638 while (line != NULL && lines > 0) {
639 linecount = view_line_draw(view, line, subline, ypos, lines);
640 ypos += linecount; lines -= linecount;
647 /* clear the rest of the view */
648 term_set_color(view->window, ATTR_RESET);
650 term_move(view->window, 0, ypos);
651 term_clrtoeol(view->window);
657 #define view_draw_top(view, lines, fill_bottom) \
658 view_draw(view, (view)->startline, (view)->subline, \
659 0, lines, fill_bottom)
661 static void view_draw_bottom(TEXT_BUFFER_VIEW_REC *view, int lines)
664 int ypos, maxline, subline, linecount;
666 maxline = view->height-lines;
667 line = view->startline; ypos = -view->subline; subline = 0;
668 while (line != NULL && ypos < maxline) {
669 linecount = view_get_linecount(view, line);
671 if (ypos > maxline) {
672 subline = maxline-(ypos-linecount);
678 view_draw(view, line, subline, maxline, lines, TRUE);
681 /* Returns number of lines actually scrolled */
682 static int view_scroll(TEXT_BUFFER_VIEW_REC *view, LINE_REC **lines,
683 int *subline, int scrollcount, int draw_nonclean)
685 int linecount, realcount, scroll_visible;
691 scroll_visible = lines == &view->startline;
693 realcount = -*subline;
694 scrollcount += *subline;
696 while (scrollcount > 0) {
697 linecount = view_get_linecount(view, *lines);
699 if ((scroll_visible && *lines == view->bottom_startline) &&
700 (scrollcount >= view->bottom_subline)) {
701 *subline = view->bottom_subline;
702 realcount += view->bottom_subline;
707 realcount += linecount;
708 scrollcount -= linecount;
709 if (scrollcount < 0) {
710 realcount += scrollcount;
711 *subline = linecount+scrollcount;
716 *lines = (*lines)->next;
720 while (scrollcount < 0 && (*lines)->prev != NULL) {
721 *lines = (*lines)->prev;
722 linecount = view_get_linecount(view, *lines);
724 realcount -= linecount;
725 scrollcount += linecount;
726 if (scrollcount > 0) {
727 realcount += scrollcount;
728 *subline = scrollcount;
733 if (scroll_visible && realcount != 0 && view->window != NULL) {
734 if (realcount <= -view->height || realcount >= view->height) {
735 /* scrolled more than screenful, redraw the
737 textbuffer_view_redraw(view);
739 term_set_color(view->window, ATTR_RESET);
740 term_window_scroll(view->window, realcount);
744 view_draw_top(view, -realcount, TRUE);
746 view_draw_bottom(view, realcount);
749 term_refresh(view->window);
753 return realcount >= 0 ? realcount : -realcount;
756 /* Resize the view. */
757 void textbuffer_view_resize(TEXT_BUFFER_VIEW_REC *view, int width, int height)
761 g_return_if_fail(view != NULL);
762 g_return_if_fail(width > 0);
764 if (view->width != width) {
765 /* line cache needs to be recreated */
766 textbuffer_cache_unref(view->cache);
767 view->cache = textbuffer_cache_get(view->siblings, width);
770 view->width = width > 10 ? width : 10;
771 view->height = height > 1 ? height : 1;
773 if (view->buffer->first_line == NULL) {
774 view->empty_linecount = height;
778 textbuffer_view_init_bottom(view);
780 /* check that we didn't scroll lower than bottom startline.. */
781 if (textbuffer_line_exists_after(view->bottom_startline->next,
783 view->startline = view->bottom_startline;
784 view->subline = view->bottom_subline;
785 } else if (view->startline == view->bottom_startline &&
786 view->subline > view->bottom_subline) {
787 view->subline = view->bottom_subline;
789 /* make sure the subline is still in allowed range */
790 linecount = view_get_linecount(view, view->startline);
791 if (view->subline > linecount)
792 view->subline = linecount;
795 textbuffer_view_init_ypos(view);
796 if (view->bottom && !view_is_bottom(view)) {
797 /* we scrolled to far up, need to get down. go right over
798 the empty lines if there's any */
799 view->startline = view->bottom_startline;
800 view->subline = view->bottom_subline;
801 if (view->empty_linecount > 0) {
802 view_scroll(view, &view->startline, &view->subline,
803 -view->empty_linecount, FALSE);
805 textbuffer_view_init_ypos(view);
808 view->bottom = view_is_bottom(view);
810 /* check if we left empty space at the bottom.. */
811 linecount = view_get_linecount_all(view, view->startline) -
813 if (view->empty_linecount < view->height-linecount)
814 view->empty_linecount = view->height-linecount;
815 view->more_text = FALSE;
821 /* Clear the view, don't actually remove any lines from buffer. */
822 void textbuffer_view_clear(TEXT_BUFFER_VIEW_REC *view)
824 g_return_if_fail(view != NULL);
827 view->bottom_startline = view->startline =
828 textbuffer_line_last(view->buffer);
829 view->bottom_subline = view->subline =
830 view->buffer->cur_line == NULL ? 0 :
831 view_get_linecount(view, view->buffer->cur_line);
832 view->empty_linecount = view->height;
834 view->more_text = FALSE;
836 textbuffer_view_redraw(view);
839 /* Scroll the view up/down */
840 void textbuffer_view_scroll(TEXT_BUFFER_VIEW_REC *view, int lines)
844 g_return_if_fail(view != NULL);
846 count = view_scroll(view, &view->startline, &view->subline,
848 view->ypos += lines < 0 ? count : -count;
849 view->bottom = view_is_bottom(view);
850 if (view->bottom) view->more_text = FALSE;
852 if (view->window != NULL)
853 term_refresh(view->window);
856 /* Scroll to specified line */
857 void textbuffer_view_scroll_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
859 g_return_if_fail(view != NULL);
861 if (textbuffer_line_exists_after(view->bottom_startline->next, line)) {
862 view->startline = view->bottom_startline;
863 view->subline = view->bottom_subline;
865 view->startline = line;
869 textbuffer_view_init_ypos(view);
870 view->bottom = view_is_bottom(view);
871 if (view->bottom) view->more_text = FALSE;
873 textbuffer_view_redraw(view);
876 /* Return line cache */
877 LINE_CACHE_REC *textbuffer_view_get_line_cache(TEXT_BUFFER_VIEW_REC *view,
880 LINE_CACHE_REC *cache;
882 g_return_val_if_fail(view != NULL, NULL);
883 g_return_val_if_fail(line != NULL, NULL);
885 cache = g_hash_table_lookup(view->cache->line_cache, line);
887 cache = view_update_line_cache(view, line);
889 cache->last_access = time(NULL);
894 static void view_insert_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
896 int linecount, ypos, subline;
899 view->more_text = TRUE;
901 if (view->bottom_startline == NULL) {
902 view->startline = view->bottom_startline =
903 view->buffer->first_line;
906 if (view->buffer->cur_line != line &&
907 !textbuffer_line_exists_after(view->bottom_startline, line))
910 linecount = view->cache->last_linecount;
911 view->ypos += linecount;
912 if (view->empty_linecount > 0) {
913 view->empty_linecount -= linecount;
914 if (view->empty_linecount >= 0)
917 linecount = -view->empty_linecount;
918 view->empty_linecount = 0;
923 view_scroll(view, &view->bottom_startline,
924 &view->bottom_subline, linecount, FALSE);
928 if (view->scroll && view->ypos >= view->height) {
929 linecount = view->ypos-view->height+1;
930 view_scroll(view, &view->startline,
931 &view->subline, linecount, FALSE);
932 view->ypos -= linecount;
934 view->bottom = view_is_bottom(view);
937 if (view->window != NULL) {
938 ypos = view->ypos+1 - view->cache->last_linecount;
945 if (ypos < view->height) {
946 view_line_draw(view, line, subline, ypos,
947 view->height - ypos);
952 if (view->window != NULL)
953 term_refresh(view->window);
956 /* Update some line in the buffer which has been modified using
957 textbuffer_append() or textbuffer_insert(). */
958 void textbuffer_view_insert_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
961 unsigned char update_counter;
963 g_return_if_fail(view != NULL);
964 g_return_if_fail(line != NULL);
966 if (!view->buffer->last_eol)
969 update_counter = view->cache->update_counter+1;
970 view_update_cache(view, line, update_counter);
971 view_insert_line(view, line);
973 for (tmp = view->siblings; tmp != NULL; tmp = tmp->next) {
974 TEXT_BUFFER_VIEW_REC *rec = tmp->data;
976 view_update_cache(rec, line, update_counter);
977 view_insert_line(rec, line);
982 LINE_REC *remove_line;
986 static void bookmark_check_remove(char *key, LINE_REC *line,
987 BOOKMARK_FIND_REC *rec)
989 if (line == rec->remove_line)
990 rec->remove_list = g_slist_append(rec->remove_list, key);
993 static void view_bookmarks_check(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
995 BOOKMARK_FIND_REC rec;
999 rec.remove_line = line;
1000 rec.remove_list = NULL;
1001 g_hash_table_foreach(view->bookmarks,
1002 (GHFunc) bookmark_check_remove, &rec);
1004 if (rec.remove_list != NULL) {
1005 new_line = line->prev == NULL ? NULL :
1006 (line->next == NULL ? line->prev : line->next);
1007 for (tmp = rec.remove_list; tmp != NULL; tmp = tmp->next) {
1008 g_hash_table_remove(view->bookmarks, tmp->data);
1009 if (new_line != NULL) {
1010 g_hash_table_insert(view->bookmarks,
1011 tmp->data, new_line);
1014 g_slist_free(rec.remove_list);
1018 /* Return number of real lines `lines' list takes -
1019 stops counting when the height reaches the view height */
1020 static int view_get_lines_height(TEXT_BUFFER_VIEW_REC *view,
1021 LINE_REC *line, int subline,
1022 LINE_REC *skip_line)
1024 int height, linecount;
1027 while (line != NULL && height < view->height) {
1028 if (line != skip_line) {
1029 linecount = view_get_linecount(view, line);
1030 height += linecount;
1035 return height < view->height ? height : view->height;
1038 static void view_remove_line_update_startline(TEXT_BUFFER_VIEW_REC *view,
1039 LINE_REC *line, int linecount)
1043 if (view->startline == line) {
1044 view->startline = view->startline->prev != NULL ?
1045 view->startline->prev : view->startline->next;
1048 scroll = view->height -
1049 view_get_lines_height(view, view->startline,
1050 view->subline, line);
1052 view_scroll(view, &view->startline,
1053 &view->subline, -scroll, FALSE);
1057 /* FIXME: this is slow and unnecessary, but it's easy and
1059 textbuffer_view_init_ypos(view);
1060 if (textbuffer_line_exists_after(view->startline, line))
1061 view->ypos -= linecount;
1064 static void view_remove_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
1069 view_bookmarks_check(view, line);
1071 if (view->buffer->cur_line == line) {
1072 /* the last line is being removed */
1075 prevline = view->buffer->first_line == line ? NULL :
1076 textbuffer_line_last(view->buffer);
1077 view->cache->last_linecount = prevline == NULL ? 0 :
1078 view_get_linecount(view, prevline);
1081 if (view->buffer->first_line == line) {
1082 /* first line in the buffer - this is the most commonly
1084 if (view->bottom_startline == line) {
1085 /* very small scrollback.. */
1086 view->bottom_startline = view->bottom_startline->next;
1087 view->bottom_subline = 0;
1090 if (view->startline == line) {
1091 /* removing the first line in screen */
1092 realcount = view_scroll(view, &view->startline,
1095 view->ypos -= realcount;
1096 view->empty_linecount += linecount-realcount;
1099 if (textbuffer_line_exists_after(view->bottom_startline,
1101 realcount = view_scroll(view, &view->bottom_startline,
1102 &view->bottom_subline,
1104 view->empty_linecount += linecount-realcount;
1107 if (textbuffer_line_exists_after(view->startline,
1109 view_remove_line_update_startline(view, line,
1114 view->bottom = view_is_bottom(view);
1115 if (view->bottom) view->more_text = FALSE;
1116 if (view->window != NULL)
1117 term_refresh(view->window);
1120 /* Remove one line from buffer. */
1121 void textbuffer_view_remove_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
1124 unsigned char update_counter;
1127 g_return_if_fail(view != NULL);
1128 g_return_if_fail(line != NULL);
1130 linecount = view_get_linecount(view, line);
1131 update_counter = view->cache->update_counter+1;
1133 view_remove_line(view, line, linecount);
1134 view_remove_cache(view, line, update_counter);
1136 for (tmp = view->siblings; tmp != NULL; tmp = tmp->next) {
1137 TEXT_BUFFER_VIEW_REC *rec = tmp->data;
1139 view_remove_line(rec, line, linecount);
1140 view_remove_cache(rec, line, update_counter);
1143 textbuffer_remove(view->buffer, line);
1146 static int g_free_true(void *data)
1152 /* Remove all lines from buffer. */
1153 void textbuffer_view_remove_all_lines(TEXT_BUFFER_VIEW_REC *view)
1155 g_return_if_fail(view != NULL);
1157 textbuffer_remove_all_lines(view->buffer);
1159 g_hash_table_foreach_remove(view->bookmarks,
1160 (GHRFunc) g_free_true, NULL);
1162 view_reset_cache(view);
1163 textbuffer_view_clear(view);
1164 g_slist_foreach(view->siblings, (GFunc) textbuffer_view_clear, NULL);
1167 /* Set a bookmark in view */
1168 void textbuffer_view_set_bookmark(TEXT_BUFFER_VIEW_REC *view,
1169 const char *name, LINE_REC *line)
1171 gpointer key, value;
1173 g_return_if_fail(view != NULL);
1174 g_return_if_fail(name != NULL);
1176 if (g_hash_table_lookup_extended(view->bookmarks, name,
1178 g_hash_table_remove(view->bookmarks, key);
1182 g_hash_table_insert(view->bookmarks, g_strdup(name), line);
1185 /* Set a bookmark in view to the bottom line */
1186 void textbuffer_view_set_bookmark_bottom(TEXT_BUFFER_VIEW_REC *view,
1191 g_return_if_fail(view != NULL);
1192 g_return_if_fail(name != NULL);
1194 if (view->bottom_startline != NULL) {
1195 line = textbuffer_line_last(view->buffer);
1196 textbuffer_view_set_bookmark(view, name, line);
1200 /* Return the line for bookmark */
1201 LINE_REC *textbuffer_view_get_bookmark(TEXT_BUFFER_VIEW_REC *view,
1204 g_return_val_if_fail(view != NULL, NULL);
1205 g_return_val_if_fail(name != NULL, NULL);
1207 return g_hash_table_lookup(view->bookmarks, name);
1210 /* Specify window where the changes in view should be drawn,
1211 NULL disables it. */
1212 void textbuffer_view_set_window(TEXT_BUFFER_VIEW_REC *view,
1213 TERM_WINDOW *window)
1215 g_return_if_fail(view != NULL);
1217 if (view->window != window) {
1218 view->window = window;
1224 /* Redraw a view to window */
1225 void textbuffer_view_redraw(TEXT_BUFFER_VIEW_REC *view)
1227 g_return_if_fail(view != NULL);
1229 if (view->window != NULL) {
1230 view->dirty = FALSE;
1231 view_draw_top(view, view->height, TRUE);
1232 term_refresh(view->window);
1236 static int line_cache_check_remove(void *key, LINE_CACHE_REC *cache,
1239 if (cache->last_access+LINE_CACHE_KEEP_TIME > *now)
1242 line_cache_destroy(NULL, cache);
1246 static int sig_check_linecache(void)
1248 GSList *tmp, *caches;
1251 now = time(NULL); caches = NULL;
1252 for (tmp = views; tmp != NULL; tmp = tmp->next) {
1253 TEXT_BUFFER_VIEW_REC *rec = tmp->data;
1255 if (g_slist_find(caches, rec->cache) != NULL)
1258 caches = g_slist_append(caches, rec->cache);
1259 g_hash_table_foreach_remove(rec->cache->line_cache,
1260 (GHRFunc) line_cache_check_remove,
1264 g_slist_free(caches);
1268 void textbuffer_view_init(void)
1270 linecache_tag = g_timeout_add(LINE_CACHE_CHECK_TIME, (GSourceFunc) sig_check_linecache, NULL);
1273 void textbuffer_view_deinit(void)
1275 g_source_remove(linecache_tag);