Add support for custom strftime(3) date formats
[tig.git] / src / draw.c
blob0e63684e0237b2e4eb4d62f6ec228e72429601f2
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, int length,
55 int max_width, 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_width <= 0)
63 return VIEW_MAX_LEN(view) <= 0;
65 len = utf8_length(&string, length, skip, &col, max_width, &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, -1, 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 length, int max_width, bool use_tilde)
109 static char text[SIZEOF_STR];
111 if (length == -1)
112 length = strlen(string);
114 do {
115 size_t pos = string_expand(text, sizeof(text), string, length, opt_tab_size);
117 if (draw_chars(view, type, text, -1, max_width, use_tilde))
118 return true;
119 string += pos;
120 length -= pos;
121 } while (*string && length > 0);
123 return VIEW_MAX_LEN(view) <= 0;
126 static inline bool
127 draw_textn(struct view *view, enum line_type type, const char *string, int length)
129 return draw_text_expanded(view, type, string, length, VIEW_MAX_LEN(view), false);
132 bool
133 draw_text(struct view *view, enum line_type type, const char *string)
135 return draw_textn(view, type, string, -1);
138 static bool
139 draw_text_overflow(struct view *view, const char *text, enum line_type type,
140 int overflow_length, int offset)
142 bool on = overflow_length > 0;
144 if (on) {
145 int overflow = overflow_length + offset;
146 int max = MIN(VIEW_MAX_LEN(view), overflow);
147 const char *tmp = text;
148 int text_width = 0;
149 int trimmed = false;
150 size_t len = utf8_length(&tmp, -1, 0, &text_width, max, &trimmed, false, 1);
152 if (draw_text_expanded(view, type, text, -1, text_width, max < overflow))
153 return true;
155 text += len;
156 type = LINE_OVERFLOW;
159 if (*text && draw_text(view, type, text))
160 return true;
162 return VIEW_MAX_LEN(view) <= 0;
165 bool PRINTF_LIKE(3, 4)
166 draw_formatted(struct view *view, enum line_type type, const char *format, ...)
168 char text[SIZEOF_STR];
169 int retval;
171 FORMAT_BUFFER(text, sizeof(text), format, retval, true);
172 return retval >= 0 ? draw_text(view, type, text) : VIEW_MAX_LEN(view) <= 0;
175 bool
176 draw_graphic(struct view *view, enum line_type type, const chtype graphic[], size_t size, bool separator)
178 size_t skip = view->pos.col > view->col ? view->pos.col - view->col : 0;
179 int max = VIEW_MAX_LEN(view);
180 int i;
182 if (max < size)
183 size = max;
185 set_view_attr(view, type);
186 /* Using waddch() instead of waddnstr() ensures that
187 * they'll be rendered correctly for the cursor line. */
188 for (i = skip; i < size; i++)
189 waddch(view->win, graphic[i]);
191 view->col += size;
192 if (separator) {
193 if (size < max && skip <= size)
194 waddch(view->win, ' ');
195 view->col++;
198 return VIEW_MAX_LEN(view) <= 0;
201 bool
202 draw_field(struct view *view, enum line_type type, const char *text, int width, enum align align, bool trim)
204 int max = MIN(VIEW_MAX_LEN(view), width + 1);
205 int col = view->col;
207 if (!text)
208 return draw_space(view, type, max, max);
210 if (align == ALIGN_RIGHT) {
211 int textlen = utf8_width_max(text, max);
212 int leftpad = max - textlen - 1;
214 if (leftpad > 0) {
215 if (draw_space(view, type, leftpad, leftpad))
216 return true;
217 max -= leftpad;
218 col += leftpad;;
222 return draw_chars(view, type, text, -1, max - 1, trim)
223 || draw_space(view, type, max - (view->col - col), max);
226 static bool
227 draw_date(struct view *view, struct view_column *column, const struct time *time)
229 struct date_options *opt = &column->opt.date;
230 enum date date = opt->display;
231 const char *text = mkdate(time, date, opt->local, opt->format);
232 enum align align = date == DATE_RELATIVE ? ALIGN_RIGHT : ALIGN_LEFT;
234 if (date == DATE_NO)
235 return false;
237 return draw_field(view, LINE_DATE, text, column->width, align, false);
240 static bool
241 draw_author(struct view *view, struct view_column *column, const struct ident *author)
243 bool trim = author_trim(column->width);
244 const char *text = mkauthor(author, column->opt.author.width, column->opt.author.display);
246 if (column->opt.author.display == AUTHOR_NO)
247 return false;
249 return draw_field(view, LINE_AUTHOR, text, column->width, ALIGN_LEFT, trim);
252 static bool
253 draw_id(struct view *view, struct view_column *column, const char *id)
255 enum line_type type = LINE_ID;
257 if (!column->opt.id.display)
258 return false;
260 if (column->opt.id.color && id) {
261 hashval_t color = iterative_hash(id, SIZEOF_REV - 1, 0);
263 type = palette_colors[color % ARRAY_SIZE(palette_colors)];
266 return draw_field(view, type, id, column->width, ALIGN_LEFT, false);
269 static bool
270 draw_filename(struct view *view, struct view_column *column, const char *filename, mode_t mode)
272 size_t width = filename ? utf8_width(filename) : 0;
273 bool trim = width >= column->width;
274 enum line_type type = S_ISDIR(mode) ? LINE_DIRECTORY : LINE_FILE;
275 int column_width = column->width ? column->width : width;
277 if (column->opt.file_name.display == FILENAME_NO)
278 return false;
280 return draw_field(view, type, filename, column_width, ALIGN_LEFT, trim);
283 static bool
284 draw_file_size(struct view *view, struct view_column *column, unsigned long size, mode_t mode)
286 const char *str = S_ISDIR(mode) ? NULL : mkfilesize(size, column->opt.file_size.display);
288 if (!column->width || column->opt.file_size.display == FILE_SIZE_NO)
289 return false;
291 return draw_field(view, LINE_FILE_SIZE, str, column->width, ALIGN_RIGHT, false);
294 static bool
295 draw_mode(struct view *view, struct view_column *column, mode_t mode)
297 const char *str = mkmode(mode);
299 if (!column->width || !column->opt.mode.display)
300 return false;
302 return draw_field(view, LINE_MODE, str, column->width, ALIGN_LEFT, false);
305 static bool
306 draw_lineno_custom(struct view *view, struct view_column *column, unsigned int lineno)
308 char number[10];
309 unsigned long digits3 = column->width < 3 ? 3 : column->width;
310 int max = MIN(VIEW_MAX_LEN(view), digits3);
311 char *text = NULL;
312 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
313 struct line_number_options *opts = &column->opt.line_number;
314 int interval = opts->interval > 0 ? opts->interval : 5;
316 if (!column->opt.line_number.display)
317 return false;
319 if (lineno == 1 || (lineno % interval) == 0) {
320 static char fmt[] = "%ld";
322 fmt[1] = '0' + (digits3 <= 9 ? digits3 : 1);
323 if (string_format(number, fmt, lineno))
324 text = number;
326 if (text)
327 draw_chars(view, LINE_LINE_NUMBER, text, -1, max, true);
328 else
329 draw_space(view, LINE_LINE_NUMBER, max, digits3);
330 return draw_graphic(view, LINE_DEFAULT, &separator, 1, true);
333 bool
334 draw_lineno(struct view *view, struct view_column *column, unsigned int lineno)
336 lineno += view->pos.offset + 1;
337 return draw_lineno_custom(view, column, lineno);
340 static bool
341 draw_ref(struct view *view, struct view_column *column, const struct ref *ref)
343 enum line_type type = !ref || !ref->valid ? LINE_DEFAULT : get_line_type_from_ref(ref);
344 const char *name = ref ? ref->name : NULL;
346 return draw_field(view, type, name, column->width, ALIGN_LEFT, false);
349 static bool
350 draw_refs(struct view *view, struct view_column *column, const struct ref *refs)
352 if (!column->opt.commit_title.refs || !refs)
353 return false;
355 for (; refs; refs = refs->next) {
356 const struct ref *ref = refs;
357 enum line_type type = get_line_type_from_ref(ref);
358 const struct ref_format *format = get_ref_format(opt_reference_format, ref);
360 if (!strcmp(format->start, "hide:") && !*format->end)
361 continue;
363 if (draw_formatted(view, type, "%s%s%s", format->start, ref->name, format->end))
364 return true;
366 if (draw_text(view, LINE_DEFAULT, " "))
367 return true;
370 return false;
373 static bool
374 draw_status(struct view *view, struct view_column *column,
375 enum line_type type, const char *status)
377 const char *label = mkstatus(status ? *status : 0, column->opt.status.display);
379 return draw_field(view, type, label, column->width, ALIGN_LEFT, false);
383 * Revision graph
386 static inline enum line_type
387 get_graph_color(int color_id)
389 if (color_id == GRAPH_COMMIT_COLOR)
390 return LINE_GRAPH_COMMIT;
391 assert(color_id < ARRAY_SIZE(palette_colors));
392 return palette_colors[color_id];
395 static bool
396 draw_graph_utf8(void *view, const struct graph *graph, const struct graph_symbol *symbol, int color_id, bool first)
398 const char *chars = graph->symbol_to_utf8(symbol);
400 return draw_text(view, get_graph_color(color_id), chars + !!first);
403 static bool
404 draw_graph_ascii(void *view, const struct graph *graph, const struct graph_symbol *symbol, int color_id, bool first)
406 const char *chars = graph->symbol_to_ascii(symbol);
408 return draw_text(view, get_graph_color(color_id), chars + !!first);
411 static bool
412 draw_graph_chtype(void *view, const struct graph *graph, const struct graph_symbol *symbol, int color_id, bool first)
414 const chtype *chars = graph->symbol_to_chtype(symbol);
416 return draw_graphic(view, get_graph_color(color_id), chars + !!first, 2 - !!first, false);
419 static bool
420 draw_graph(struct view *view, const struct graph *graph, const struct graph_canvas *canvas)
422 static const graph_symbol_iterator_fn fns[] = {
423 draw_graph_ascii,
424 draw_graph_chtype,
425 draw_graph_utf8
428 graph->foreach_symbol(graph, canvas, fns[opt_line_graphics], view);
429 return draw_text(view, LINE_DEFAULT, " ");
432 static bool
433 draw_commit_title(struct view *view, struct view_column *column,
434 const struct graph *graph, const struct graph_canvas *graph_canvas,
435 const struct ref *refs, const char *commit_title)
437 if (graph && graph_canvas && column->opt.commit_title.graph &&
438 draw_graph(view, graph, graph_canvas))
439 return true;
440 if (draw_refs(view, column, refs))
441 return true;
442 return draw_text_overflow(view, commit_title, LINE_DEFAULT,
443 column->opt.commit_title.overflow, 0);
446 bool
447 view_column_draw(struct view *view, struct line *line, unsigned int lineno)
449 struct view_column *column = view->columns;
450 struct view_column_data column_data = {0};
452 if (!view->ops->get_column_data(view, line, &column_data))
453 return true;
455 if (column_data.section)
456 column = column_data.section;
458 for (; column; column = column->next) {
459 mode_t mode = column_data.mode ? *column_data.mode : 0;
461 if (column->hidden)
462 continue;
464 switch (column->type) {
465 case VIEW_COLUMN_DATE:
466 if (draw_date(view, column, column_data.date))
467 return true;
468 continue;
470 case VIEW_COLUMN_AUTHOR:
471 if (draw_author(view, column, column_data.author))
472 return true;
473 continue;
475 case VIEW_COLUMN_REF:
476 if (draw_ref(view, column, column_data.ref))
477 return true;
478 continue;
480 case VIEW_COLUMN_ID:
481 if (draw_id(view, column, column_data.reflog ? column_data.reflog : column_data.id))
482 return true;
483 continue;
485 case VIEW_COLUMN_LINE_NUMBER:
486 if (draw_lineno(view, column, column_data.line_number ? *column_data.line_number : lineno))
487 return true;
488 continue;
490 case VIEW_COLUMN_MODE:
491 if (draw_mode(view, column, mode))
492 return true;
493 continue;
495 case VIEW_COLUMN_FILE_SIZE:
496 if (draw_file_size(view, column, column_data.file_size ? *column_data.file_size : 0, mode))
497 return true;
498 continue;
500 case VIEW_COLUMN_COMMIT_TITLE:
501 if (draw_commit_title(view, column, column_data.graph, column_data.graph_canvas,
502 column_data.refs, column_data.commit_title))
503 return true;
504 continue;
506 case VIEW_COLUMN_FILE_NAME:
507 if (draw_filename(view, column, column_data.file_name, mode))
508 return true;
509 continue;
511 case VIEW_COLUMN_SECTION:
512 if (draw_text(view, column->opt.section.type, column->opt.section.text))
513 return true;
514 continue;
516 case VIEW_COLUMN_STATUS:
517 if (draw_status(view, column, line->type, column_data.status))
518 return true;
519 continue;
521 case VIEW_COLUMN_TEXT:
523 enum line_type type = line->type;
524 const char *text = column_data.text;
526 if (line->wrapped && draw_text(view, LINE_DELIMITER, "+"))
527 return true;
529 if (line->graph_indent) {
530 size_t indent = get_graph_indent(text);
532 if (draw_text_expanded(view, LINE_DEFAULT, text, -1, indent, false))
533 return true;
534 text += indent;
537 if (line->commit_title) {
538 if (draw_text_overflow(view, text, LINE_DEFAULT,
539 column->opt.text.commit_title_overflow, 4))
540 return true;
542 } else if (column_data.box) {
543 const struct box *box = column_data.box;
544 const char *text = box->text;
545 size_t i;
547 for (i = 0; i < box->cells; i++) {
548 const struct box_cell *cell = &box->cell[i];
550 if (draw_textn(view, cell->type, text, cell->length))
551 return true;
553 text += cell->length;
556 } else if (draw_text(view, type, text)) {
557 return true;
560 continue;
564 return true;
567 static void
568 draw_view_line_search_result(struct view *view, unsigned int lineno)
570 size_t bufsize = view->width * 4;
571 char *buf = malloc(bufsize);
572 regmatch_t pmatch[1];
573 int i;
575 if (!buf || mvwinnstr(view->win, lineno, 0, buf, bufsize) == ERR ||
576 regexec(view->regex, buf, ARRAY_SIZE(pmatch), pmatch, 0)) {
577 free(buf);
578 return;
581 for (i = 0; i < ARRAY_SIZE(pmatch); i++) {
582 regoff_t start = pmatch[i].rm_so;
584 if (start == -1)
585 continue;
587 mvwchgat(view->win, lineno,
588 utf8_width_of(buf, start, -1),
589 utf8_width_of(buf + start, pmatch[i].rm_eo - start, -1),
590 get_view_attr(view, LINE_SEARCH_RESULT),
591 get_view_color(view, LINE_SEARCH_RESULT),
592 NULL);
595 free(buf);
598 bool
599 draw_view_line(struct view *view, unsigned int lineno)
601 struct line *line;
602 bool selected = (view->pos.offset + lineno == view->pos.lineno);
603 bool ok;
605 /* FIXME: Disabled during code split.
606 assert(view_is_displayed(view));
609 if (view->pos.offset + lineno >= view->lines)
610 return false;
612 line = &view->line[view->pos.offset + lineno];
614 wmove(view->win, lineno, 0);
615 if (line->cleareol)
616 wclrtoeol(view->win);
617 view->col = 0;
618 view->curline = line;
619 view->curtype = LINE_NONE;
620 line->selected = false;
621 line->dirty = line->cleareol = 0;
623 if (selected) {
624 set_view_attr(view, LINE_CURSOR);
625 line->selected = true;
626 view->ops->select(view, line);
629 ok = view->ops->draw(view, line, lineno);
631 if (ok && line->search_result && view->regex)
632 draw_view_line_search_result(view, lineno);
634 return ok;
637 void
638 redraw_view_dirty(struct view *view)
640 bool dirty = false;
641 int lineno;
643 for (lineno = 0; lineno < view->height; lineno++) {
644 if (view->pos.offset + lineno >= view->lines)
645 break;
646 if (!view->line[view->pos.offset + lineno].dirty)
647 continue;
648 dirty = true;
649 if (!draw_view_line(view, lineno))
650 break;
653 if (!dirty)
654 return;
655 wnoutrefresh(view->win);
658 void
659 redraw_view_from(struct view *view, int lineno)
661 assert(0 <= lineno && lineno < view->height);
663 if (view->columns && view_column_info_changed(view, false)) {
664 int i;
666 view_column_reset(view);
667 for (i = 0; i < view->lines; i++) {
668 view_column_info_update(view, &view->line[i]);
672 for (; lineno < view->height; lineno++) {
673 if (!draw_view_line(view, lineno))
674 break;
677 wnoutrefresh(view->win);
680 void
681 redraw_view(struct view *view)
683 werase(view->win);
684 redraw_view_from(view, 0);
687 /* vim: set ts=8 sw=8 noexpandtab: */