summaryrefslogtreecommitdiffstats
path: root/src/fe-text/textbuffer-view.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/fe-text/textbuffer-view.c1546
1 files changed, 1546 insertions, 0 deletions
diff --git a/src/fe-text/textbuffer-view.c b/src/fe-text/textbuffer-view.c
new file mode 100644
index 0000000..2cc6ce6
--- /dev/null
+++ b/src/fe-text/textbuffer-view.c
@@ -0,0 +1,1546 @@
+/*
+ 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.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#define G_LOG_DOMAIN "TextBufferView"
+
+#include "module.h"
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/utf8.h>
+#include <irssi/src/fe-common/core/formats.h>
+#include <irssi/src/fe-text/textbuffer-formats.h>
+#include <irssi/src/fe-text/textbuffer-view.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_hidden(view, line) \
+ textbuffer_view_get_line_cache(view, line)->count
+
+#define view_line_is_hidden(view, line) \
+ (((line)->info.level & (view)->hidden_level) != 0)
+
+#define view_get_linecount(view, line) \
+ (view_line_is_hidden(view, line) ? 0 : view_get_linecount_hidden(view, line))
+
+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->line_text);
+ 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);
+}
+
+#define FGATTR (ATTR_NOCOLORS | ATTR_RESETFG | FG_MASK | ATTR_FGCOLOR24)
+#define BGATTR (ATTR_NOCOLORS | ATTR_RESETBG | BG_MASK | ATTR_BGCOLOR24)
+
+#ifdef TERM_TRUECOLOR
+static void unformat_24bit_line_color(const unsigned char **ptr, int off, int *flags, unsigned int *fg, unsigned int *bg)
+{
+ unsigned int color;
+ unsigned char rgbx[4];
+ unsigned int i;
+ for (i = 0; i < 4; ++i) {
+ if ((*ptr)[i + off] == '\0')
+ return;
+ rgbx[i] = (*ptr)[i + off];
+ }
+ rgbx[3] -= 0x20;
+ *ptr += 4;
+ for (i = 0; i < 3; ++i) {
+ if (rgbx[3] & (0x10 << i))
+ rgbx[i] -= 0x20;
+ }
+ color = rgbx[0] << 16 | rgbx[1] << 8 | rgbx[2];
+ if (rgbx[3] & 0x1) {
+ *flags = (*flags & FGATTR) | ATTR_BGCOLOR24;
+ *bg = color;
+ }
+ else {
+ *flags = (*flags & BGATTR) | ATTR_FGCOLOR24;
+ *fg = color;
+ }
+}
+#endif
+
+static inline unichar read_unichar(const unsigned char *data, const unsigned char **next, int *width)
+{
+ unichar chr = g_utf8_get_char_validated((const char *) data, -1);
+
+ if (chr & 0x80000000) {
+ chr = 0xfffd;
+ *next = data + 1;
+ *width = 1;
+ } else {
+ *next = (unsigned char *)g_utf8_next_char(data);
+ *width = unichar_isprint(chr) ? i_wcwidth(chr) : 1;
+ }
+ return chr;
+}
+
+static inline void unformat(const unsigned char **ptr, int *color, unsigned int *fg24,
+ unsigned int *bg24)
+{
+ switch (**ptr) {
+ case FORMAT_STYLE_BLINK:
+ *color ^= ATTR_BLINK;
+ break;
+ case FORMAT_STYLE_UNDERLINE:
+ *color ^= ATTR_UNDERLINE;
+ break;
+ case FORMAT_STYLE_BOLD:
+ *color ^= ATTR_BOLD;
+ break;
+ case FORMAT_STYLE_REVERSE:
+ *color ^= ATTR_REVERSE;
+ break;
+ case FORMAT_STYLE_ITALIC:
+ *color ^= ATTR_ITALIC;
+ break;
+ case FORMAT_STYLE_MONOSPACE:
+ /* *color ^= ATTR_MONOSPACE; */
+ break;
+ case FORMAT_STYLE_DEFAULTS:
+ *color = ATTR_RESET;
+ break;
+ case FORMAT_STYLE_CLRTOEOL:
+ break;
+#define SET_COLOR_EXT_FG_BITS(base, pc) \
+ *color &= ~ATTR_FGCOLOR24; \
+ *color = (*color & BGATTR) | (base + *pc - FORMAT_COLOR_NOCHANGE)
+#define SET_COLOR_EXT_BG_BITS(base, pc) \
+ *color &= ~ATTR_BGCOLOR24; \
+ *color = (*color & FGATTR) | ((base + *pc - FORMAT_COLOR_NOCHANGE) << BG_SHIFT)
+ case FORMAT_COLOR_EXT1:
+ SET_COLOR_EXT_FG_BITS(0x10, ++*ptr);
+ break;
+ case FORMAT_COLOR_EXT1_BG:
+ SET_COLOR_EXT_BG_BITS(0x10, ++*ptr);
+ break;
+ case FORMAT_COLOR_EXT2:
+ SET_COLOR_EXT_FG_BITS(0x60, ++*ptr);
+ break;
+ case FORMAT_COLOR_EXT2_BG:
+ SET_COLOR_EXT_BG_BITS(0x60, ++*ptr);
+ break;
+ case FORMAT_COLOR_EXT3:
+ SET_COLOR_EXT_FG_BITS(0xb0, ++*ptr);
+ break;
+ case FORMAT_COLOR_EXT3_BG:
+ SET_COLOR_EXT_BG_BITS(0xb0, ++*ptr);
+ break;
+#undef SET_COLOR_EXT_BG_BITS
+#undef SET_COLOR_EXT_FG_BITS
+#ifdef TERM_TRUECOLOR
+ case FORMAT_COLOR_24:
+ unformat_24bit_line_color(ptr, 1, color, fg24, bg24);
+ break;
+#endif
+ default:
+ if (**ptr != FORMAT_COLOR_NOCHANGE) {
+ if (**ptr == (unsigned char) 0xff) {
+ *color = (*color & BGATTR) | ATTR_RESETFG;
+ } else {
+ *color = (*color & BGATTR) | (((unsigned char) **ptr - '0') & 0xf);
+ }
+ }
+ if ((*ptr)[1] == '\0')
+ break;
+
+ (*ptr)++;
+ if (**ptr != FORMAT_COLOR_NOCHANGE) {
+ if (**ptr == (unsigned char) 0xff) {
+ *color = (*color & FGATTR) | ATTR_RESETBG;
+ } else {
+ *color = (*color & FGATTR) |
+ ((((unsigned char) **ptr - '0') & 0xf) << BG_SHIFT);
+ }
+ }
+ }
+ if (**ptr == '\0')
+ return;
+
+ (*ptr)++;
+}
+
+#define NEXT_CHAR_OR_BREAK(p) \
+ (p)++; \
+ if (*(p) == '\0') \
+ break
+
+static LINE_CACHE_REC *
+view_update_line_cache(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
+{
+ INDENT_FUNC indent_func;
+ LINE_CACHE_REC *rec;
+ LINE_CACHE_SUB_REC *sub;
+ GSList *lines;
+ char *line_text;
+ const unsigned char *ptr, *next_ptr, *last_space_ptr;
+ int xpos, pos, indent_pos, last_space, last_color, color, linecount;
+ unsigned int last_bg24, last_fg24, bg24, fg24;
+ int char_width;
+
+ color = ATTR_RESETFG | ATTR_RESETBG;
+ xpos = 0; indent_pos = view->default_indent;
+ last_space = last_color = 0; last_space_ptr = NULL; sub = NULL;
+ bg24 = fg24 = last_bg24 = last_fg24 = UINT_MAX;
+
+ indent_func = view->default_indent_func;
+ linecount = 1;
+ lines = NULL;
+
+ line_text = textbuffer_line_get_text(view->buffer, line, FALSE);
+ if (line_text != NULL) {
+ for (ptr = (unsigned char *) line_text;;) {
+ if (*ptr == '\0')
+ break;
+
+ if (*ptr == '\n') {
+ /* newline */
+ xpos = 0;
+ last_space = 0;
+
+ sub = g_new0(LINE_CACHE_SUB_REC, 1);
+
+ sub->start = ++ptr;
+ sub->color = color;
+#ifdef TERM_TRUECOLOR
+ sub->fg24 = fg24;
+ sub->bg24 = bg24;
+#endif
+
+ lines = g_slist_append(lines, sub);
+ linecount++;
+
+ continue;
+ }
+
+ if (*ptr == 4) {
+ /* format */
+ NEXT_CHAR_OR_BREAK(ptr);
+
+ if (*ptr == FORMAT_STYLE_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;
+ ptr++;
+ } else {
+ unformat(&ptr, &color, &fg24, &bg24);
+ }
+ continue;
+ }
+
+ if (!view->utf8) {
+ /* MH */
+ if (term_type != TERM_TYPE_BIG5 || ptr[1] == '\0' ||
+ !is_big5(ptr[0], ptr[1]))
+ char_width = 1;
+ else
+ char_width = 2;
+ next_ptr = ptr + char_width;
+ } else {
+ read_unichar(ptr, &next_ptr, &char_width);
+ }
+
+ if (xpos + char_width > 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;
+ sub->indent_func = NULL;
+ }
+
+ if (xpos + char_width > view->width) {
+ xpos =
+ indent_func == NULL ? indent_pos : indent_func(view, line, -1);
+
+ sub = g_new0(LINE_CACHE_SUB_REC, 1);
+ if (last_space > indent_pos && last_space > 10) {
+ /* go back to last space */
+ color = last_color;
+ fg24 = last_fg24;
+ bg24 = last_bg24;
+ 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->indent_func = indent_func;
+ sub->color = color;
+#ifdef TERM_TRUECOLOR
+ sub->fg24 = fg24;
+ sub->bg24 = bg24;
+#endif
+
+ lines = g_slist_append(lines, sub);
+ linecount++;
+
+ last_space = 0;
+ continue;
+ }
+
+ if (view->break_wide && char_width > 1) {
+ last_space = xpos;
+ last_space_ptr = next_ptr;
+ last_color = color;
+ last_fg24 = fg24;
+ last_bg24 = bg24;
+ } else if (*ptr == ' ') {
+ last_space = xpos;
+ last_space_ptr = ptr;
+ last_color = color;
+ last_fg24 = fg24;
+ last_bg24 = bg24;
+ }
+
+ xpos += char_width;
+ ptr = next_ptr;
+ }
+ }
+
+ rec = g_malloc(sizeof(LINE_CACHE_REC)-sizeof(LINE_CACHE_SUB_REC) +
+ sizeof(LINE_CACHE_SUB_REC) * (linecount-1));
+ rec->last_access = time(NULL);
+ if (line_text == NULL) {
+ linecount = 0;
+ }
+ rec->count = linecount;
+ rec->line_text = line_text;
+
+ if (rec->count > 1) {
+ for (pos = 0; lines != NULL; pos++) {
+ LINE_CACHE_SUB_REC *data = lines->data;
+
+ memcpy(&rec->lines[pos], data, sizeof(LINE_CACHE_SUB_REC));
+
+ lines = g_slist_remove(lines, data);
+ g_free(data);
+ }
+ }
+
+ g_hash_table_insert(view->cache->line_cache, line, rec);
+ return rec;
+}
+
+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) {
+ line_cache_destroy(NULL, 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_get_linecount(view, line);
+}
+
+void textbuffer_view_reset_cache(TEXT_BUFFER_VIEW_REC *view)
+{
+ GSList *tmp;
+
+ /* 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);
+
+ view->cache = textbuffer_cache_get(view->siblings, view->width);
+ 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);
+ }
+}
+
+static int view_line_draw(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
+ int subline, int ypos, int max)
+{
+ INDENT_FUNC indent_func;
+ LINE_CACHE_REC *cache;
+ const unsigned char *text, *end, *text_newline;
+ unichar chr;
+ int xpos, color, drawcount, first, need_move, need_clrtoeol, char_width;
+ unsigned int fg24, bg24;
+ fg24 = bg24 = UINT_MAX;
+
+ if (view->dirty) /* don't bother drawing anything - redraw is coming */
+ return 0;
+
+ cache = textbuffer_view_get_line_cache(view, line);
+ if (subline >= cache->count)
+ return 0;
+
+ color = ATTR_RESET;
+ need_move = TRUE; need_clrtoeol = FALSE;
+ xpos = drawcount = 0; first = TRUE;
+ text_newline = text =
+ subline == 0 ? (unsigned char *) cache->line_text : cache->lines[subline - 1].start;
+
+ for (;;) {
+ if (text == text_newline) {
+ if (need_clrtoeol && xpos < view->width + (view->width == term_width ? 0 : 1)) {
+ term_set_color(view->window, ATTR_RESET);
+ term_window_clrtoeol(view->window, ypos);
+ }
+
+ if (first)
+ first = FALSE;
+ else {
+ ypos++;
+ if (--max == 0)
+ break;
+ }
+
+ if (subline > 0) {
+ /* continuing previous line - indent it */
+ indent_func = cache->lines[subline-1].indent_func;
+ if (indent_func == NULL || cache->lines[subline-1].continues)
+ xpos = cache->lines[subline-1].indent;
+ color = cache->lines[subline-1].color;
+#ifdef TERM_TRUECOLOR
+ fg24 = cache->lines[subline-1].fg24;
+ bg24 = cache->lines[subline-1].bg24;
+#endif
+ } else {
+ indent_func = NULL;
+ }
+
+ if (xpos == 0 && (indent_func == NULL || cache->lines[subline-1].continues))
+ need_clrtoeol = TRUE;
+ else {
+ /* line was indented - need to clear the
+ indented area first */
+ term_set_color(view->window, ATTR_RESET);
+ term_move(view->window, 0, ypos);
+ term_window_clrtoeol(view->window, ypos);
+
+ if (indent_func != NULL)
+ xpos = indent_func(view, line, ypos);
+ }
+
+ if (need_move || xpos > 0)
+ term_move(view->window, xpos, ypos);
+
+ term_set_color2(view->window, color, fg24, bg24);
+
+ if (subline == cache->count-1) {
+ text_newline = NULL;
+ need_move = FALSE;
+ } else {
+ /* get the beginning of the next subline */
+ text_newline = cache->lines[subline].start;
+ if (view->width == term_width) {
+ /* ensure that links / long words are not broken */
+ need_move = !cache->lines[subline].continues;
+ } else {
+ /* we cannot use the need_move
+ optimisation unless the split spans
+ the whole width */
+ need_move = TRUE;
+ }
+ }
+ drawcount++;
+ subline++;
+ }
+
+ if (*text == '\n') {
+ /* newline */
+ NEXT_CHAR_OR_BREAK(text);
+ continue;
+ }
+
+ if (*text == 0) {
+ break;
+ }
+
+ if (*text == 4) {
+ /* format */
+ NEXT_CHAR_OR_BREAK(text);
+
+ if (*text == FORMAT_STYLE_INDENT) {
+ /* ??? */
+ NEXT_CHAR_OR_BREAK(text);
+ } else {
+ unformat(&text, &color, &fg24, &bg24);
+ term_set_color2(view->window, color, fg24, bg24);
+ if (*text == 0)
+ break;
+ }
+ continue;
+ }
+
+ if (view->utf8) {
+ chr = read_unichar(text, &end, &char_width);
+ } else {
+ chr = *text;
+ end = text;
+ if (term_type == TERM_TYPE_BIG5 &&
+ is_big5(end[0], end[1]))
+ char_width = 2;
+ else
+ char_width = 1;
+ end += char_width;
+ }
+
+ xpos += char_width;
+ if (xpos <= view->width) {
+ if (unichar_isprint(chr)) {
+ if (view->utf8)
+ term_add_unichar(view->window, chr);
+ else
+ for (; text < end; text++)
+ term_addch(view->window, *text);
+ } else {
+ /* low-ascii */
+ term_set_color(view->window, ATTR_RESET|ATTR_REVERSE);
+ term_addch(view->window, (chr & 127)+'A'-1);
+ term_set_color2(view->window, color, fg24, bg24);
+ }
+ }
+ text = end;
+ }
+
+ if (need_clrtoeol && xpos < view->width + (view->width == term_width ? 0 : 1)) {
+ term_set_color(view->window, ATTR_RESET);
+ term_window_clrtoeol(view->window, ypos);
+ }
+
+ 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)
+{
+ LINE_REC *line;
+ 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;
+ line = textbuffer_line_last(view->buffer);
+ for (; line != NULL; line = line->prev) {
+ if (view_line_is_hidden(view, line))
+ continue;
+
+ linecount = view_get_linecount(view, line);
+ if (line == 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 = line;
+ 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->first_line;
+ view->bottom_subline = 0;
+ view->empty_linecount = view->height - total;
+}
+
+static void textbuffer_view_init_ypos(TEXT_BUFFER_VIEW_REC *view)
+{
+ LINE_REC *line;
+
+ g_return_if_fail(view != NULL);
+
+ view->ypos = -view->subline-1;
+ for (line = view->startline; line != NULL; line = line->next)
+ view->ypos += view_get_linecount(view, line);
+}
+
+/* Create new view. */
+TEXT_BUFFER_VIEW_REC *textbuffer_view_create(TEXT_BUFFER_REC *buffer,
+ int width, int height,
+ int scroll, int utf8)
+{
+ 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->scroll = scroll;
+ view->utf8 = utf8;
+
+ 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;
+
+ view->hidden_level = 0;
+
+ 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,
+ INDENT_FUNC indent_func)
+{
+ if (default_indent != -1)
+ view->default_indent = default_indent;
+ if (longword_noindent != -1)
+ view->longword_noindent = longword_noindent;
+
+ view->default_indent_func = indent_func;
+}
+
+/* Enable breaking of wide chars */
+void textbuffer_view_set_break_wide(TEXT_BUFFER_VIEW_REC *view,
+ gboolean break_wide)
+{
+ if (view->break_wide != break_wide) {
+ view->break_wide = break_wide;
+ textbuffer_view_reset_cache(view);
+ }
+}
+
+static void view_unregister_indent_func(TEXT_BUFFER_VIEW_REC *view,
+ INDENT_FUNC indent_func)
+{
+ if (view->default_indent_func == indent_func)
+ view->default_indent_func = NULL;
+
+ /* recreate cache so it won't contain references
+ to the indent function */
+ textbuffer_view_reset_cache(view);
+}
+
+void textbuffer_views_unregister_indent_func(INDENT_FUNC indent_func)
+{
+ g_slist_foreach(views, (GFunc) view_unregister_indent_func,
+ (void *) indent_func);
+}
+
+void textbuffer_view_set_scroll(TEXT_BUFFER_VIEW_REC *view, int scroll)
+{
+ view->scroll = scroll;
+}
+
+void textbuffer_view_set_utf8(TEXT_BUFFER_VIEW_REC *view, int utf8)
+{
+ view->utf8 = utf8;
+}
+
+static int view_get_linecount_all(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
+{
+ int linecount;
+
+ linecount = 0;
+ while (line != NULL) {
+ linecount += view_get_linecount(view, line);
+ line = line->next;
+ }
+
+ return linecount;
+}
+
+static void view_draw(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
+ int subline, int ypos, int lines, int fill_bottom)
+{
+ int linecount;
+
+ if (view->dirty) /* don't bother drawing anything - redraw is coming */
+ return;
+
+ while (line != NULL && lines > 0) {
+ if (!view_line_is_hidden(view, line)) {
+ linecount = view_line_draw(view, line, subline, ypos, lines);
+ ypos += linecount; lines -= linecount;
+ }
+
+ subline = 0;
+ line = line->next;
+ }
+
+ if (fill_bottom) {
+ /* clear the rest of the view */
+ term_set_color(view->window, ATTR_RESET);
+ while (lines > 0) {
+ term_move(view->window, 0, ypos);
+ term_window_clrtoeol(view->window, ypos);
+ ypos++; lines--;
+ }
+ }
+}
+
+#define view_draw_top(view, lines, fill_bottom) \
+ view_draw(view, (view)->startline, (view)->subline, \
+ 0, lines, fill_bottom)
+
+static void view_draw_bottom(TEXT_BUFFER_VIEW_REC *view, int lines)
+{
+ LINE_REC *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);
+ ypos += linecount;
+ if (ypos > maxline) {
+ subline = maxline-(ypos-linecount);
+ break;
+ }
+ line = line->next;
+ }
+
+ view_draw(view, line, subline, maxline, lines, TRUE);
+}
+
+/* lines: this pointer is scrolled by scrollcount screen lines
+ subline: this pointer contains the subline position
+ scrollcount: the number of lines to scroll down (negative: up)
+ draw_nonclean: whether to redraw the screen now
+
+ Returns number of lines actually scrolled */
+static int view_scroll(TEXT_BUFFER_VIEW_REC *view, LINE_REC **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);
+
+ 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;
+ }
+
+ if ((*lines)->next == NULL)
+ break;
+
+ *lines = (*lines)->next;
+ }
+
+ /* scroll up */
+ while (scrollcount < 0 && (*lines)->prev != NULL) {
+ *lines = (*lines)->prev;
+ linecount = view_get_linecount(view, *lines);
+
+ 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 {
+ if (view->width == term_width) {
+ /* we can try to use vt100 scroll regions */
+ term_set_color(view->window, ATTR_RESET);
+ term_window_scroll(view->window, realcount);
+
+ if (draw_nonclean) {
+ if (realcount < 0)
+ view_draw_top(view, -realcount, TRUE);
+ else
+ view_draw_bottom(view, realcount);
+ }
+
+ term_refresh(view->window);
+ } else {
+ /* do not bother with vt400 scroll
+ rectangles for now, redraw the
+ whole view */
+ view->dirty = TRUE;
+ irssi_set_dirty();
+ }
+ }
+ }
+
+ 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->width != width) {
+ /* line cache needs to be recreated */
+ textbuffer_cache_unref(view->cache);
+ view->cache = textbuffer_cache_get(view->siblings, width);
+ }
+
+ view->width = width > 10 ? width : 10;
+ view->height = height > 1 ? height : 1;
+
+ if (view->buffer->first_line == NULL) {
+ view->empty_linecount = height;
+ return;
+ }
+
+ textbuffer_view_init_bottom(view);
+
+ /* check that we didn't scroll lower than bottom startline.. */
+ if (textbuffer_line_exists_after(view->bottom_startline->next,
+ view->startline)) {
+ 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 if (view->startline != NULL) {
+ /* make sure the subline is still in allowed range */
+ linecount = view_get_linecount(view, view->startline);
+ if (view->subline > linecount)
+ view->subline = linecount;
+ } else {
+ /* we don't have a startline. still under construction? */
+ view->subline = 0;
+ }
+
+ 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;
+ view->more_text = FALSE;
+ }
+
+ view->dirty = TRUE;
+}
+
+/* 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 =
+ textbuffer_line_last(view->buffer);
+ 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;
+ view->more_text = FALSE;
+
+ textbuffer_view_redraw(view);
+}
+
+/* Scroll the view up/down */
+void textbuffer_view_scroll(TEXT_BUFFER_VIEW_REC *view, int lines)
+{
+ int count, ypos;
+
+ g_return_if_fail(view != NULL);
+
+ count = view_scroll(view, &view->startline, &view->subline, lines, TRUE);
+
+ ypos = view->ypos + (lines < 0 ? count : -count);
+ textbuffer_view_init_ypos(view);
+ if (ypos != view->ypos)
+ textbuffer_view_resize(view, view->width, view->height);
+
+ view->bottom = view_is_bottom(view);
+ if (view->bottom) view->more_text = FALSE;
+
+ if (view->window != NULL)
+ term_refresh(view->window);
+}
+
+/* Scroll to specified line */
+void textbuffer_view_scroll_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
+{
+ g_return_if_fail(view != NULL);
+
+ if (textbuffer_line_exists_after(view->bottom_startline->next, line)) {
+ view->startline = view->bottom_startline;
+ view->subline = view->bottom_subline;
+ } else {
+ view->startline = line;
+ view->subline = 0;
+ }
+
+ textbuffer_view_init_ypos(view);
+ view->bottom = view_is_bottom(view);
+ if (view->bottom) view->more_text = FALSE;
+
+ 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_assert(view != NULL);
+ g_assert(line != 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_insert_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
+{
+ int linecount, ypos, subline;
+
+ if (!view->bottom)
+ view->more_text = TRUE;
+
+ if (view->bottom_startline == NULL) {
+ view->startline = view->bottom_startline =
+ view->buffer->first_line;
+ }
+
+ if (view->buffer->cur_line != line &&
+ !textbuffer_line_exists_after(view->bottom_startline, line))
+ return;
+
+ linecount = view_get_linecount(view, line);
+ 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->scroll && view->ypos >= view->height) {
+ linecount = view->ypos-view->height+1;
+ view_scroll(view, &view->startline,
+ &view->subline, linecount, FALSE);
+ view->ypos -= linecount;
+ } else {
+ view->bottom = view_is_bottom(view);
+ }
+
+ if (view->window != NULL && !view_line_is_hidden(view, line)) {
+ ypos = view->ypos+1 - view_get_linecount(view, line);
+ if (ypos >= 0)
+ subline = 0;
+ else {
+ subline = -ypos;
+ ypos = 0;
+ }
+ if (ypos < view->height) {
+ view_line_draw(view, line, subline, ypos,
+ view->height - ypos);
+ }
+ }
+ }
+
+ if (view->window != NULL && !view_line_is_hidden(view, line))
+ term_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;
+ 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) {
+ for (tmp = rec.remove_list; tmp != NULL; tmp = tmp->next) {
+ g_hash_table_remove(view->bookmarks, tmp->data);
+ g_free(tmp->data);
+ }
+ 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,
+ LINE_REC *line, int subline,
+ LINE_REC *skip_line)
+{
+ int height, linecount;
+
+ height = -subline;
+ while (line != NULL && height < view->height) {
+ if (line != skip_line) {
+ linecount = view_get_linecount(view, line);
+ height += linecount;
+ }
+ line = line->next;
+ }
+
+ return height < view->height ? height : view->height;
+}
+
+/* line: line to remove
+ linecount: linecount of that line, to be offset when the line was in/below view
+
+ scroll the window maintaining the startline while removing line
+ if startline is removed, make the previous line the new startline
+*/
+static void view_remove_line_update_startline(TEXT_BUFFER_VIEW_REC *view,
+ LINE_REC *line, int linecount)
+{
+ int scroll;
+
+ if (view->startline == line) {
+ view->startline = view->startline->prev != NULL ?
+ view->startline->prev : view->startline->next;
+ view->subline = 0;
+ } else {
+ scroll = view->height -
+ view_get_lines_height(view, view->startline,
+ view->subline, line);
+ if (scroll > 0) {
+ view_scroll(view, &view->startline,
+ &view->subline, -scroll, FALSE);
+ }
+ }
+
+ /* FIXME: this is slow and unnecessary, but it's easy and
+ really works :) */
+ textbuffer_view_init_ypos(view);
+ if (textbuffer_line_exists_after(view->startline, line))
+ view->ypos -= linecount;
+}
+
+static void view_remove_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
+ int linecount)
+{
+ int realcount;
+
+ view_bookmarks_check(view, line);
+
+ if (view->buffer->cur_line == line) {
+ /* the last line is being removed */
+ LINE_REC *prevline;
+
+ prevline = view->buffer->first_line == line ?
+ NULL :
+ textbuffer_line_last(view->buffer)->prev;
+ if (prevline != NULL)
+ view_get_linecount(view, prevline);
+ }
+
+ /* first line in the buffer - this is the most commonly
+ removed line.. */
+ if (view->buffer->first_line == line) {
+ if (view->bottom_startline == line) {
+ /* very small scrollback.. */
+ view->bottom_startline = view->bottom_startline->next;
+ view->bottom_subline = 0;
+ }
+
+ if (view->startline == line) {
+ /* removing the first line in screen */
+ int is_last = view->startline->next == NULL;
+
+ realcount = view_scroll(view, &view->startline,
+ &view->subline,
+ linecount, FALSE);
+ view->ypos -= realcount;
+ view->empty_linecount += linecount-realcount;
+ if (is_last == 1)
+ view->startline = NULL;
+ }
+
+ if (view->startline == line) {
+ view->startline = line->next;
+ view->subline = 0;
+ }
+ } else {
+ if (textbuffer_line_exists_after(view->bottom_startline,
+ line)) {
+ realcount = view_scroll(view, &view->bottom_startline,
+ &view->bottom_subline,
+ -linecount, FALSE);
+ view->empty_linecount += linecount-realcount;
+ }
+
+ if (view->bottom_startline == line) {
+ view->bottom_startline = view->bottom_startline->next;
+ view->bottom_subline = 0;
+ }
+
+ if (textbuffer_line_exists_after(view->startline,
+ line)) {
+ view_remove_line_update_startline(view, line,
+ linecount);
+ }
+ }
+
+ view->bottom = view_is_bottom(view);
+ if (view->bottom) view->more_text = FALSE;
+ if (view->window != NULL)
+ term_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);
+
+ signal_emit("gui textbuffer line removed", 3, view, line, line->prev);
+
+ 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);
+ if (view->bottom_startline == NULL) {
+ /* We may have removed the bottom_startline, make sure
+ that scroll doesn't get stuck */
+ textbuffer_view_init_bottom(view);
+ }
+}
+
+void textbuffer_view_remove_lines_by_level(TEXT_BUFFER_VIEW_REC *view, int level)
+{
+ LINE_REC *line, *next;
+
+ term_refresh_freeze();
+ line = textbuffer_view_get_lines(view);
+
+ while (line != NULL) {
+ next = line->next;
+
+ if (line->info.level & level)
+ textbuffer_view_remove_line(view, line);
+ line = next;
+ }
+ textbuffer_view_redraw(view);
+ term_refresh_thaw();
+}
+
+/* Remove all lines from buffer. */
+void textbuffer_view_remove_all_lines(TEXT_BUFFER_VIEW_REC *view)
+{
+ g_return_if_fail(view != NULL);
+
+ textbuffer_remove_all_lines(view->buffer);
+
+ g_hash_table_foreach(view->bookmarks, (GHFunc) g_free, NULL);
+ g_hash_table_remove_all(view->bookmarks);
+
+ textbuffer_view_reset_cache(view);
+ textbuffer_view_clear(view);
+ g_slist_foreach(view->siblings, (GFunc) textbuffer_view_clear, NULL);
+}
+
+/* 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 = textbuffer_line_last(view->buffer);
+ 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);
+}
+
+void textbuffer_view_set_hidden_level(TEXT_BUFFER_VIEW_REC *view, int level)
+{
+ g_return_if_fail(view != NULL);
+
+ if (view->hidden_level != level) {
+ if (view->empty_linecount > 0 && view->startline != NULL) {
+ int old_height, new_height;
+ LINE_REC *hidden_start;
+
+ hidden_start = view->startline;
+ while (hidden_start->prev != NULL && view_line_is_hidden(view, hidden_start->prev)) {
+ hidden_start = hidden_start->prev;
+ }
+
+ old_height = view_get_lines_height(view, hidden_start, view->subline, NULL);
+ view->hidden_level = level;
+ new_height = view_get_lines_height(view, hidden_start, view->subline, NULL);
+
+ view->empty_linecount -= new_height - old_height;
+
+ if (view->empty_linecount < 0)
+ view->empty_linecount = 0;
+ else if (view->empty_linecount > view->height)
+ view->empty_linecount = view->height;
+ } else {
+ view->hidden_level = level;
+ }
+ textbuffer_view_resize(view, view->width, view->height);
+ }
+}
+
+/* Specify window where the changes in view should be drawn,
+ NULL disables it. */
+void textbuffer_view_set_window(TEXT_BUFFER_VIEW_REC *view,
+ TERM_WINDOW *window)
+{
+ g_return_if_fail(view != NULL);
+
+ if (view->window != window) {
+ view->window = window;
+ if (window != NULL) {
+ textbuffer_view_resize(view, view->width, view->height);
+ view->dirty = TRUE;
+ }
+ }
+}
+
+/* Redraw a view to window */
+void textbuffer_view_redraw(TEXT_BUFFER_VIEW_REC *view)
+{
+ g_return_if_fail(view != NULL);
+
+ if (view->window != NULL) {
+ view->dirty = FALSE;
+ view_draw_top(view, view->height, TRUE);
+ term_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 (rec->window != NULL) {
+ /* keep visible lines mapped */
+ view_get_lines_height(rec, rec->startline, rec->subline, NULL);
+ }
+
+ 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);
+ }
+
+ g_slist_free(caches);
+ 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);
+}