Highlight search results
[tig.git] / src / draw.c
blob5c3fb33c96236b6f0812e811a276fbb24cf0bcea
1 /* Copyright (c) 2006-2015 Jonas Fonseca <jonas.fonseca@gmail.com>
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
14 #include "tig/tig.h"
15 #include "tig/graph.h"
16 #include "tig/draw.h"
17 #include "tig/options.h"
18 #include "compat/hashtab.h"
20 static const enum line_type palette_colors[] = {
21 LINE_PALETTE_0,
22 LINE_PALETTE_1,
23 LINE_PALETTE_2,
24 LINE_PALETTE_3,
25 LINE_PALETTE_4,
26 LINE_PALETTE_5,
27 LINE_PALETTE_6,
28 LINE_PALETTE_7,
29 LINE_PALETTE_8,
30 LINE_PALETTE_9,
31 LINE_PALETTE_10,
32 LINE_PALETTE_11,
33 LINE_PALETTE_12,
34 LINE_PALETTE_13,
38 * View drawing.
41 static inline void
42 set_view_attr(struct view *view, enum line_type type)
44 if (!view->curline->selected && view->curtype != type) {
45 (void) wattrset(view->win, get_view_attr(view, type));
46 wchgat(view->win, -1, 0, get_view_color(view, type), NULL);
47 view->curtype = type;
51 #define VIEW_MAX_LEN(view) ((view)->width + (view)->pos.col - (view)->col)
53 static bool
54 draw_chars(struct view *view, enum line_type type, const char *string,
55 int max_len, bool use_tilde)
57 int len = 0;
58 int col = 0;
59 int trimmed = false;
60 size_t skip = view->pos.col > view->col ? view->pos.col - view->col : 0;
62 if (max_len <= 0)
63 return VIEW_MAX_LEN(view) <= 0;
65 len = utf8_length(&string, -1, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
67 if (opt_iconv_out != ICONV_NONE) {
68 string = encoding_iconv(opt_iconv_out, string, len);
69 if (!string)
70 return VIEW_MAX_LEN(view) <= 0;
73 set_view_attr(view, type);
74 if (len > 0) {
75 waddnstr(view->win, string, len);
77 if (trimmed && use_tilde) {
78 set_view_attr(view, LINE_DELIMITER);
79 waddch(view->win, '~');
80 col++;
84 view->col += col;
85 return VIEW_MAX_LEN(view) <= 0;
88 static bool
89 draw_space(struct view *view, enum line_type type, int max, int spaces)
91 static char space[] = " ";
93 spaces = MIN(max, spaces);
95 while (spaces > 0) {
96 int len = MIN(spaces, sizeof(space) - 1);
98 if (draw_chars(view, type, space, len, false))
99 return true;
100 spaces -= len;
103 return VIEW_MAX_LEN(view) <= 0;
106 static bool
107 draw_text_expanded(struct view *view, enum line_type type, const char *string, int max_len, bool use_tilde)
109 static char text[SIZEOF_STR];
111 do {
112 size_t pos = string_expand(text, sizeof(text), string, opt_tab_size);
114 if (draw_chars(view, type, text, max_len, use_tilde))
115 return true;
116 string += pos;
117 } while (*string);
119 return VIEW_MAX_LEN(view) <= 0;
122 bool
123 draw_text(struct view *view, enum line_type type, const char *string)
125 return draw_text_expanded(view, type, string, VIEW_MAX_LEN(view), false);
128 static bool
129 draw_text_overflow(struct view *view, const char *text, enum line_type type,
130 int overflow_length, int offset)
132 bool on = overflow_length > 0;
134 if (on) {
135 int overflow = overflow_length + offset;
136 int max = MIN(VIEW_MAX_LEN(view), overflow);
137 const char *tmp = text;
138 int text_width = 0;
139 int trimmed = false;
140 size_t len = utf8_length(&tmp, -1, 0, &text_width, max, &trimmed, false, 1);
142 if (draw_text_expanded(view, type, text, text_width, max < overflow))
143 return true;
145 text += len;
146 type = LINE_OVERFLOW;
149 if (*text && draw_text(view, type, text))
150 return true;
152 return VIEW_MAX_LEN(view) <= 0;
155 bool PRINTF_LIKE(3, 4)
156 draw_formatted(struct view *view, enum line_type type, const char *format, ...)
158 char text[SIZEOF_STR];
159 int retval;
161 FORMAT_BUFFER(text, sizeof(text), format, retval, true);
162 return retval >= 0 ? draw_text(view, type, text) : VIEW_MAX_LEN(view) <= 0;
165 bool
166 draw_graphic(struct view *view, enum line_type type, const chtype graphic[], size_t size, bool separator)
168 size_t skip = view->pos.col > view->col ? view->pos.col - view->col : 0;
169 int max = VIEW_MAX_LEN(view);
170 int i;
172 if (max < size)
173 size = max;
175 set_view_attr(view, type);
176 /* Using waddch() instead of waddnstr() ensures that
177 * they'll be rendered correctly for the cursor line. */
178 for (i = skip; i < size; i++)
179 waddch(view->win, graphic[i]);
181 view->col += size;
182 if (separator) {
183 if (size < max && skip <= size)
184 waddch(view->win, ' ');
185 view->col++;
188 return VIEW_MAX_LEN(view) <= 0;
191 bool
192 draw_field(struct view *view, enum line_type type, const char *text, int width, enum align align, bool trim)
194 int max = MIN(VIEW_MAX_LEN(view), width + 1);
195 int col = view->col;
197 if (!text)
198 return draw_space(view, type, max, max);
200 if (align == ALIGN_RIGHT) {
201 int textlen = utf8_width_max(text, max);
202 int leftpad = max - textlen - 1;
204 if (leftpad > 0) {
205 if (draw_space(view, type, leftpad, leftpad))
206 return true;
207 max -= leftpad;
208 col += leftpad;;
212 return draw_chars(view, type, text, max - 1, trim)
213 || draw_space(view, LINE_DEFAULT, max - (view->col - col), max);
216 static bool
217 draw_date(struct view *view, struct view_column *column, const struct time *time)
219 enum date date = column->opt.date.display;
220 const char *text = mkdate(time, date);
221 enum align align = date == DATE_RELATIVE ? ALIGN_RIGHT : ALIGN_LEFT;
223 if (date == DATE_NO)
224 return false;
226 return draw_field(view, LINE_DATE, text, column->width, align, false);
229 static bool
230 draw_author(struct view *view, struct view_column *column, const struct ident *author)
232 bool trim = author_trim(column->width);
233 const char *text = mkauthor(author, column->opt.author.width, column->opt.author.display);
235 if (column->opt.author.display == AUTHOR_NO)
236 return false;
238 return draw_field(view, LINE_AUTHOR, text, column->width, ALIGN_LEFT, trim);
241 static bool
242 draw_id(struct view *view, struct view_column *column, const char *id)
244 enum line_type type = LINE_ID;
246 if (!column->opt.id.display)
247 return false;
249 if (column->opt.id.color && id) {
250 hashval_t color = iterative_hash(id, SIZEOF_REV - 1, 0);
252 type = palette_colors[color % ARRAY_SIZE(palette_colors)];
255 return draw_field(view, type, id, column->width, ALIGN_LEFT, false);
258 static bool
259 draw_filename(struct view *view, struct view_column *column, const char *filename, mode_t mode)
261 size_t width = filename ? utf8_width(filename) : 0;
262 bool trim = width >= column->width;
263 enum line_type type = S_ISDIR(mode) ? LINE_DIRECTORY : LINE_FILE;
264 int column_width = column->width ? column->width : width;
266 if (column->opt.file_name.display == FILENAME_NO)
267 return false;
269 return draw_field(view, type, filename, column_width, ALIGN_LEFT, trim);
272 static bool
273 draw_file_size(struct view *view, struct view_column *column, unsigned long size, mode_t mode)
275 const char *str = S_ISDIR(mode) ? NULL : mkfilesize(size, column->opt.file_size.display);
277 if (!column->width || column->opt.file_size.display == FILE_SIZE_NO)
278 return false;
280 return draw_field(view, LINE_FILE_SIZE, str, column->width, ALIGN_RIGHT, false);
283 static bool
284 draw_mode(struct view *view, struct view_column *column, mode_t mode)
286 const char *str = mkmode(mode);
288 if (!column->width || !column->opt.mode.display)
289 return false;
291 return draw_field(view, LINE_MODE, str, column->width, ALIGN_LEFT, false);
294 static bool
295 draw_lineno_custom(struct view *view, struct view_column *column, unsigned int lineno)
297 char number[10];
298 unsigned long digits3 = column->width < 3 ? 3 : column->width;
299 int max = MIN(VIEW_MAX_LEN(view), digits3);
300 char *text = NULL;
301 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
302 struct line_number_options *opts = &column->opt.line_number;
303 int interval = opts->interval > 0 ? opts->interval : 5;
305 if (!column->opt.line_number.display)
306 return false;
308 if (lineno == 1 || (lineno % interval) == 0) {
309 static char fmt[] = "%ld";
311 fmt[1] = '0' + (digits3 <= 9 ? digits3 : 1);
312 if (string_format(number, fmt, lineno))
313 text = number;
315 if (text)
316 draw_chars(view, LINE_LINE_NUMBER, text, max, true);
317 else
318 draw_space(view, LINE_LINE_NUMBER, max, digits3);
319 return draw_graphic(view, LINE_DEFAULT, &separator, 1, true);
322 bool
323 draw_lineno(struct view *view, struct view_column *column, unsigned int lineno)
325 lineno += view->pos.offset + 1;
326 return draw_lineno_custom(view, column, lineno);
329 static bool
330 draw_ref(struct view *view, struct view_column *column, const struct ref *ref)
332 enum line_type type = !ref || !ref->valid ? LINE_DEFAULT : get_line_type_from_ref(ref);
333 const char *name = ref ? ref->name : NULL;
335 return draw_field(view, type, name, column->width, ALIGN_LEFT, false);
338 static bool
339 draw_refs(struct view *view, struct view_column *column, const struct ref *refs)
341 if (!column->opt.commit_title.refs || !refs)
342 return false;
344 for (; refs; refs = refs->next) {
345 const struct ref *ref = refs;
346 enum line_type type = get_line_type_from_ref(ref);
347 const struct ref_format *format = get_ref_format(opt_reference_format, ref);
349 if (!strcmp(format->start, "hide:") && !*format->end)
350 continue;
352 if (draw_formatted(view, type, "%s%s%s", format->start, ref->name, format->end))
353 return true;
355 if (draw_text(view, LINE_DEFAULT, " "))
356 return true;
359 return false;
362 static bool
363 draw_status(struct view *view, struct view_column *column,
364 enum line_type type, const char *status)
366 const char *label = mkstatus(status ? *status : 0, column->opt.status.display);
368 return draw_field(view, type, label, column->width, ALIGN_LEFT, false);
372 * Revision graph
375 static inline enum line_type
376 get_graph_color(int color_id)
378 if (color_id == GRAPH_COMMIT_COLOR)
379 return LINE_GRAPH_COMMIT;
380 assert(color_id < ARRAY_SIZE(palette_colors));
381 return palette_colors[color_id];
384 static bool
385 draw_graph_utf8(void *view, const struct graph *graph, const struct graph_symbol *symbol, int color_id, bool first)
387 const char *chars = graph->symbol_to_utf8(symbol);
389 return draw_text(view, get_graph_color(color_id), chars + !!first);
392 static bool
393 draw_graph_ascii(void *view, const struct graph *graph, const struct graph_symbol *symbol, int color_id, bool first)
395 const char *chars = graph->symbol_to_ascii(symbol);
397 return draw_text(view, get_graph_color(color_id), chars + !!first);
400 static bool
401 draw_graph_chtype(void *view, const struct graph *graph, const struct graph_symbol *symbol, int color_id, bool first)
403 const chtype *chars = graph->symbol_to_chtype(symbol);
405 return draw_graphic(view, get_graph_color(color_id), chars + !!first, 2 - !!first, false);
408 static bool
409 draw_graph(struct view *view, const struct graph *graph, const struct graph_canvas *canvas)
411 static const graph_symbol_iterator_fn fns[] = {
412 draw_graph_ascii,
413 draw_graph_chtype,
414 draw_graph_utf8
417 graph->foreach_symbol(graph, canvas, fns[opt_line_graphics], view);
418 return draw_text(view, LINE_DEFAULT, " ");
421 static bool
422 draw_commit_title(struct view *view, struct view_column *column,
423 const struct graph *graph, const struct graph_canvas *graph_canvas,
424 const struct ref *refs, const char *commit_title)
426 if (graph && graph_canvas && column->opt.commit_title.graph &&
427 draw_graph(view, graph, graph_canvas))
428 return true;
429 if (draw_refs(view, column, refs))
430 return true;
431 return draw_text_overflow(view, commit_title, LINE_DEFAULT,
432 column->opt.commit_title.overflow, 0);
435 static bool
436 draw_diff_stat_part(struct view *view, enum line_type *type, const char **text, char c, enum line_type next_type)
438 const char *sep = c == '|' ? strrchr(*text, c) : strchr(*text, c);
440 if (sep != NULL) {
441 draw_text_expanded(view, *type, *text, sep - *text, false);
442 *text = sep;
443 *type = next_type;
446 return sep != NULL;
449 static void
450 draw_diff_stat(struct view *view, enum line_type *type, const char **text)
452 draw_diff_stat_part(view, type, text, '|', LINE_DEFAULT);
453 if (draw_diff_stat_part(view, type, text, 'B', LINE_DEFAULT)) {
454 /* Handle binary diffstat: Bin <deleted> -> <added> bytes */
455 draw_diff_stat_part(view, type, text, ' ', LINE_DIFF_DEL);
456 draw_diff_stat_part(view, type, text, '-', LINE_DEFAULT);
457 draw_diff_stat_part(view, type, text, ' ', LINE_DIFF_ADD);
458 draw_diff_stat_part(view, type, text, 'b', LINE_DEFAULT);
460 } else {
461 draw_diff_stat_part(view, type, text, '+', LINE_DIFF_ADD);
462 draw_diff_stat_part(view, type, text, '-', LINE_DIFF_DEL);
466 bool
467 view_column_draw(struct view *view, struct line *line, unsigned int lineno)
469 struct view_column *column = view->columns;
470 struct view_column_data column_data = {0};
472 if (!view->ops->get_column_data(view, line, &column_data))
473 return true;
475 if (column_data.section)
476 column = column_data.section;
478 for (; column; column = column->next) {
479 mode_t mode = column_data.mode ? *column_data.mode : 0;
481 if (column->hidden)
482 continue;
484 switch (column->type) {
485 case VIEW_COLUMN_DATE:
486 if (draw_date(view, column, column_data.date))
487 return true;
488 continue;
490 case VIEW_COLUMN_AUTHOR:
491 if (draw_author(view, column, column_data.author))
492 return true;
493 continue;
495 case VIEW_COLUMN_REF:
496 if (draw_ref(view, column, column_data.ref))
497 return true;
498 continue;
500 case VIEW_COLUMN_ID:
501 if (draw_id(view, column, column_data.reflog ? column_data.reflog : column_data.id))
502 return true;
503 continue;
505 case VIEW_COLUMN_LINE_NUMBER:
506 if (draw_lineno(view, column, column_data.line_number ? *column_data.line_number : lineno))
507 return true;
508 continue;
510 case VIEW_COLUMN_MODE:
511 if (draw_mode(view, column, mode))
512 return true;
513 continue;
515 case VIEW_COLUMN_FILE_SIZE:
516 if (draw_file_size(view, column, column_data.file_size ? *column_data.file_size : 0, mode))
517 return true;
518 continue;
520 case VIEW_COLUMN_COMMIT_TITLE:
521 if (draw_commit_title(view, column, column_data.graph, column_data.graph_canvas,
522 column_data.refs, column_data.commit_title))
523 return true;
524 continue;
526 case VIEW_COLUMN_FILE_NAME:
527 if (draw_filename(view, column, column_data.file_name, mode))
528 return true;
529 continue;
531 case VIEW_COLUMN_SECTION:
532 if (draw_text(view, column->opt.section.type, column->opt.section.text))
533 return true;
534 continue;
536 case VIEW_COLUMN_STATUS:
537 if (draw_status(view, column, line->type, column_data.status))
538 return true;
539 continue;
541 case VIEW_COLUMN_TEXT:
543 enum line_type type = line->type;
544 const char *text = column_data.text;
546 if (line->wrapped && draw_text(view, LINE_DELIMITER, "+"))
547 return true;
549 if (line->graph_indent) {
550 size_t indent = get_graph_indent(text);
552 if (draw_text_expanded(view, LINE_DEFAULT, text, indent, false))
553 return true;
554 text += indent;
556 if (type == LINE_DIFF_STAT)
557 draw_diff_stat(view, &type, &text);
558 if (line->commit_title) {
559 if (draw_text_overflow(view, text, LINE_DEFAULT,
560 column->opt.text.commit_title_overflow, 4))
561 return true;
562 } else if (draw_text(view, type, text)) {
563 return true;
566 continue;
570 return true;
573 static void
574 draw_view_line_search_result(struct view *view, unsigned int lineno)
576 size_t bufsize = view->width * 4;
577 char *buf = malloc(bufsize);
578 regmatch_t pmatch[1];
579 int i;
581 if (!buf || mvwinnstr(view->win, lineno, 0, buf, bufsize) == ERR ||
582 regexec(view->regex, buf, ARRAY_SIZE(pmatch), pmatch, 0)) {
583 free(buf);
584 return;
587 for (i = 0; i < ARRAY_SIZE(pmatch); i++) {
588 regoff_t start = pmatch[i].rm_so;
590 if (start == -1)
591 continue;
593 mvwchgat(view->win, lineno,
594 utf8_width_of(buf, start, -1),
595 utf8_width_of(buf + start, pmatch[i].rm_eo - start, -1),
596 get_view_attr(view, LINE_SEARCH_RESULT),
597 get_view_color(view, LINE_SEARCH_RESULT),
598 NULL);
601 free(buf);
604 bool
605 draw_view_line(struct view *view, unsigned int lineno)
607 struct line *line;
608 bool selected = (view->pos.offset + lineno == view->pos.lineno);
609 bool ok;
611 /* FIXME: Disabled during code split.
612 assert(view_is_displayed(view));
615 if (view->pos.offset + lineno >= view->lines)
616 return false;
618 line = &view->line[view->pos.offset + lineno];
620 wmove(view->win, lineno, 0);
621 if (line->cleareol)
622 wclrtoeol(view->win);
623 view->col = 0;
624 view->curline = line;
625 view->curtype = LINE_NONE;
626 line->selected = false;
627 line->dirty = line->cleareol = 0;
629 if (selected) {
630 set_view_attr(view, LINE_CURSOR);
631 line->selected = true;
632 view->ops->select(view, line);
635 ok = view->ops->draw(view, line, lineno);
637 if (ok && line->search_result && view->regex)
638 draw_view_line_search_result(view, lineno);
640 return ok;
643 void
644 redraw_view_dirty(struct view *view)
646 bool dirty = false;
647 int lineno;
649 for (lineno = 0; lineno < view->height; lineno++) {
650 if (view->pos.offset + lineno >= view->lines)
651 break;
652 if (!view->line[view->pos.offset + lineno].dirty)
653 continue;
654 dirty = true;
655 if (!draw_view_line(view, lineno))
656 break;
659 if (!dirty)
660 return;
661 wnoutrefresh(view->win);
664 void
665 redraw_view_from(struct view *view, int lineno)
667 assert(0 <= lineno && lineno < view->height);
669 if (view->columns && view_column_info_changed(view, false)) {
670 int i;
672 view_column_reset(view);
673 for (i = 0; i < view->lines; i++) {
674 view_column_info_update(view, &view->line[i]);
678 for (; lineno < view->height; lineno++) {
679 if (!draw_view_line(view, lineno))
680 break;
683 wnoutrefresh(view->win);
686 void
687 redraw_view(struct view *view)
689 werase(view->win);
690 redraw_view_from(view, 0);
693 /* vim: set ts=8 sw=8 noexpandtab: */