/* textbuffer-view.c : Text buffer handling Copyright (C) 1999-2001 Timo Sirainen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "module.h" #include "textbuffer-view.h" #include "screen.h" typedef struct { char *name; LINE_REC *line; } BOOKMARK_REC; /* how often to scan line cache for lines not accessed for a while (ms) */ #define LINE_CACHE_CHECK_TIME (5*60*1000) /* how long to keep line cache in memory (seconds) */ #define LINE_CACHE_KEEP_TIME (10*60) static int linecache_tag; static GSList *views; #define view_is_bottom(view) \ ((view)->ypos >= -1 && (view)->ypos < (view)->height) #define view_get_linecount(view, line) \ textbuffer_view_get_line_cache(view, line)->count static GSList *textbuffer_get_views(TEXT_BUFFER_REC *buffer) { GSList *tmp, *list; for (tmp = views; tmp != NULL; tmp = tmp->next) { TEXT_BUFFER_VIEW_REC *view = tmp->data; if (view->buffer == buffer) { list = g_slist_copy(view->siblings); return g_slist_prepend(list, view); } } return NULL; } static TEXT_BUFFER_CACHE_REC * textbuffer_cache_get(GSList *views, int width) { TEXT_BUFFER_CACHE_REC *cache; /* check if there's existing cache with correct width */ while (views != NULL) { TEXT_BUFFER_VIEW_REC *view = views->data; if (view->width == width) { view->cache->refcount++; return view->cache; } views = views->next; } /* create new cache */ cache = g_new0(TEXT_BUFFER_CACHE_REC, 1); cache->refcount = 1; cache->width = width; cache->line_cache = g_hash_table_new((GHashFunc) g_direct_hash, (GCompareFunc) g_direct_equal); return cache; } static int line_cache_destroy(void *key, LINE_CACHE_REC *cache) { g_free(cache); return TRUE; } static void textbuffer_cache_destroy(TEXT_BUFFER_CACHE_REC *cache) { g_hash_table_foreach(cache->line_cache, (GHFunc) line_cache_destroy, NULL); g_hash_table_destroy(cache->line_cache); g_free(cache); } static void textbuffer_cache_unref(TEXT_BUFFER_CACHE_REC *cache) { if (--cache->refcount == 0) textbuffer_cache_destroy(cache); } static LINE_CACHE_REC * view_update_line_cache(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line) { LINE_CACHE_REC *rec; LINE_CACHE_SUB_REC *sub; GSList *lines; unsigned char cmd; char *ptr, *last_space_ptr; int xpos, pos, indent_pos, last_space, last_color, color, linecount; g_return_val_if_fail(line->text != NULL, NULL); xpos = 0; color = 0; indent_pos = view->default_indent; last_space = last_color = 0; last_space_ptr = NULL; sub = NULL; linecount = 1; lines = NULL; for (ptr = line->text;;) { if (*ptr == '\0') { /* command */ ptr++; cmd = *ptr; ptr++; if (cmd == LINE_CMD_EOL || cmd == LINE_CMD_FORMAT) break; if (cmd == LINE_CMD_CONTINUE) { char *tmp; memcpy(&tmp, ptr, sizeof(char *)); ptr = tmp; continue; } if ((cmd & 0x80) == 0) { /* set color */ color = (color & ATTR_UNDERLINE) | cmd; } else switch (cmd) { case LINE_CMD_UNDERLINE: color ^= ATTR_UNDERLINE; break; case LINE_CMD_COLOR0: color = color & ATTR_UNDERLINE; break; case LINE_CMD_COLOR8: color &= 0xfff0; color |= 8|ATTR_COLOR8; break; case LINE_CMD_BLINK: color |= 0x80; break; case LINE_CMD_INDENT: /* set indentation position here - don't do it if we're too close to right border */ if (xpos < view->width-5) indent_pos = xpos; break; } continue; } if (xpos == view->width && sub != NULL && (last_space <= indent_pos || last_space <= 10) && !view->longword_noindent) { /* long word, remove the indentation from this line */ xpos -= sub->indent; sub->indent = 0; } if (xpos == view->width) { xpos = indent_pos; sub = g_new0(LINE_CACHE_SUB_REC, 1); if (last_space > indent_pos && last_space > 10) { /* go back to last space */ color = last_color; ptr = last_space_ptr; while (*ptr == ' ') ptr++; } else if (!view->longword_noindent) { /* long word, no indentation in next line */ xpos = 0; sub->continues = TRUE; } sub->start = ptr; sub->indent = xpos; sub->color = color; lines = g_slist_append(lines, sub); linecount++; last_space = 0; continue; } xpos++; if (*ptr++ == ' ') { last_space = xpos-1; last_space_ptr = ptr; last_color = color; } } rec = g_malloc(sizeof(LINE_CACHE_REC)-sizeof(LINE_CACHE_SUB_REC) + sizeof(LINE_CACHE_SUB_REC) * (linecount-1)); rec->last_access = time(NULL); rec->count = linecount; if (rec->count > 1) { for (pos = 0; lines != NULL; pos++) { memcpy(&rec->lines[pos], lines->data, sizeof(LINE_CACHE_SUB_REC)); g_free(lines->data); lines = g_slist_remove(lines, lines->data); } } g_hash_table_insert(view->cache->line_cache, line, rec); return rec; } static int view_line_draw(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line, int subline, int ypos, int max) { LINE_CACHE_REC *cache; const unsigned char *text, *text_newline; char *tmp; int xpos, color, drawcount, first; cache = textbuffer_view_get_line_cache(view, line); if (subline >= cache->count) return 0; xpos = color = drawcount = 0; first = TRUE; text_newline = text = subline == 0 ? line->text : cache->lines[subline-1].start; for (;;) { if (text == text_newline) { if (first) first = FALSE; else { ypos++; if (--max == 0) break; } if (subline > 0) { xpos = cache->lines[subline-1].indent; color = cache->lines[subline-1].color; } set_color(view->window, 0); wmove(view->window, ypos, 0); wclrtoeol(view->window); wmove(view->window, ypos, xpos); set_color(view->window, color); /* get the beginning of the next subline */ text_newline = subline == cache->count-1 ? NULL : cache->lines[subline].start; drawcount++; subline++; } if (*text == '\0') { /* command */ text++; if (*text == LINE_CMD_EOL || *text == LINE_CMD_FORMAT) break; if ((*text & 0x80) == 0) { /* set color */ color = (color & ATTR_UNDERLINE) | *text; } else if (*text == LINE_CMD_CONTINUE) { /* jump to next block */ memcpy(&tmp, text+1, sizeof(unsigned char *)); text = tmp; continue; } else switch (*text) { case LINE_CMD_UNDERLINE: color ^= ATTR_UNDERLINE; break; case LINE_CMD_COLOR0: color = color & ATTR_UNDERLINE; break; case LINE_CMD_COLOR8: color &= 0xfff0; color |= 8|ATTR_COLOR8; break; case LINE_CMD_BLINK: color |= 0x80; break; } set_color(view->window, color); text++; continue; } if ((*text & 127) >= 32) waddch(view->window, *text); else { /* low-ascii */ set_color(view->window, ATTR_REVERSE); waddch(view->window, (*text & 127)+'A'-1); set_color(view->window, color); } text++; } return drawcount; } /* Recalculate view's bottom line information - try to keep the original if possible */ static void textbuffer_view_init_bottom(TEXT_BUFFER_VIEW_REC *view) { GList *tmp; int linecount, total; if (view->empty_linecount == 0) { /* no empty lines in screen, no need to try to keep the old bottom startline */ view->bottom_startline = NULL; } total = 0; tmp = g_list_last(view->buffer->lines); for (; tmp != NULL; tmp = tmp->prev) { LINE_REC *line = tmp->data; linecount = view_get_linecount(view, line); if (tmp == view->bottom_startline) { /* keep the old one, make sure that subline is ok */ if (view->bottom_subline > linecount) view->bottom_subline = linecount; view->empty_linecount = view->height - total - (linecount-view->bottom_subline); return; } total += linecount; if (total >= view->height) { view->bottom_startline = tmp; view->bottom_subline = total - view->height; view->empty_linecount = 0; return; } } /* not enough lines so we must be at the beginning of the buffer */ view->bottom_startline = view->buffer->lines; view->bottom_subline = 0; view->empty_linecount = view->height - total; } static void textbuffer_view_init_ypos(TEXT_BUFFER_VIEW_REC *view) { GList *tmp; g_return_if_fail(view != NULL); view->ypos = -view->subline-1; for (tmp = view->startline; tmp != NULL; tmp = tmp->next) view->ypos += view_get_linecount(view, tmp->data); } /* Create new view. */ TEXT_BUFFER_VIEW_REC *textbuffer_view_create(TEXT_BUFFER_REC *buffer, int width, int height, int default_indent, int longword_noindent) { TEXT_BUFFER_VIEW_REC *view; g_return_val_if_fail(buffer != NULL, NULL); g_return_val_if_fail(width > 0, NULL); view = g_new0(TEXT_BUFFER_VIEW_REC, 1); view->buffer = buffer; view->siblings = textbuffer_get_views(buffer); view->width = width; view->height = height; view->default_indent = default_indent; view->longword_noindent = longword_noindent; view->cache = textbuffer_cache_get(view->siblings, width); textbuffer_view_init_bottom(view); view->startline = view->bottom_startline; view->subline = view->bottom_subline; view->bottom = TRUE; textbuffer_view_init_ypos(view); view->bookmarks = g_hash_table_new((GHashFunc) g_str_hash, (GCompareFunc) g_str_equal); views = g_slist_append(views, view); return view; } /* Destroy the view. */ void textbuffer_view_destroy(TEXT_BUFFER_VIEW_REC *view) { GSList *tmp; g_return_if_fail(view != NULL); views = g_slist_remove(views, view); if (view->siblings == NULL) { /* last view for textbuffer, destroy */ textbuffer_destroy(view->buffer); } else { /* remove ourself from siblings lists */ for (tmp = view->siblings; tmp != NULL; tmp = tmp->next) { TEXT_BUFFER_VIEW_REC *rec = tmp->data; rec->siblings = g_slist_remove(rec->siblings, view); } g_slist_free(view->siblings); } g_hash_table_foreach(view->bookmarks, (GHFunc) g_free, NULL); g_hash_table_destroy(view->bookmarks); textbuffer_cache_unref(view->cache); g_free(view); } /* Change the default indent position */ void textbuffer_view_set_default_indent(TEXT_BUFFER_VIEW_REC *view, int default_indent, int longword_noindent) { view->default_indent = default_indent; view->longword_noindent = longword_noindent; } static int view_get_linecount_all(TEXT_BUFFER_VIEW_REC *view, GList *lines) { int linecount; linecount = 0; while (lines != NULL) { linecount += view_get_linecount(view, lines->data); lines = lines->next; } return linecount; } static void view_draw(TEXT_BUFFER_VIEW_REC *view, GList *line, int subline, int ypos, int lines) { int linecount; while (line != NULL && lines > 0) { LINE_REC *rec = line->data; linecount = view_line_draw(view, rec, subline, ypos, lines); ypos += linecount; lines -= linecount; subline = 0; line = line->next; } /* clear the rest of the view */ while (lines > 0) { wmove(view->window, ypos, 0); wclrtoeol(view->window); ypos++; lines--; } } #define view_draw_top(view, lines) \ view_draw(view, (view)->startline, (view)->subline, 0, lines) static void view_draw_bottom(TEXT_BUFFER_VIEW_REC *view, int lines) { GList *line; int ypos, maxline, subline, linecount; maxline = view->height-lines; line = view->startline; ypos = -view->subline; subline = 0; while (line != NULL && ypos < maxline) { linecount = view_get_linecount(view, line->data); ypos += linecount; if (ypos > maxline) { subline = maxline-(ypos-linecount); break; } line = line->next; } view_draw(view, line, subline, maxline, lines); } /* Returns number of lines actually scrolled */ static int view_scroll(TEXT_BUFFER_VIEW_REC *view, GList **lines, int *subline, int scrollcount, int draw_nonclean) { int linecount, realcount, scroll_visible; if (*lines == NULL) return 0; /* scroll down */ scroll_visible = lines == &view->startline; realcount = -*subline; scrollcount += *subline; *subline = 0; while (scrollcount > 0) { linecount = view_get_linecount(view, (*lines)->data); if ((scroll_visible && *lines == view->bottom_startline) && (scrollcount >= view->bottom_subline)) { *subline = view->bottom_subline; realcount += view->bottom_subline; scrollcount = 0; break; } realcount += linecount; scrollcount -= linecount; if (scrollcount < 0) { realcount += scrollcount; *subline = linecount+scrollcount; scrollcount = 0; break; } *lines = (*lines)->next; } /* scroll up */ while (scrollcount < 0 && (*lines)->prev != NULL) { *lines = (*lines)->prev; linecount = view_get_linecount(view, (*lines)->data); realcount -= linecount; scrollcount += linecount; if (scrollcount > 0) { realcount += scrollcount; *subline = scrollcount; break; } } if (scroll_visible && realcount != 0 && view->window != NULL) { if (realcount <= -view->height || realcount >= view->height) { /* scrolled more than screenful, redraw the whole view */ textbuffer_view_redraw(view); } else { scrollok(view->window, TRUE); wscrl(view->window, realcount); scrollok(view->window, FALSE); if (draw_nonclean) { if (realcount < 0) view_draw_top(view, -realcount); else view_draw_bottom(view, realcount); } screen_refresh(view->window); } } return realcount >= 0 ? realcount : -realcount; } /* Resize the view. */ void textbuffer_view_resize(TEXT_BUFFER_VIEW_REC *view, int width, int height) { int linecount; g_return_if_fail(view != NULL); g_return_if_fail(width > 0); if (view->buffer->lines == NULL) return; if (view->width != width) { /* line cache needs to be recreated */ textbuffer_cache_unref(view->cache); view->cache = textbuffer_cache_get(view->siblings, width); } view->width = width; view->height = height; textbuffer_view_init_bottom(view); /* check that we didn't scroll lower than bottom startline.. */ if (g_list_find(view->bottom_startline->next, view->startline->data) != NULL) { view->startline = view->bottom_startline; view->subline = view->bottom_subline; } else if (view->startline == view->bottom_startline && view->subline > view->bottom_subline) { view->subline = view->bottom_subline; } else { /* make sure the subline is still in allowed range */ linecount = view_get_linecount(view, view->startline->data); if (view->subline > linecount) view->subline = linecount; } textbuffer_view_init_ypos(view); if (view->bottom && !view_is_bottom(view)) { /* we scrolled to far up, need to get down. go right over the empty lines if there's any */ view->startline = view->bottom_startline; view->subline = view->bottom_subline; if (view->empty_linecount > 0) { view_scroll(view, &view->startline, &view->subline, -view->empty_linecount, FALSE); } textbuffer_view_init_ypos(view); } view->bottom = view_is_bottom(view); if (view->bottom) { /* check if we left empty space at the bottom.. */ linecount = view_get_linecount_all(view, view->startline) - view->subline; if (view->empty_linecount < view->height-linecount) view->empty_linecount = view->height-linecount; } textbuffer_view_redraw(view); } /* Clear the view, don't actually remove any lines from buffer. */ void textbuffer_view_clear(TEXT_BUFFER_VIEW_REC *view) { g_return_if_fail(view != NULL); view->ypos = -1; view->bottom_startline = view->startline = g_list_last(view->buffer->lines); view->bottom_subline = view->subline = view->buffer->cur_line == NULL ? 0 : view_get_linecount(view, view->buffer->cur_line); view->empty_linecount = view->height; view->bottom = TRUE; textbuffer_view_redraw(view); } /* Scroll the view up/down */ void textbuffer_view_scroll(TEXT_BUFFER_VIEW_REC *view, int lines) { int count; g_return_if_fail(view != NULL); count = view_scroll(view, &view->startline, &view->subline, lines, TRUE); view->ypos += lines < 0 ? count : -count; view->bottom = view_is_bottom(view); if (view->window != NULL) screen_refresh(view->window); } /* Scroll to specified line */ void textbuffer_view_scroll_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line) { GList *tmp; g_return_if_fail(view != NULL); if (g_list_find(view->bottom_startline->next, line) != NULL) { view->startline = view->bottom_startline; view->subline = view->bottom_subline; } else { for (tmp = view->buffer->lines; tmp != NULL; tmp = tmp->next) { LINE_REC *rec = tmp->data; if (rec == line) { view->startline = tmp; view->subline = 0; break; } } } textbuffer_view_init_ypos(view); view->bottom = view_is_bottom(view); textbuffer_view_redraw(view); } /* Return line cache */ LINE_CACHE_REC *textbuffer_view_get_line_cache(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line) { LINE_CACHE_REC *cache; g_return_val_if_fail(view != NULL, NULL); g_return_val_if_fail(line != NULL, NULL); cache = g_hash_table_lookup(view->cache->line_cache, line); if (cache == NULL) cache = view_update_line_cache(view, line); else cache->last_access = time(NULL); return cache; } static void view_remove_cache(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line, unsigned char update_counter) { LINE_CACHE_REC *cache; if (view->cache->update_counter == update_counter) return; view->cache->update_counter = update_counter; cache = g_hash_table_lookup(view->cache->line_cache, line); if (cache != NULL) { g_free(cache); g_hash_table_remove(view->cache->line_cache, line); } } static void view_update_cache(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line, unsigned char update_counter) { view_remove_cache(view, line, update_counter); if (view->buffer->cur_line == line) view->cache->last_linecount = view_get_linecount(view, line); } static void view_insert_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line) { int linecount, ypos, subline; if (view->bottom_startline == NULL) { view->startline = view->bottom_startline = view->buffer->lines; } if (view->buffer->cur_line != line && g_list_find(view->bottom_startline, line) == NULL) return; linecount = view->cache->last_linecount; view->ypos += linecount; if (view->empty_linecount > 0) { view->empty_linecount -= linecount; if (view->empty_linecount >= 0) linecount = 0; else { linecount = -view->empty_linecount; view->empty_linecount = 0; } } if (linecount > 0) { view_scroll(view, &view->bottom_startline, &view->bottom_subline, linecount, FALSE); } if (view->bottom) { if (view->ypos >= view->height) { linecount = view->ypos-view->height+1; view_scroll(view, &view->startline, &view->subline, linecount, FALSE); view->ypos -= linecount; } if (view->window != NULL) { ypos = view->ypos+1 - view->cache->last_linecount; if (ypos >= 0) subline = 0; else { subline = -ypos; ypos = 0; } view_line_draw(view, line, subline, ypos, view->height - ypos); } } if (view->window != NULL) screen_refresh(view->window); } /* Update some line in the buffer which has been modified using textbuffer_append() or textbuffer_insert(). */ void textbuffer_view_insert_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line) { GSList *tmp; unsigned char update_counter; g_return_if_fail(view != NULL); g_return_if_fail(line != NULL); if (!view->buffer->last_eol) return; update_counter = view->cache->update_counter+1; view_update_cache(view, line, update_counter); view_insert_line(view, line); for (tmp = view->siblings; tmp != NULL; tmp = tmp->next) { TEXT_BUFFER_VIEW_REC *rec = tmp->data; view_update_cache(rec, line, update_counter); view_insert_line(rec, line); } } typedef struct { LINE_REC *remove_line; GSList *remove_list; } BOOKMARK_FIND_REC; static void bookmark_check_remove(char *key, LINE_REC *line, BOOKMARK_FIND_REC *rec) { if (line == rec->remove_line) rec->remove_list = g_slist_append(rec->remove_list, key); } static void view_bookmarks_check(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line) { BOOKMARK_FIND_REC rec; LINE_REC *newline; GSList *tmp; rec.remove_line = line; rec.remove_list = NULL; g_hash_table_foreach(view->bookmarks, (GHFunc) bookmark_check_remove, &rec); if (rec.remove_list != NULL) { GList *pos = g_list_find(view->buffer->lines, line); newline = pos == NULL || pos->prev == NULL ? NULL : (pos->next == NULL ? pos->prev->data : pos->next->data); for (tmp = rec.remove_list; tmp != NULL; tmp = tmp->next) { g_hash_table_remove(view->bookmarks, tmp->data); if (newline != NULL) { g_hash_table_insert(view->bookmarks, tmp->data, newline); } } g_slist_free(rec.remove_list); } } /* Return number of real lines `lines' list takes - stops counting when the height reaches the view height */ static int view_get_lines_height(TEXT_BUFFER_VIEW_REC *view, GList *lines, int subline, LINE_REC *skip_line) { int height, linecount; height = -subline; while (lines != NULL && height < view->height) { LINE_REC *line = lines->data; if (line != skip_line) { linecount = view_get_linecount(view, line); height += linecount; } lines = lines->next; } return height < view->height ? height : view->height; } static void view_remove_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line, int linecount) { int realcount, scroll; view_bookmarks_check(view, line); if (view->buffer->cur_line == line) { /* the last line is being removed */ LINE_REC *prevline; prevline = view->buffer->lines->data == line ? NULL : g_list_last(view->bottom_startline)->data; view->cache->last_linecount = prevline == NULL ? 0 : view_get_linecount(view, prevline); } if (line == view->buffer->lines->data) { /* first line in the buffer - this is the most commonly removed line.. */ if (view->bottom_startline->data == line) { /* very small scrollback.. */ view->bottom_startline = view->bottom_startline->next; view->bottom_subline = 0; } if (view->startline->data == line) { /* removing the first line in screen */ realcount = view_scroll(view, &view->startline, &view->subline, linecount, TRUE); view->ypos -= realcount; view->empty_linecount += linecount-realcount; } } else if (g_list_find(view->bottom_startline, line) != NULL) { realcount = view_scroll(view, &view->bottom_startline, &view->bottom_subline, -linecount, FALSE); if (view->bottom) { /* we're at the bottom, remove the same amount as from bottom_startline */ view_scroll(view, &view->startline, &view->subline, -linecount, TRUE); view->ypos -= linecount-realcount; } else { if (view->startline->data == line) { view->startline = view->startline->next != NULL ? view->startline->next : view->startline->prev; view->subline = 0; } scroll = view->height - view_get_lines_height(view, view->startline, view->subline, line); if (scroll > 0) { view_scroll(view, &view->startline, &view->subline, -scroll, TRUE); view->ypos -= scroll; } } view->empty_linecount += linecount-realcount; } view->bottom = view_is_bottom(view); if (view->window != NULL) screen_refresh(view->window); } /* Remove one line from buffer. */ void textbuffer_view_remove_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line) { GSList *tmp; unsigned char update_counter; int linecount; g_return_if_fail(view != NULL); g_return_if_fail(line != NULL); linecount = view_get_linecount(view, line); update_counter = view->cache->update_counter+1; view_remove_line(view, line, linecount); view_remove_cache(view, line, update_counter); for (tmp = view->siblings; tmp != NULL; tmp = tmp->next) { TEXT_BUFFER_VIEW_REC *rec = tmp->data; view_remove_line(rec, line, linecount); view_remove_cache(rec, line, update_counter); } textbuffer_remove(view->buffer, line); } /* Remove all lines from buffer. */ void textbuffer_view_remove_all_lines(TEXT_BUFFER_VIEW_REC *view) { GSList *tmp; g_return_if_fail(view != NULL); textbuffer_remove_all_lines(view->buffer); /* destroy line caches - note that you can't do simultaneously unrefs + cache_get()s or it will keep using the old caches */ textbuffer_cache_unref(view->cache); g_slist_foreach(view->siblings, (GFunc) textbuffer_cache_unref, NULL); /* recreate caches, clear screens */ view->cache = textbuffer_cache_get(view->siblings, view->width); textbuffer_view_clear(view); for (tmp = view->siblings; tmp != NULL; tmp = tmp->next) { TEXT_BUFFER_VIEW_REC *rec = tmp->data; rec->cache = textbuffer_cache_get(rec->siblings, rec->width); textbuffer_view_clear(rec); } } /* Set a bookmark in view */ void textbuffer_view_set_bookmark(TEXT_BUFFER_VIEW_REC *view, const char *name, LINE_REC *line) { gpointer key, value; g_return_if_fail(view != NULL); g_return_if_fail(name != NULL); if (g_hash_table_lookup_extended(view->bookmarks, name, &key, &value)) { g_hash_table_remove(view->bookmarks, key); g_free(key); } g_hash_table_insert(view->bookmarks, g_strdup(name), line); } /* Set a bookmark in view to the bottom line */ void textbuffer_view_set_bookmark_bottom(TEXT_BUFFER_VIEW_REC *view, const char *name) { LINE_REC *line; g_return_if_fail(view != NULL); g_return_if_fail(name != NULL); if (view->bottom_startline != NULL) { line = g_list_last(view->bottom_startline)->data; textbuffer_view_set_bookmark(view, name, line); } } /* Return the line for bookmark */ LINE_REC *textbuffer_view_get_bookmark(TEXT_BUFFER_VIEW_REC *view, const char *name) { g_return_val_if_fail(view != NULL, NULL); g_return_val_if_fail(name != NULL, NULL); return g_hash_table_lookup(view->bookmarks, name); } /* Specify window where the changes in view should be drawn, NULL disables it. */ void textbuffer_view_set_window(TEXT_BUFFER_VIEW_REC *view, WINDOW *window) { g_return_if_fail(view != NULL); if (view->window != window) { view->window = window; if (window != NULL) textbuffer_view_redraw(view); } } /* Redraw a view to window */ void textbuffer_view_redraw(TEXT_BUFFER_VIEW_REC *view) { g_return_if_fail(view != NULL); if (view->window != NULL) { werase(view->window); view_draw_top(view, view->height); screen_refresh(view->window); } } static int line_cache_check_remove(void *key, LINE_CACHE_REC *cache, time_t *now) { if (cache->last_access+LINE_CACHE_KEEP_TIME > *now) return FALSE; line_cache_destroy(NULL, cache); return TRUE; } static int sig_check_linecache(void) { GSList *tmp, *caches; time_t now; now = time(NULL); caches = NULL; for (tmp = views; tmp != NULL; tmp = tmp->next) { TEXT_BUFFER_VIEW_REC *rec = tmp->data; if (g_slist_find(caches, rec->cache) != NULL) continue; caches = g_slist_append(caches, rec->cache); g_hash_table_foreach_remove(rec->cache->line_cache, (GHRFunc) line_cache_check_remove, &now); } return 1; } void textbuffer_view_init(void) { linecache_tag = g_timeout_add(LINE_CACHE_CHECK_TIME, (GSourceFunc) sig_check_linecache, NULL); } void textbuffer_view_deinit(void) { g_source_remove(linecache_tag); }