1 /*
2  textbuffer-view.c : Text buffer handling
3 
4     Copyright (C) 1999-2001 Timo Sirainen
5 
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10 
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15 
16     You should have received a copy of the GNU General Public License along
17     with this program; if not, write to the Free Software Foundation, Inc.,
18     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20 
21 #define	G_LOG_DOMAIN "TextBufferView"
22 
23 #include "module.h"
24 #include "textbuffer-view.h"
25 #include "signals.h"
26 #include "utf8.h"
27 
28 typedef struct {
29 	char *name;
30         LINE_REC *line;
31 } BOOKMARK_REC;
32 
33 /* how often to scan line cache for lines not accessed for a while (ms) */
34 #define LINE_CACHE_CHECK_TIME (5*60*1000)
35 /* how long to keep line cache in memory (seconds) */
36 #define LINE_CACHE_KEEP_TIME (10*60)
37 
38 static int linecache_tag;
39 static GSList *views;
40 
41 #define view_is_bottom(view) \
42         ((view)->ypos >= -1 && (view)->ypos < (view)->height)
43 
44 #define view_get_linecount_hidden(view, line) \
45         textbuffer_view_get_line_cache(view, line)->count
46 
47 #define view_line_is_hidden(view, line) \
48 	(((line)->info.level & (view)->hidden_level) != 0)
49 
50 #define view_get_linecount(view, line) \
51 	(view_line_is_hidden(view, line) ? 0 : view_get_linecount_hidden(view, line))
52 
textbuffer_get_views(TEXT_BUFFER_REC * buffer)53 static GSList *textbuffer_get_views(TEXT_BUFFER_REC *buffer)
54 {
55 	GSList *tmp, *list;
56 
57 	for (tmp = views; tmp != NULL; tmp = tmp->next) {
58 		TEXT_BUFFER_VIEW_REC *view = tmp->data;
59 
60 		if (view->buffer == buffer) {
61 			list = g_slist_copy(view->siblings);
62                         return g_slist_prepend(list, view);
63 		}
64 	}
65 
66         return NULL;
67 }
68 
69 static TEXT_BUFFER_CACHE_REC *
textbuffer_cache_get(GSList * views,int width)70 textbuffer_cache_get(GSList *views, int width)
71 {
72 	TEXT_BUFFER_CACHE_REC *cache;
73 
74         /* check if there's existing cache with correct width */
75 	while (views != NULL) {
76 		TEXT_BUFFER_VIEW_REC *view = views->data;
77 
78 		if (view->width == width) {
79 			view->cache->refcount++;
80 			return view->cache;
81 		}
82                 views = views->next;
83 	}
84 
85         /* create new cache */
86 	cache = g_new0(TEXT_BUFFER_CACHE_REC, 1);
87 	cache->refcount = 1;
88         cache->width = width;
89 	cache->line_cache = g_hash_table_new((GHashFunc) g_direct_hash,
90 					     (GCompareFunc) g_direct_equal);
91         return cache;
92 }
93 
line_cache_destroy(void * key,LINE_CACHE_REC * cache)94 static int line_cache_destroy(void *key, LINE_CACHE_REC *cache)
95 {
96 	g_free(cache);
97 	return TRUE;
98 }
99 
textbuffer_cache_destroy(TEXT_BUFFER_CACHE_REC * cache)100 static void textbuffer_cache_destroy(TEXT_BUFFER_CACHE_REC *cache)
101 {
102 	g_hash_table_foreach(cache->line_cache,
103 			     (GHFunc) line_cache_destroy, NULL);
104 	g_hash_table_destroy(cache->line_cache);
105         g_free(cache);
106 }
107 
textbuffer_cache_unref(TEXT_BUFFER_CACHE_REC * cache)108 static void textbuffer_cache_unref(TEXT_BUFFER_CACHE_REC *cache)
109 {
110 	if (--cache->refcount == 0)
111                 textbuffer_cache_destroy(cache);
112 }
113 
114 #define FGATTR (ATTR_NOCOLORS | ATTR_RESETFG | FG_MASK | ATTR_FGCOLOR24)
115 #define BGATTR (ATTR_NOCOLORS | ATTR_RESETBG | BG_MASK | ATTR_BGCOLOR24)
116 
update_cmd_color(unsigned char cmd,int * color)117 static void update_cmd_color(unsigned char cmd, int *color)
118 {
119 	if ((cmd & 0x80) == 0) {
120 		if (cmd & LINE_COLOR_BG) {
121 			/* set background color */
122 			*color &= FGATTR;
123 			if ((cmd & LINE_COLOR_DEFAULT) == 0)
124 				*color |= (cmd & 0x0f) << BG_SHIFT;
125 			else {
126 				*color = (*color & FGATTR) | ATTR_RESETBG;
127 			}
128 		} else {
129 			/* set foreground color */
130 			*color &= BGATTR;
131 			if ((cmd & LINE_COLOR_DEFAULT) == 0)
132 				*color |= cmd & 0x0f;
133 			else {
134 				*color = (*color & BGATTR) | ATTR_RESETFG;
135 			}
136 		}
137 	} else switch (cmd) {
138 	case LINE_CMD_UNDERLINE:
139 		*color ^= ATTR_UNDERLINE;
140 		break;
141 	case LINE_CMD_REVERSE:
142 		*color ^= ATTR_REVERSE;
143 		break;
144 	case LINE_CMD_BLINK:
145 		*color ^= ATTR_BLINK;
146 		break;
147 	case LINE_CMD_BOLD:
148 		*color ^= ATTR_BOLD;
149 		break;
150 	case LINE_CMD_ITALIC:
151 		*color ^= ATTR_ITALIC;
152 		break;
153 	case LINE_CMD_MONOSPACE:
154 		/* ignored */
155 		break;
156 	case LINE_CMD_COLOR0:
157 		*color &= BGATTR;
158 		*color &= ~ATTR_FGCOLOR24;
159 		break;
160 	}
161 }
162 
163 #ifdef TERM_TRUECOLOR
unformat_24bit_line_color(const unsigned char ** ptr,int off,int * flags,unsigned int * fg,unsigned int * bg)164 static void unformat_24bit_line_color(const unsigned char **ptr, int off, int *flags, unsigned int *fg, unsigned int *bg)
165 {
166 	unsigned int color;
167 	unsigned char rgbx[4];
168 	unsigned int i;
169 	for (i = 0; i < 4; ++i) {
170 		rgbx[i] = (*ptr)[i + off];
171 	}
172 	rgbx[3] -= 0x20;
173 	*ptr += 4;
174 	for (i = 0; i < 3; ++i) {
175 		if (rgbx[3] & (0x10 << i))
176 			rgbx[i] -= 0x20;
177 	}
178 	color = rgbx[0] << 16 | rgbx[1] << 8 | rgbx[2];
179 	if (rgbx[3] & 0x1) {
180 		*flags = (*flags & FGATTR) | ATTR_BGCOLOR24;
181 		*bg = color;
182 	}
183 	else {
184 		*flags = (*flags & BGATTR) | ATTR_FGCOLOR24;
185 		*fg = color;
186 	}
187 }
188 #endif
189 
read_unichar(const unsigned char * data,const unsigned char ** next,int * width)190 static inline unichar read_unichar(const unsigned char *data, const unsigned char **next, int *width)
191 {
192 	unichar chr = g_utf8_get_char_validated((const char *) data, -1);
193 
194 	if (chr & 0x80000000) {
195 		chr = 0xfffd;
196 		*next = data + 1;
197 		*width = 1;
198 	} else {
199 		*next = (unsigned char *)g_utf8_next_char(data);
200 		*width = unichar_isprint(chr) ? i_wcwidth(chr) : 1;
201 	}
202 	return chr;
203 }
204 
205 static LINE_CACHE_REC *
view_update_line_cache(TEXT_BUFFER_VIEW_REC * view,LINE_REC * line)206 view_update_line_cache(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
207 {
208         INDENT_FUNC indent_func;
209 	LINE_CACHE_REC *rec;
210 	LINE_CACHE_SUB_REC *sub;
211 	GSList *lines;
212         unsigned char cmd;
213 	const unsigned char *ptr, *next_ptr, *last_space_ptr;
214 	int xpos, pos, indent_pos, last_space, last_color, color, linecount;
215 	unsigned int last_bg24, last_fg24, bg24, fg24;
216 	int char_width;
217 
218 	g_return_val_if_fail(line->text != NULL, NULL);
219 
220 	color = ATTR_RESETFG | ATTR_RESETBG;
221 	xpos = 0; indent_pos = view->default_indent;
222 	last_space = last_color = 0; last_space_ptr = NULL; sub = NULL;
223 	bg24 = fg24 = last_bg24 = last_fg24 = UINT_MAX;
224 
225         indent_func = view->default_indent_func;
226         linecount = 1;
227 	lines = NULL;
228 	for (ptr = line->text;;) {
229 		if (*ptr == '\0') {
230 			/* command */
231 			ptr++;
232 			cmd = *ptr;
233                         ptr++;
234 
235 			if (cmd == LINE_CMD_EOL)
236 				break;
237 
238 			if (cmd == LINE_CMD_CONTINUE) {
239 				unsigned char *tmp;
240 
241 				memcpy(&tmp, ptr, sizeof(char *));
242 				ptr = tmp;
243 				continue;
244 			}
245 
246 			if (cmd == LINE_CMD_INDENT) {
247 				/* set indentation position here - don't do
248 				   it if we're too close to right border */
249 				if (xpos < view->width-5) indent_pos = xpos;
250 			} else if (cmd == LINE_COLOR_EXT) {
251 				color &= ~ATTR_FGCOLOR24;
252 				color = (color & BGATTR) | *ptr++;
253 			} else if (cmd == LINE_COLOR_EXT_BG) {
254 				color &= ~ATTR_BGCOLOR24;
255 				color = (color & FGATTR) | (*ptr++ << BG_SHIFT);
256 			}
257 #ifdef TERM_TRUECOLOR
258 			else if (cmd == LINE_COLOR_24)
259 				unformat_24bit_line_color(&ptr, 0, &color, &fg24, &bg24);
260 #endif
261 			else
262 				update_cmd_color(cmd, &color);
263 			continue;
264 		}
265 
266 		if (!view->utf8) {
267 			/* MH */
268 			if (term_type != TERM_TYPE_BIG5 ||
269 			    ptr[1] == '\0' || !is_big5(ptr[0], ptr[1]))
270 				char_width = 1;
271 			else
272 				char_width = 2;
273 			next_ptr = ptr+char_width;
274 		} else {
275 			read_unichar(ptr, &next_ptr, &char_width);
276 		}
277 
278 		if (xpos + char_width > view->width && sub != NULL &&
279 		    (last_space <= indent_pos || last_space <= 10) &&
280 		    view->longword_noindent) {
281                         /* long word, remove the indentation from this line */
282 			xpos -= sub->indent;
283                         sub->indent = 0;
284 		}
285 
286 		if (xpos + char_width > view->width) {
287 			xpos = indent_func == NULL ? indent_pos :
288 				indent_func(view, line, -1);
289 
290 			sub = g_new0(LINE_CACHE_SUB_REC, 1);
291 			if (last_space > indent_pos && last_space > 10) {
292                                 /* go back to last space */
293                                 color = last_color; fg24 = last_fg24; bg24 = last_bg24;
294 				ptr = last_space_ptr;
295 				while (*ptr == ' ') ptr++;
296 			} else if (view->longword_noindent) {
297 				/* long word, no indentation in next line */
298 				xpos = 0;
299 				sub->continues = TRUE;
300 			}
301 
302 			sub->start = ptr;
303 			sub->indent = xpos;
304                         sub->indent_func = indent_func;
305 			sub->color = color;
306 #ifdef TERM_TRUECOLOR
307 			sub->fg24 = fg24; sub->bg24 = bg24;
308 #endif
309 
310 			lines = g_slist_append(lines, sub);
311 			linecount++;
312 
313 			last_space = 0;
314 			continue;
315 		}
316 
317 		if (view->break_wide && char_width > 1) {
318 			last_space = xpos;
319 			last_space_ptr = next_ptr;
320 			last_color = color; last_fg24 = fg24; last_bg24 = bg24;
321 		} else if (*ptr == ' ') {
322 			last_space = xpos;
323 			last_space_ptr = ptr;
324 			last_color = color; last_fg24 = fg24; last_bg24 = bg24;
325 		}
326 
327 		xpos += char_width;
328 		ptr = next_ptr;
329 	}
330 
331 	rec = g_malloc(sizeof(LINE_CACHE_REC)-sizeof(LINE_CACHE_SUB_REC) +
332 		       sizeof(LINE_CACHE_SUB_REC) * (linecount-1));
333 	rec->last_access = time(NULL);
334 	rec->count = linecount;
335 
336 	if (rec->count > 1) {
337 		for (pos = 0; lines != NULL; pos++) {
338 			void *data = lines->data;
339 
340 			memcpy(&rec->lines[pos], data,
341 			       sizeof(LINE_CACHE_SUB_REC));
342 
343 			lines = g_slist_remove(lines, data);
344 			g_free(data);
345 		}
346 	}
347 
348 	g_hash_table_insert(view->cache->line_cache, line, rec);
349 	return rec;
350 }
351 
view_remove_cache(TEXT_BUFFER_VIEW_REC * view,LINE_REC * line,unsigned char update_counter)352 static void view_remove_cache(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
353 			      unsigned char update_counter)
354 {
355 	LINE_CACHE_REC *cache;
356 
357 	if (view->cache->update_counter == update_counter)
358 		return;
359 	view->cache->update_counter = update_counter;
360 
361 	cache = g_hash_table_lookup(view->cache->line_cache, line);
362 	if (cache != NULL) {
363                 g_free(cache);
364 		g_hash_table_remove(view->cache->line_cache, line);
365 	}
366 }
367 
view_update_cache(TEXT_BUFFER_VIEW_REC * view,LINE_REC * line,unsigned char update_counter)368 static void view_update_cache(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
369 			      unsigned char update_counter)
370 {
371 	view_remove_cache(view, line, update_counter);
372 
373 	if (view->buffer->cur_line == line)
374 		view->cache->last_linecount = view_get_linecount(view, line);
375 }
376 
textbuffer_view_reset_cache(TEXT_BUFFER_VIEW_REC * view)377 void textbuffer_view_reset_cache(TEXT_BUFFER_VIEW_REC *view)
378 {
379 	GSList *tmp;
380 
381 	/* destroy line caches - note that you can't do simultaneously
382 	   unrefs + cache_get()s or it will keep using the old caches */
383 	textbuffer_cache_unref(view->cache);
384         g_slist_foreach(view->siblings, (GFunc) textbuffer_cache_unref, NULL);
385 
386 	view->cache = textbuffer_cache_get(view->siblings, view->width);
387 	for (tmp = view->siblings; tmp != NULL; tmp = tmp->next) {
388 		TEXT_BUFFER_VIEW_REC *rec = tmp->data;
389 
390 		rec->cache = textbuffer_cache_get(rec->siblings, rec->width);
391 	}
392 }
393 
view_line_draw(TEXT_BUFFER_VIEW_REC * view,LINE_REC * line,int subline,int ypos,int max)394 static int view_line_draw(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
395 			  int subline, int ypos, int max)
396 {
397 	INDENT_FUNC indent_func;
398 	LINE_CACHE_REC *cache;
399 	const unsigned char *text, *end, *text_newline;
400 	unsigned char *tmp;
401 	unichar chr;
402 	int xpos, color, drawcount, first, need_move, need_clrtoeol, char_width;
403 #ifdef TERM_TRUECOLOR
404 	unsigned int fg24, bg24;
405 #endif
406 
407 	if (view->dirty) /* don't bother drawing anything - redraw is coming */
408 		return 0;
409 
410 	cache = textbuffer_view_get_line_cache(view, line);
411 	if (subline >= cache->count)
412 		return 0;
413 
414 	color = ATTR_RESET;
415 	need_move = TRUE; need_clrtoeol = FALSE;
416 	xpos = drawcount = 0; first = TRUE;
417 	text_newline = text =
418 		subline == 0 ? line->text : cache->lines[subline-1].start;
419 	for (;;) {
420 		if (text == text_newline) {
421 			if (need_clrtoeol && xpos < view->width + (view->width == term_width ? 0 : 1)) {
422 				term_set_color(view->window, ATTR_RESET);
423 				term_window_clrtoeol(view->window, ypos);
424 			}
425 
426 			if (first)
427 				first = FALSE;
428 			else {
429 				ypos++;
430 				if (--max == 0)
431 					break;
432 			}
433 
434 			if (subline > 0) {
435 				/* continuing previous line - indent it */
436 				indent_func = cache->lines[subline-1].indent_func;
437 				if (indent_func == NULL)
438 					xpos = cache->lines[subline-1].indent;
439 				color = cache->lines[subline-1].color;
440 #ifdef TERM_TRUECOLOR
441 				fg24 = cache->lines[subline-1].fg24;
442 				bg24 = cache->lines[subline-1].bg24;
443 #endif
444 			} else {
445 				indent_func = NULL;
446 			}
447 
448 			if (xpos == 0 && indent_func == NULL)
449 				need_clrtoeol = TRUE;
450 			else {
451 				/* line was indented - need to clear the
452 				   indented area first */
453 				term_set_color(view->window, ATTR_RESET);
454 				term_move(view->window, 0, ypos);
455 				term_window_clrtoeol(view->window, ypos);
456 
457 				if (indent_func != NULL)
458 					xpos = indent_func(view, line, ypos);
459 			}
460 
461 			if (need_move || xpos > 0)
462 				term_move(view->window, xpos, ypos);
463 
464 			term_set_color2(view->window, color, fg24, bg24);
465 
466 			if (subline == cache->count-1) {
467 				text_newline = NULL;
468 				need_move = FALSE;
469 			} else {
470 				/* get the beginning of the next subline */
471 				text_newline = cache->lines[subline].start;
472 				if (view->width == term_width) {
473 					/* ensure that links / long words are not broken */
474 					need_move = !cache->lines[subline].continues;
475 				} else {
476 					/* we cannot use the need_move
477 					   optimisation unless the split spans
478 					   the whole width */
479 					need_move = TRUE;
480 				}
481 			}
482 			drawcount++;
483 			subline++;
484 		}
485 
486 		if (*text == '\0') {
487 			/* command */
488 			text++;
489 			if (*text == LINE_CMD_EOL)
490 				break;
491 
492 			if (*text == LINE_CMD_CONTINUE) {
493 				/* jump to next block */
494 				memcpy(&tmp, text+1, sizeof(unsigned char *));
495 				text = tmp;
496 				continue;
497 			} else {
498 				if (*text == LINE_COLOR_EXT)
499 					color = (color & BGATTR & ~ATTR_FGCOLOR24) | *++text;
500 				else if (*text == LINE_COLOR_EXT_BG)
501 					color = (color & FGATTR & ~ATTR_BGCOLOR24) | (*++text << BG_SHIFT);
502 #ifdef TERM_TRUECOLOR
503 				else if (*text == LINE_COLOR_24)
504 					unformat_24bit_line_color(&text, 1, &color, &fg24, &bg24);
505 #endif
506 				else
507 					update_cmd_color(*text, &color);
508 				term_set_color2(view->window, color, fg24, bg24);
509 			}
510 			text++;
511 			continue;
512 		}
513 
514 		if (view->utf8) {
515 			chr = read_unichar(text, &end, &char_width);
516 		} else {
517 			chr = *text;
518 			end = text;
519 			if (term_type == TERM_TYPE_BIG5 &&
520 			    is_big5(end[0], end[1]))
521 				char_width = 2;
522 			else
523 				char_width = 1;
524 			end += char_width;
525 		}
526 
527 		xpos += char_width;
528 		if (xpos <= view->width) {
529 			if (unichar_isprint(chr)) {
530 				if (view->utf8)
531 					term_add_unichar(view->window, chr);
532 				else
533 					for (; text < end; text++)
534 						term_addch(view->window, *text);
535 			} else {
536 				/* low-ascii */
537 				term_set_color(view->window, ATTR_RESET|ATTR_REVERSE);
538 				term_addch(view->window, (chr & 127)+'A'-1);
539 				term_set_color2(view->window, color, fg24, bg24);
540 			}
541 		}
542 		text = end;
543 	}
544 
545 	if (need_clrtoeol && xpos < view->width + (view->width == term_width ? 0 : 1)) {
546 		term_set_color(view->window, ATTR_RESET);
547 		term_window_clrtoeol(view->window, ypos);
548 	}
549 
550 	return drawcount;
551 }
552 
553 /* Recalculate view's bottom line information - try to keep the
554    original if possible */
textbuffer_view_init_bottom(TEXT_BUFFER_VIEW_REC * view)555 static void textbuffer_view_init_bottom(TEXT_BUFFER_VIEW_REC *view)
556 {
557         LINE_REC *line;
558         int linecount, total;
559 
560 	if (view->empty_linecount == 0) {
561 		/* no empty lines in screen, no need to try to keep
562 		   the old bottom startline */
563                 view->bottom_startline = NULL;
564 	}
565 
566 	total = 0;
567         line = textbuffer_line_last(view->buffer);
568 	for (; line != NULL; line = line->prev) {
569 		if (view_line_is_hidden(view, line))
570 			continue;
571 
572 		linecount = view_get_linecount(view, line);
573 		if (line == view->bottom_startline) {
574 			/* keep the old one, make sure that subline is ok */
575 			if (view->bottom_subline > linecount)
576 				view->bottom_subline = linecount;
577 			view->empty_linecount = view->height - total -
578 				(linecount-view->bottom_subline);
579                         return;
580 		}
581 
582                 total += linecount;
583 		if (total >= view->height) {
584 			view->bottom_startline = line;
585 			view->bottom_subline = total - view->height;
586                         view->empty_linecount = 0;
587                         return;
588 		}
589 	}
590 
591         /* not enough lines so we must be at the beginning of the buffer */
592 	view->bottom_startline = view->buffer->first_line;
593 	view->bottom_subline = 0;
594 	view->empty_linecount = view->height - total;
595 }
596 
textbuffer_view_init_ypos(TEXT_BUFFER_VIEW_REC * view)597 static void textbuffer_view_init_ypos(TEXT_BUFFER_VIEW_REC *view)
598 {
599         LINE_REC *line;
600 
601 	g_return_if_fail(view != NULL);
602 
603 	view->ypos = -view->subline-1;
604 	for (line = view->startline; line != NULL; line = line->next)
605 		view->ypos += view_get_linecount(view, line);
606 }
607 
608 /* Create new view. */
textbuffer_view_create(TEXT_BUFFER_REC * buffer,int width,int height,int scroll,int utf8)609 TEXT_BUFFER_VIEW_REC *textbuffer_view_create(TEXT_BUFFER_REC *buffer,
610 					     int width, int height,
611 					     int scroll, int utf8)
612 {
613 	TEXT_BUFFER_VIEW_REC *view;
614 
615         g_return_val_if_fail(buffer != NULL, NULL);
616         g_return_val_if_fail(width > 0, NULL);
617 
618 	view = g_new0(TEXT_BUFFER_VIEW_REC, 1);
619 	view->buffer = buffer;
620         view->siblings = textbuffer_get_views(buffer);
621 
622 	view->width = width;
623         view->height = height;
624 	view->scroll = scroll;
625         view->utf8 = utf8;
626 
627 	view->cache = textbuffer_cache_get(view->siblings, width);
628 	textbuffer_view_init_bottom(view);
629 
630 	view->startline = view->bottom_startline;
631         view->subline = view->bottom_subline;
632 	view->bottom = TRUE;
633 
634 	view->hidden_level = 0;
635 
636 	textbuffer_view_init_ypos(view);
637 
638 	view->bookmarks = g_hash_table_new((GHashFunc) g_str_hash,
639 					   (GCompareFunc) g_str_equal);
640 
641 	views = g_slist_append(views, view);
642         return view;
643 }
644 
645 /* Destroy the view. */
textbuffer_view_destroy(TEXT_BUFFER_VIEW_REC * view)646 void textbuffer_view_destroy(TEXT_BUFFER_VIEW_REC *view)
647 {
648 	GSList *tmp;
649 
650 	g_return_if_fail(view != NULL);
651 
652 	views = g_slist_remove(views, view);
653 
654 	if (view->siblings == NULL) {
655 		/* last view for textbuffer, destroy */
656                 textbuffer_destroy(view->buffer);
657 	} else {
658 		/* remove ourself from siblings lists */
659 		for (tmp = view->siblings; tmp != NULL; tmp = tmp->next) {
660 			TEXT_BUFFER_VIEW_REC *rec = tmp->data;
661 
662 			rec->siblings = g_slist_remove(rec->siblings, view);
663 		}
664 		g_slist_free(view->siblings);
665 	}
666 
667 	g_hash_table_foreach(view->bookmarks, (GHFunc) g_free, NULL);
668 	g_hash_table_destroy(view->bookmarks);
669 
670         textbuffer_cache_unref(view->cache);
671 	g_free(view);
672 }
673 
674 /* Change the default indent position */
textbuffer_view_set_default_indent(TEXT_BUFFER_VIEW_REC * view,int default_indent,int longword_noindent,INDENT_FUNC indent_func)675 void textbuffer_view_set_default_indent(TEXT_BUFFER_VIEW_REC *view,
676 					int default_indent,
677 					int longword_noindent,
678 					INDENT_FUNC indent_func)
679 {
680         if (default_indent != -1)
681 		view->default_indent = default_indent;
682         if (longword_noindent != -1)
683 		view->longword_noindent = longword_noindent;
684 
685 	view->default_indent_func = indent_func;
686 }
687 
688 /* Enable breaking of wide chars */
textbuffer_view_set_break_wide(TEXT_BUFFER_VIEW_REC * view,gboolean break_wide)689 void textbuffer_view_set_break_wide(TEXT_BUFFER_VIEW_REC *view,
690 				    gboolean break_wide)
691 {
692 	if (view->break_wide != break_wide) {
693 		view->break_wide = break_wide;
694 		textbuffer_view_reset_cache(view);
695 	}
696 }
697 
view_unregister_indent_func(TEXT_BUFFER_VIEW_REC * view,INDENT_FUNC indent_func)698 static void view_unregister_indent_func(TEXT_BUFFER_VIEW_REC *view,
699 					INDENT_FUNC indent_func)
700 {
701 	if (view->default_indent_func == indent_func)
702 		view->default_indent_func = NULL;
703 
704 	/* recreate cache so it won't contain references
705 	   to the indent function */
706 	textbuffer_view_reset_cache(view);
707 	view->cache = textbuffer_cache_get(view->siblings, view->width);
708 }
709 
textbuffer_views_unregister_indent_func(INDENT_FUNC indent_func)710 void textbuffer_views_unregister_indent_func(INDENT_FUNC indent_func)
711 {
712 	g_slist_foreach(views, (GFunc) view_unregister_indent_func,
713 			(void *) indent_func);
714 }
715 
textbuffer_view_set_scroll(TEXT_BUFFER_VIEW_REC * view,int scroll)716 void textbuffer_view_set_scroll(TEXT_BUFFER_VIEW_REC *view, int scroll)
717 {
718         view->scroll = scroll;
719 }
720 
textbuffer_view_set_utf8(TEXT_BUFFER_VIEW_REC * view,int utf8)721 void textbuffer_view_set_utf8(TEXT_BUFFER_VIEW_REC *view, int utf8)
722 {
723         view->utf8 = utf8;
724 }
725 
view_get_linecount_all(TEXT_BUFFER_VIEW_REC * view,LINE_REC * line)726 static int view_get_linecount_all(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
727 {
728 	int linecount;
729 
730         linecount = 0;
731 	while (line != NULL) {
732 		linecount += view_get_linecount(view, line);
733                 line = line->next;
734 	}
735 
736         return linecount;
737 }
738 
view_draw(TEXT_BUFFER_VIEW_REC * view,LINE_REC * line,int subline,int ypos,int lines,int fill_bottom)739 static void view_draw(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
740 		      int subline, int ypos, int lines, int fill_bottom)
741 {
742 	int linecount;
743 
744 	if (view->dirty) /* don't bother drawing anything - redraw is coming */
745                 return;
746 
747 	while (line != NULL && lines > 0) {
748 		if (!view_line_is_hidden(view, line)) {
749 			linecount = view_line_draw(view, line, subline, ypos, lines);
750 			ypos += linecount; lines -= linecount;
751 		}
752 
753 		subline = 0;
754                 line = line->next;
755 	}
756 
757 	if (fill_bottom) {
758 		/* clear the rest of the view */
759 		term_set_color(view->window, ATTR_RESET);
760 		while (lines > 0) {
761 			term_move(view->window, 0, ypos);
762 			term_window_clrtoeol(view->window, ypos);
763 			ypos++; lines--;
764 		}
765 	}
766 }
767 
768 #define view_draw_top(view, lines, fill_bottom) \
769 	view_draw(view, (view)->startline, (view)->subline, \
770 		  0, lines, fill_bottom)
771 
view_draw_bottom(TEXT_BUFFER_VIEW_REC * view,int lines)772 static void view_draw_bottom(TEXT_BUFFER_VIEW_REC *view, int lines)
773 {
774 	LINE_REC *line;
775 	int ypos, maxline, subline, linecount;
776 
777 	maxline = view->height-lines;
778 	line = view->startline; ypos = -view->subline; subline = 0;
779 	while (line != NULL && ypos < maxline) {
780                 linecount = view_get_linecount(view, line);
781 		ypos += linecount;
782 		if (ypos > maxline) {
783 			subline = maxline-(ypos-linecount);
784 			break;
785 		}
786                 line = line->next;
787 	}
788 
789         view_draw(view, line, subline, maxline, lines, TRUE);
790 }
791 
792 /* lines: this pointer is scrolled by scrollcount screen lines
793    subline: this pointer contains the subline position
794    scrollcount: the number of lines to scroll down (negative: up)
795    draw_nonclean: whether to redraw the screen now
796 
797    Returns number of lines actually scrolled */
view_scroll(TEXT_BUFFER_VIEW_REC * view,LINE_REC ** lines,int * subline,int scrollcount,int draw_nonclean)798 static int view_scroll(TEXT_BUFFER_VIEW_REC *view, LINE_REC **lines,
799 		       int *subline, int scrollcount, int draw_nonclean)
800 {
801 	int linecount, realcount, scroll_visible;
802 
803 	if (*lines == NULL)
804 		return 0;
805 
806 	/* scroll down */
807 	scroll_visible = lines == &view->startline;
808 
809 	realcount = -*subline;
810 	scrollcount += *subline;
811 	*subline = 0;
812 	while (scrollcount > 0) {
813 		linecount = view_get_linecount(view, *lines);
814 
815 		if ((scroll_visible && *lines == view->bottom_startline) &&
816 		    (scrollcount >= view->bottom_subline)) {
817 			*subline = view->bottom_subline;
818 			realcount += view->bottom_subline;
819 			scrollcount = 0;
820 			break;
821 		}
822 
823 		realcount += linecount;
824 		scrollcount -= linecount;
825 		if (scrollcount < 0) {
826 			realcount += scrollcount;
827 			*subline = linecount+scrollcount;
828 			scrollcount = 0;
829 			break;
830 		}
831 
832 		if ((*lines)->next == NULL)
833 			break;
834 
835 		*lines = (*lines)->next;
836 	}
837 
838 	/* scroll up */
839 	while (scrollcount < 0 && (*lines)->prev != NULL) {
840 		*lines = (*lines)->prev;
841 		linecount = view_get_linecount(view, *lines);
842 
843 		realcount -= linecount;
844 		scrollcount += linecount;
845 		if (scrollcount > 0) {
846 			realcount += scrollcount;
847 			*subline = scrollcount;
848 			break;
849 		}
850 	}
851 
852 	if (scroll_visible && realcount != 0 && view->window != NULL) {
853 		if (realcount <= -view->height || realcount >= view->height) {
854 			/* scrolled more than screenful, redraw the
855 			   whole view */
856 			textbuffer_view_redraw(view);
857 		} else {
858 			if (view->width == term_width) {
859 				/* we can try to use vt100 scroll regions */
860 				term_set_color(view->window, ATTR_RESET);
861 				term_window_scroll(view->window, realcount);
862 
863 				if (draw_nonclean) {
864 					if (realcount < 0)
865 						view_draw_top(view, -realcount, TRUE);
866 					else
867 						view_draw_bottom(view, realcount);
868 				}
869 
870 				term_refresh(view->window);
871 			} else {
872 				/* do not bother with vt400 scroll
873 				   rectangles for now, redraw the
874 				   whole view */
875 				view->dirty = TRUE;
876 				irssi_set_dirty();
877 			}
878 		}
879 	}
880 
881 	return realcount >= 0 ? realcount : -realcount;
882 }
883 
884 /* Resize the view. */
textbuffer_view_resize(TEXT_BUFFER_VIEW_REC * view,int width,int height)885 void textbuffer_view_resize(TEXT_BUFFER_VIEW_REC *view, int width, int height)
886 {
887 	int linecount;
888 
889         g_return_if_fail(view != NULL);
890         g_return_if_fail(width > 0);
891 
892 	if (view->width != width) {
893                 /* line cache needs to be recreated */
894 		textbuffer_cache_unref(view->cache);
895 		view->cache = textbuffer_cache_get(view->siblings, width);
896 	}
897 
898 	view->width = width > 10 ? width : 10;
899 	view->height = height > 1 ? height : 1;
900 
901 	if (view->buffer->first_line == NULL) {
902                 view->empty_linecount = height;
903 		return;
904 	}
905 
906 	textbuffer_view_init_bottom(view);
907 
908 	/* check that we didn't scroll lower than bottom startline.. */
909 	if (textbuffer_line_exists_after(view->bottom_startline->next,
910 					 view->startline)) {
911 		view->startline = view->bottom_startline;
912                 view->subline = view->bottom_subline;
913 	} else if (view->startline == view->bottom_startline &&
914 		   view->subline > view->bottom_subline) {
915                 view->subline = view->bottom_subline;
916 	} else if (view->startline != NULL) {
917 		/* make sure the subline is still in allowed range */
918 		linecount = view_get_linecount(view, view->startline);
919 		if (view->subline > linecount)
920                         view->subline = linecount;
921 	} else {
922 		/* we don't have a startline. still under construction? */
923 		view->subline = 0;
924 	}
925 
926 	textbuffer_view_init_ypos(view);
927 	if (view->bottom && !view_is_bottom(view)) {
928 		/* we scrolled to far up, need to get down. go right over
929 		   the empty lines if there's any */
930 		view->startline = view->bottom_startline;
931 		view->subline = view->bottom_subline;
932 		if (view->empty_linecount > 0) {
933 			view_scroll(view, &view->startline, &view->subline,
934 				    -view->empty_linecount, FALSE);
935 		}
936 		textbuffer_view_init_ypos(view);
937 	}
938 
939 	view->bottom = view_is_bottom(view);
940 	if (view->bottom) {
941 		/* check if we left empty space at the bottom.. */
942 		linecount = view_get_linecount_all(view, view->startline) -
943 			view->subline;
944                 if (view->empty_linecount < view->height-linecount)
945 			view->empty_linecount = view->height-linecount;
946                 view->more_text = FALSE;
947 	}
948 
949 	view->dirty = TRUE;
950 }
951 
952 /* Clear the view, don't actually remove any lines from buffer. */
textbuffer_view_clear(TEXT_BUFFER_VIEW_REC * view)953 void textbuffer_view_clear(TEXT_BUFFER_VIEW_REC *view)
954 {
955         g_return_if_fail(view != NULL);
956 
957 	view->ypos = -1;
958 	view->bottom_startline = view->startline =
959 		textbuffer_line_last(view->buffer);
960 	view->bottom_subline = view->subline =
961 		view->buffer->cur_line == NULL ? 0 :
962 		view_get_linecount(view, view->buffer->cur_line);
963 	view->empty_linecount = view->height;
964 	view->bottom = TRUE;
965 	view->more_text = FALSE;
966 
967         textbuffer_view_redraw(view);
968 }
969 
970 /* Scroll the view up/down */
textbuffer_view_scroll(TEXT_BUFFER_VIEW_REC * view,int lines)971 void textbuffer_view_scroll(TEXT_BUFFER_VIEW_REC *view, int lines)
972 {
973 	int count;
974 
975         g_return_if_fail(view != NULL);
976 
977 	count = view_scroll(view, &view->startline, &view->subline,
978 			    lines, TRUE);
979 	view->ypos += lines < 0 ? count : -count;
980 	view->bottom = view_is_bottom(view);
981         if (view->bottom) view->more_text = FALSE;
982 
983         if (view->window != NULL)
984 		term_refresh(view->window);
985 }
986 
987 /* Scroll to specified line */
textbuffer_view_scroll_line(TEXT_BUFFER_VIEW_REC * view,LINE_REC * line)988 void textbuffer_view_scroll_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
989 {
990         g_return_if_fail(view != NULL);
991 
992 	if (textbuffer_line_exists_after(view->bottom_startline->next, line)) {
993 		view->startline = view->bottom_startline;
994 		view->subline = view->bottom_subline;
995 	} else {
996 		view->startline = line;
997                 view->subline = 0;
998 	}
999 
1000 	textbuffer_view_init_ypos(view);
1001 	view->bottom = view_is_bottom(view);
1002         if (view->bottom) view->more_text = FALSE;
1003 
1004 	textbuffer_view_redraw(view);
1005 }
1006 
1007 /* Return line cache */
textbuffer_view_get_line_cache(TEXT_BUFFER_VIEW_REC * view,LINE_REC * line)1008 LINE_CACHE_REC *textbuffer_view_get_line_cache(TEXT_BUFFER_VIEW_REC *view,
1009 					       LINE_REC *line)
1010 {
1011 	LINE_CACHE_REC *cache;
1012 
1013         g_assert(view != NULL);
1014         g_assert(line != NULL);
1015 
1016 	cache = g_hash_table_lookup(view->cache->line_cache, line);
1017 	if (cache == NULL)
1018 		cache = view_update_line_cache(view, line);
1019         else
1020 		cache->last_access = time(NULL);
1021 
1022         return cache;
1023 }
1024 
view_insert_line(TEXT_BUFFER_VIEW_REC * view,LINE_REC * line)1025 static void view_insert_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
1026 {
1027 	int linecount, ypos, subline;
1028 
1029         if (!view->bottom)
1030 		view->more_text = TRUE;
1031 
1032 	if (view->bottom_startline == NULL) {
1033 		view->startline = view->bottom_startline =
1034 			view->buffer->first_line;
1035 	}
1036 
1037 	if (view->buffer->cur_line != line &&
1038 	    !textbuffer_line_exists_after(view->bottom_startline, line))
1039 		return;
1040 
1041 	linecount = view_get_linecount(view, line);
1042 	view->ypos += linecount;
1043 	if (view->empty_linecount > 0) {
1044 		view->empty_linecount -= linecount;
1045 		if (view->empty_linecount >= 0)
1046 			linecount = 0;
1047 		else {
1048 			linecount = -view->empty_linecount;
1049 			view->empty_linecount = 0;
1050 		}
1051 	}
1052 
1053 	if (linecount > 0) {
1054 		view_scroll(view, &view->bottom_startline,
1055 			    &view->bottom_subline, linecount, FALSE);
1056 	}
1057 
1058 	if (view->bottom) {
1059 		if (view->scroll && view->ypos >= view->height) {
1060 			linecount = view->ypos-view->height+1;
1061 			view_scroll(view, &view->startline,
1062 				    &view->subline, linecount, FALSE);
1063 			view->ypos -= linecount;
1064 		} else {
1065 			view->bottom = view_is_bottom(view);
1066 		}
1067 
1068 		if (view->window != NULL && !view_line_is_hidden(view, line)) {
1069 			ypos = view->ypos+1 - view_get_linecount(view, line);
1070 			if (ypos >= 0)
1071 				subline = 0;
1072 			else {
1073 				subline = -ypos;
1074 				ypos = 0;
1075 			}
1076 			if (ypos < view->height) {
1077 				view_line_draw(view, line, subline, ypos,
1078 					       view->height - ypos);
1079 			}
1080 		}
1081 	}
1082 
1083         if (view->window != NULL && !view_line_is_hidden(view, line))
1084 		term_refresh(view->window);
1085 }
1086 
1087 /* Update some line in the buffer which has been modified using
1088    textbuffer_append() or textbuffer_insert(). */
textbuffer_view_insert_line(TEXT_BUFFER_VIEW_REC * view,LINE_REC * line)1089 void textbuffer_view_insert_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
1090 {
1091         GSList *tmp;
1092 	unsigned char update_counter;
1093 
1094 	g_return_if_fail(view != NULL);
1095 	g_return_if_fail(line != NULL);
1096 
1097 	if (!view->buffer->last_eol)
1098                 return;
1099 
1100         update_counter = view->cache->update_counter+1;
1101 	view_update_cache(view, line, update_counter);
1102         view_insert_line(view, line);
1103 
1104 	for (tmp = view->siblings; tmp != NULL; tmp = tmp->next) {
1105 		TEXT_BUFFER_VIEW_REC *rec = tmp->data;
1106 
1107                 view_update_cache(rec, line, update_counter);
1108 		view_insert_line(rec, line);
1109 	}
1110 }
1111 
1112 typedef struct {
1113 	LINE_REC *remove_line;
1114         GSList *remove_list;
1115 } BOOKMARK_FIND_REC;
1116 
bookmark_check_remove(char * key,LINE_REC * line,BOOKMARK_FIND_REC * rec)1117 static void bookmark_check_remove(char *key, LINE_REC *line,
1118 				  BOOKMARK_FIND_REC *rec)
1119 {
1120 	if (line == rec->remove_line)
1121                 rec->remove_list = g_slist_append(rec->remove_list, key);
1122 }
1123 
view_bookmarks_check(TEXT_BUFFER_VIEW_REC * view,LINE_REC * line)1124 static void view_bookmarks_check(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
1125 {
1126         BOOKMARK_FIND_REC rec;
1127 	GSList *tmp;
1128 
1129         rec.remove_line = line;
1130         rec.remove_list = NULL;
1131 	g_hash_table_foreach(view->bookmarks,
1132 			     (GHFunc) bookmark_check_remove, &rec);
1133 
1134 	if (rec.remove_list != NULL) {
1135 		for (tmp = rec.remove_list; tmp != NULL; tmp = tmp->next) {
1136 			g_hash_table_remove(view->bookmarks, tmp->data);
1137 			g_free(tmp->data);
1138 		}
1139 		g_slist_free(rec.remove_list);
1140 	}
1141 }
1142 
1143 /* Return number of real lines `lines' list takes -
1144    stops counting when the height reaches the view height */
view_get_lines_height(TEXT_BUFFER_VIEW_REC * view,LINE_REC * line,int subline,LINE_REC * skip_line)1145 static int view_get_lines_height(TEXT_BUFFER_VIEW_REC *view,
1146 				 LINE_REC *line, int subline,
1147 				 LINE_REC *skip_line)
1148 {
1149 	int height, linecount;
1150 
1151         height = -subline;
1152 	while (line != NULL && height < view->height) {
1153 		if (line != skip_line) {
1154                         linecount = view_get_linecount(view, line);
1155 			height += linecount;
1156 		}
1157                 line = line->next;
1158 	}
1159 
1160 	return height < view->height ? height : view->height;
1161 }
1162 
1163 /* line: line to remove
1164    linecount: linecount of that line, to be offset when the line was in/below view
1165 
1166    scroll the window maintaining the startline while removing line
1167    if startline is removed, make the previous line the new startline
1168 */
view_remove_line_update_startline(TEXT_BUFFER_VIEW_REC * view,LINE_REC * line,int linecount)1169 static void view_remove_line_update_startline(TEXT_BUFFER_VIEW_REC *view,
1170 					      LINE_REC *line, int linecount)
1171 {
1172 	int scroll;
1173 
1174 	if (view->startline == line) {
1175 		view->startline = view->startline->prev != NULL ?
1176 			view->startline->prev : view->startline->next;
1177 		view->subline = 0;
1178 	} else {
1179 		scroll = view->height -
1180 			view_get_lines_height(view, view->startline,
1181 					      view->subline, line);
1182 		if (scroll > 0) {
1183 			view_scroll(view, &view->startline,
1184 				    &view->subline, -scroll, FALSE);
1185 		}
1186 	}
1187 
1188 	/* FIXME: this is slow and unnecessary, but it's easy and
1189 	   really works :) */
1190 	textbuffer_view_init_ypos(view);
1191 	if (textbuffer_line_exists_after(view->startline, line))
1192 		view->ypos -= linecount;
1193 }
1194 
view_remove_line(TEXT_BUFFER_VIEW_REC * view,LINE_REC * line,int linecount)1195 static void view_remove_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
1196 			     int linecount)
1197 {
1198 	int realcount;
1199 
1200 	view_bookmarks_check(view, line);
1201 
1202 	if (view->buffer->cur_line == line) {
1203                 /* the last line is being removed */
1204 		LINE_REC *prevline;
1205 
1206 		prevline = view->buffer->first_line == line ? NULL :
1207 			textbuffer_line_last(view->buffer)->prev;
1208 		view->cache->last_linecount = prevline == NULL ? 0 :
1209 			view_get_linecount(view, prevline);
1210 	}
1211 
1212 	/* first line in the buffer - this is the most commonly
1213 	   removed line.. */
1214 	if (view->buffer->first_line == line) {
1215 		if (view->bottom_startline == line) {
1216 			/* very small scrollback.. */
1217                         view->bottom_startline = view->bottom_startline->next;
1218 			view->bottom_subline = 0;
1219 		}
1220 
1221 		if (view->startline == line) {
1222                         /* removing the first line in screen */
1223 			int is_last = view->startline->next == NULL;
1224 
1225 			realcount = view_scroll(view, &view->startline,
1226 						&view->subline,
1227 						linecount, FALSE);
1228 			view->ypos -= realcount;
1229 			view->empty_linecount += linecount-realcount;
1230 			if (is_last == 1)
1231 				view->startline = NULL;
1232 		}
1233 
1234 		if (view->startline == line) {
1235 			view->startline = line->next;
1236 			view->subline = 0;
1237 		}
1238 	} else {
1239 		if (textbuffer_line_exists_after(view->bottom_startline,
1240 						 line)) {
1241 			realcount = view_scroll(view, &view->bottom_startline,
1242 						&view->bottom_subline,
1243 						-linecount, FALSE);
1244 			view->empty_linecount += linecount-realcount;
1245 		}
1246 
1247 		if (view->bottom_startline == line) {
1248 			view->bottom_startline = view->bottom_startline->next;
1249 			view->bottom_subline = 0;
1250 		}
1251 
1252 		if (textbuffer_line_exists_after(view->startline,
1253 						 line)) {
1254 			view_remove_line_update_startline(view, line,
1255 							  linecount);
1256 		}
1257 	}
1258 
1259 	view->bottom = view_is_bottom(view);
1260         if (view->bottom) view->more_text = FALSE;
1261         if (view->window != NULL)
1262 		term_refresh(view->window);
1263 }
1264 
1265 /* Remove one line from buffer. */
textbuffer_view_remove_line(TEXT_BUFFER_VIEW_REC * view,LINE_REC * line)1266 void textbuffer_view_remove_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
1267 {
1268         GSList *tmp;
1269 	unsigned char update_counter;
1270         int linecount;
1271 
1272 	g_return_if_fail(view != NULL);
1273 	g_return_if_fail(line != NULL);
1274 
1275 	signal_emit("gui textbuffer line removed", 3, view, line, line->prev);
1276 
1277         linecount = view_get_linecount(view, line);
1278         update_counter = view->cache->update_counter+1;
1279 
1280         view_remove_line(view, line, linecount);
1281 	view_remove_cache(view, line, update_counter);
1282 
1283 	for (tmp = view->siblings; tmp != NULL; tmp = tmp->next) {
1284 		TEXT_BUFFER_VIEW_REC *rec = tmp->data;
1285 
1286 		view_remove_line(rec, line, linecount);
1287 		view_remove_cache(rec, line, update_counter);
1288 	}
1289 
1290 	textbuffer_remove(view->buffer, line);
1291 	if (view->bottom_startline == NULL) {
1292 		/* We may have removed the bottom_startline, make sure
1293 		   that scroll doesn't get stuck */
1294 		textbuffer_view_init_bottom(view);
1295 	}
1296 }
1297 
textbuffer_view_remove_lines_by_level(TEXT_BUFFER_VIEW_REC * view,int level)1298 void textbuffer_view_remove_lines_by_level(TEXT_BUFFER_VIEW_REC *view, int level)
1299 {
1300 	LINE_REC *line, *next;
1301 
1302 	term_refresh_freeze();
1303 	line = textbuffer_view_get_lines(view);
1304 
1305 	while (line != NULL) {
1306 		next = line->next;
1307 
1308 		if (line->info.level & level)
1309 			textbuffer_view_remove_line(view, line);
1310 		line = next;
1311 	}
1312 	textbuffer_view_redraw(view);
1313 	term_refresh_thaw();
1314 }
1315 
g_free_true(void * data)1316 static int g_free_true(void *data)
1317 {
1318 	g_free(data);
1319         return TRUE;
1320 }
1321 
1322 /* Remove all lines from buffer. */
textbuffer_view_remove_all_lines(TEXT_BUFFER_VIEW_REC * view)1323 void textbuffer_view_remove_all_lines(TEXT_BUFFER_VIEW_REC *view)
1324 {
1325 	g_return_if_fail(view != NULL);
1326 
1327 	textbuffer_remove_all_lines(view->buffer);
1328 
1329 	g_hash_table_foreach_remove(view->bookmarks,
1330 				    (GHRFunc) g_free_true, NULL);
1331 
1332 	textbuffer_view_reset_cache(view);
1333 	textbuffer_view_clear(view);
1334 	g_slist_foreach(view->siblings, (GFunc) textbuffer_view_clear, NULL);
1335 }
1336 
1337 /* Set a bookmark in view */
textbuffer_view_set_bookmark(TEXT_BUFFER_VIEW_REC * view,const char * name,LINE_REC * line)1338 void textbuffer_view_set_bookmark(TEXT_BUFFER_VIEW_REC *view,
1339 				  const char *name, LINE_REC *line)
1340 {
1341 	gpointer key, value;
1342 
1343 	g_return_if_fail(view != NULL);
1344 	g_return_if_fail(name != NULL);
1345 
1346 	if (g_hash_table_lookup_extended(view->bookmarks, name,
1347 					 &key, &value)) {
1348 		g_hash_table_remove(view->bookmarks, key);
1349                 g_free(key);
1350 	}
1351 
1352 	g_hash_table_insert(view->bookmarks, g_strdup(name), line);
1353 }
1354 
1355 /* Set a bookmark in view to the bottom line */
textbuffer_view_set_bookmark_bottom(TEXT_BUFFER_VIEW_REC * view,const char * name)1356 void textbuffer_view_set_bookmark_bottom(TEXT_BUFFER_VIEW_REC *view,
1357 					 const char *name)
1358 {
1359 	LINE_REC *line;
1360 
1361 	g_return_if_fail(view != NULL);
1362 	g_return_if_fail(name != NULL);
1363 
1364 	if (view->bottom_startline != NULL) {
1365                 line = textbuffer_line_last(view->buffer);
1366 		textbuffer_view_set_bookmark(view, name, line);
1367 	}
1368 }
1369 
1370 /* Return the line for bookmark */
textbuffer_view_get_bookmark(TEXT_BUFFER_VIEW_REC * view,const char * name)1371 LINE_REC *textbuffer_view_get_bookmark(TEXT_BUFFER_VIEW_REC *view,
1372 				       const char *name)
1373 {
1374 	g_return_val_if_fail(view != NULL, NULL);
1375 	g_return_val_if_fail(name != NULL, NULL);
1376 
1377         return g_hash_table_lookup(view->bookmarks, name);
1378 }
1379 
textbuffer_view_set_hidden_level(TEXT_BUFFER_VIEW_REC * view,int level)1380 void textbuffer_view_set_hidden_level(TEXT_BUFFER_VIEW_REC *view, int level)
1381 {
1382 	g_return_if_fail(view != NULL);
1383 
1384 	if (view->hidden_level != level) {
1385 		if (view->empty_linecount > 0 && view->startline != NULL) {
1386 			int old_height, new_height;
1387 			LINE_REC *hidden_start;
1388 
1389 			hidden_start = view->startline;
1390 			while (hidden_start->prev != NULL && view_line_is_hidden(view, hidden_start->prev)) {
1391 				hidden_start = hidden_start->prev;
1392 			}
1393 
1394 			old_height = view_get_lines_height(view, hidden_start, view->subline, NULL);
1395 			view->hidden_level = level;
1396 			new_height = view_get_lines_height(view, hidden_start, view->subline, NULL);
1397 
1398 			view->empty_linecount -= new_height - old_height;
1399 
1400 			if (view->empty_linecount < 0)
1401 				view->empty_linecount = 0;
1402 			else if (view->empty_linecount > view->height)
1403 				view->empty_linecount = view->height;
1404 		} else {
1405 			view->hidden_level = level;
1406 		}
1407 		textbuffer_view_resize(view, view->width, view->height);
1408 	}
1409 }
1410 
1411 /* Specify window where the changes in view should be drawn,
1412    NULL disables it. */
textbuffer_view_set_window(TEXT_BUFFER_VIEW_REC * view,TERM_WINDOW * window)1413 void textbuffer_view_set_window(TEXT_BUFFER_VIEW_REC *view,
1414 				TERM_WINDOW *window)
1415 {
1416 	g_return_if_fail(view != NULL);
1417 
1418 	if (view->window != window) {
1419 		view->window = window;
1420                 if (window != NULL)
1421 			view->dirty = TRUE;
1422 	}
1423 }
1424 
1425 /* Redraw a view to window */
textbuffer_view_redraw(TEXT_BUFFER_VIEW_REC * view)1426 void textbuffer_view_redraw(TEXT_BUFFER_VIEW_REC *view)
1427 {
1428 	g_return_if_fail(view != NULL);
1429 
1430 	if (view->window != NULL) {
1431 		view->dirty = FALSE;
1432 		view_draw_top(view, view->height, TRUE);
1433 		term_refresh(view->window);
1434 	}
1435 }
1436 
line_cache_check_remove(void * key,LINE_CACHE_REC * cache,time_t * now)1437 static int line_cache_check_remove(void *key, LINE_CACHE_REC *cache,
1438 				   time_t *now)
1439 {
1440 	if (cache->last_access+LINE_CACHE_KEEP_TIME > *now)
1441 		return FALSE;
1442 
1443 	line_cache_destroy(NULL, cache);
1444 	return TRUE;
1445 }
1446 
sig_check_linecache(void)1447 static int sig_check_linecache(void)
1448 {
1449 	GSList *tmp, *caches;
1450         time_t now;
1451 
1452         now = time(NULL); caches = NULL;
1453 	for (tmp = views; tmp != NULL; tmp = tmp->next) {
1454 		TEXT_BUFFER_VIEW_REC *rec = tmp->data;
1455 
1456 		if (g_slist_find(caches, rec->cache) != NULL)
1457 			continue;
1458 
1459 		caches = g_slist_append(caches, rec->cache);
1460 		g_hash_table_foreach_remove(rec->cache->line_cache,
1461 					    (GHRFunc) line_cache_check_remove,
1462 					    &now);
1463 	}
1464 
1465         g_slist_free(caches);
1466 	return 1;
1467 }
1468 
textbuffer_view_init(void)1469 void textbuffer_view_init(void)
1470 {
1471 	linecache_tag = g_timeout_add(LINE_CACHE_CHECK_TIME, (GSourceFunc) sig_check_linecache, NULL);
1472 }
1473 
textbuffer_view_deinit(void)1474 void textbuffer_view_deinit(void)
1475 {
1476 	g_source_remove(linecache_tag);
1477 }
1478