1
0
mirror of https://github.com/irssi/irssi.git synced 2024-11-03 04:27:19 -05:00
irssi/src/fe-text/textbuffer-view.c

1241 lines
32 KiB
C
Raw Normal View History

/*
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"
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);
}
#define FGATTR (ATTR_NOCOLORS | ATTR_RESETBG | ATTR_BLINK | 0xf0)
#define BGATTR (ATTR_NOCOLORS | ATTR_RESETFG | ATTR_BOLD | 0x0f)
static void update_cmd_color(unsigned char cmd, int *color)
{
if ((cmd & 0x80) == 0) {
if (cmd & LINE_COLOR_BG) {
/* set background color */
*color &= BGATTR;
if ((cmd & LINE_COLOR_DEFAULT) == 0)
*color |= (cmd & 0x0f) << 4;
else {
*color |= ATTR_RESETBG;
if (cmd & LINE_COLOR_BLINK)
*color |= ATTR_BLINK;
}
} else {
/* set foreground color */
*color &= FGATTR;
if ((cmd & LINE_COLOR_DEFAULT) == 0)
*color |= cmd & 0x0f;
else {
*color |= ATTR_RESETFG;
if (cmd & LINE_COLOR_BOLD)
*color |= ATTR_BOLD;
}
}
} else switch (cmd) {
case LINE_CMD_UNDERLINE:
*color ^= ATTR_UNDERLINE;
break;
case LINE_CMD_REVERSE:
*color ^= ATTR_REVERSE;
break;
case LINE_CMD_COLOR0:
*color &= BGATTR;
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;
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);
color = ATTR_RESETFG | ATTR_RESETBG;
xpos = 0; indent_pos = view->default_indent;
last_space = last_color = 0; last_space_ptr = NULL; sub = NULL;
indent_func = view->default_indent_func;
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 == 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;
} else if (cmd == LINE_CMD_INDENT_FUNC) {
memcpy(&indent_func, ptr, sizeof(INDENT_FUNC));
ptr += sizeof(INDENT_FUNC);
if (indent_func == NULL)
indent_func = view->default_indent_func;
} else
update_cmd_color(cmd, &color);
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_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;
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;
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 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_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, *text_newline;
char *tmp;
int xpos, color, drawcount, first, need_move, need_clrtoeol;
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;
need_move = TRUE; need_clrtoeol = FALSE;
xpos = color = drawcount = 0; first = TRUE;
text_newline = text =
subline == 0 ? line->text : cache->lines[subline-1].start;
for (;;) {
if (text == text_newline) {
if (need_clrtoeol && need_move) {
term_set_color(view->window, ATTR_RESET);
term_clrtoeol(view->window);
}
if (first)
first = FALSE;
else {
ypos++;
if (--max == 0)
break;
}
if (subline > 0) {
indent_func = cache->lines[subline-1].indent_func;
xpos = indent_func != NULL ?
indent_func(view, line, ypos) :
cache->lines[subline-1].indent;
color = cache->lines[subline-1].color;
}
if (need_move || xpos > 0) {
/* first clear the line */
if (xpos == 0)
need_clrtoeol = TRUE;
else {
term_set_color(view->window, ATTR_RESET);
term_move(view->window, 0, ypos);
term_clrtoeol(view->window);
}
term_move(view->window, xpos, ypos);
}
term_set_color(view->window, color);
/* get the beginning of the next subline */
text_newline = subline == cache->count-1 ? NULL :
cache->lines[subline].start;
need_move = !cache->lines[subline].continues;
drawcount++;
subline++;
}
if (*text == '\0') {
/* command */
text++;
if (*text == LINE_CMD_EOL || *text == LINE_CMD_FORMAT)
break;
if (*text == LINE_CMD_CONTINUE) {
/* jump to next block */
memcpy(&tmp, text+1, sizeof(unsigned char *));
text = tmp;
continue;
} else if (*text == LINE_CMD_INDENT_FUNC) {
text += sizeof(INDENT_FUNC);
} else {
update_cmd_color(*text, &color);
term_set_color(view->window, color);
}
text++;
continue;
}
if ((*text & 127) >= 32)
term_addch(view->window, *text);
else {
/* low-ascii */
term_set_color(view->window, ATTR_RESET|ATTR_REVERSE);
term_addch(view->window, (*text & 127)+'A'-1);
term_set_color(view->window, color);
}
text++;
}
if (need_clrtoeol) {
term_set_color(view->window, ATTR_RESET);
term_clrtoeol(view->window);
}
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) {
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)
{
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->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,
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;
}
static void view_unregister_indent_func(TEXT_BUFFER_VIEW_REC *view,
INDENT_FUNC indent_func)
{
INDENT_FUNC func;
LINE_REC *line;
const unsigned char *text, *tmp;
if (view->default_indent_func == indent_func)
view->default_indent_func = NULL;
/* recreate cache so it won't contain references
to the indent function */
view_reset_cache(view);
view->cache = textbuffer_cache_get(view->siblings, view->width);
/* remove all references to the indent function from buffer */
line = view->buffer->first_line;
while (line != NULL) {
text = line->text;
for (text = line->text;; text++) {
if (*text != '\0')
continue;
text++;
if (*text == LINE_CMD_EOL)
break;
if (*text == LINE_CMD_INDENT_FUNC) {
text++;
memcpy(&func, text, sizeof(INDENT_FUNC));
if (func == indent_func)
memset(&func, 0, sizeof(INDENT_FUNC));
text += sizeof(INDENT_FUNC);
} else if (*text == LINE_CMD_CONTINUE) {
memcpy(&tmp, text+1, sizeof(char *));
text = tmp-1;
}
}
line = line->next;
}
}
void textbuffer_views_unregister_indent_func(INDENT_FUNC indent_func)
{
g_slist_foreach(views, (GFunc) view_unregister_indent_func,
indent_func);
}
void textbuffer_view_set_scroll(TEXT_BUFFER_VIEW_REC *view, int scroll)
{
view->scroll = scroll;
}
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) {
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_clrtoeol(view->window);
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);
}
/* 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;
}
*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 {
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);
}
}
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 {
/* make sure the subline is still in allowed range */
linecount = view_get_linecount(view, view->startline);
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;
}
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;
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)
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);
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_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->first_line;
}
if (view->buffer->cur_line != line &&
!textbuffer_line_exists_after(view->bottom_startline, line))
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->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) {
ypos = view->ypos+1 - view->cache->last_linecount;
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)
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;
LINE_REC *new_line;
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) {
new_line = line->prev == NULL ? NULL :
(line->next == NULL ? line->prev : line->next);
for (tmp = rec.remove_list; tmp != NULL; tmp = tmp->next) {
g_hash_table_remove(view->bookmarks, tmp->data);
if (new_line != NULL) {
g_hash_table_insert(view->bookmarks,
tmp->data, new_line);
}
}
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;
}
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);
view->cache->last_linecount = prevline == NULL ? 0 :
view_get_linecount(view, prevline);
}
if (view->buffer->first_line == line) {
/* first line in the buffer - this is the most commonly
removed 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 */
realcount = view_scroll(view, &view->startline,
&view->subline,
linecount, FALSE);
view->ypos -= realcount;
view->empty_linecount += linecount-realcount;
}
} 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 (textbuffer_line_exists_after(view->startline,
line)) {
view_remove_line_update_startline(view, line,
linecount);
}
}
view->bottom = view_is_bottom(view);
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);
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);
}
static int g_free_true(void *data)
{
g_free(data);
return TRUE;
}
/* 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_remove(view->bookmarks,
(GHRFunc) g_free_true, NULL);
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);
}
/* 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)
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;
term_window_clear(view->window);
view_draw_top(view, view->height, FALSE);
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 (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);
}