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