Fix readline prompt to correctly handle UTF-8 characters
[tig.git] / src / draw.c
blob0bfcc95387902ac8549237d1c8d204eee3a9a2b1
1 /* Copyright (c) 2006-2014 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"
20 * View drawing.
23 static inline void
24 set_view_attr(struct view *view, enum line_type type)
26 if (!view->curline->selected && view->curtype != type) {
27 (void) wattrset(view->win, get_view_attr(view, type));
28 wchgat(view->win, -1, 0, get_view_color(view, type), NULL);
29 view->curtype = type;
33 #define VIEW_MAX_LEN(view) ((view)->width + (view)->pos.col - (view)->col)
35 static bool
36 draw_chars(struct view *view, enum line_type type, const char *string,
37 int max_len, bool use_tilde)
39 int len = 0;
40 int col = 0;
41 int trimmed = FALSE;
42 size_t skip = view->pos.col > view->col ? view->pos.col - view->col : 0;
44 if (max_len <= 0)
45 return VIEW_MAX_LEN(view) <= 0;
47 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
49 if (opt_iconv_out != ICONV_NONE) {
50 string = encoding_iconv(opt_iconv_out, string, len);
51 if (!string)
52 return VIEW_MAX_LEN(view) <= 0;
55 set_view_attr(view, type);
56 if (len > 0) {
57 waddnstr(view->win, string, len);
59 if (trimmed && use_tilde) {
60 set_view_attr(view, LINE_DELIMITER);
61 waddch(view->win, '~');
62 col++;
66 view->col += col;
67 return VIEW_MAX_LEN(view) <= 0;
70 static bool
71 draw_space(struct view *view, enum line_type type, int max, int spaces)
73 static char space[] = " ";
75 spaces = MIN(max, spaces);
77 while (spaces > 0) {
78 int len = MIN(spaces, sizeof(space) - 1);
80 if (draw_chars(view, type, space, len, FALSE))
81 return TRUE;
82 spaces -= len;
85 return VIEW_MAX_LEN(view) <= 0;
88 static bool
89 draw_text_expanded(struct view *view, enum line_type type, const char *string, int max_len, bool use_tilde)
91 static char text[SIZEOF_STR];
93 do {
94 size_t pos = string_expand(text, sizeof(text), string, opt_tab_size);
96 if (draw_chars(view, type, text, max_len, use_tilde))
97 return TRUE;
98 string += pos;
99 } while (*string);
101 return VIEW_MAX_LEN(view) <= 0;
104 bool
105 draw_text(struct view *view, enum line_type type, const char *string)
107 return draw_text_expanded(view, type, string, VIEW_MAX_LEN(view), FALSE);
110 static bool
111 draw_text_overflow(struct view *view, const char *text, enum line_type type,
112 int overflow_length, int offset)
114 bool on = overflow_length > 0;
116 if (on) {
117 int overflow = overflow_length + offset;
118 int max = MIN(VIEW_MAX_LEN(view), overflow);
119 int len = strlen(text);
121 if (draw_text_expanded(view, type, text, max, max < overflow))
122 return TRUE;
124 text = len > overflow ? text + overflow : "";
125 type = LINE_OVERFLOW;
128 if (*text && draw_text(view, type, text))
129 return TRUE;
131 return VIEW_MAX_LEN(view) <= 0;
134 bool PRINTF_LIKE(3, 4)
135 draw_formatted(struct view *view, enum line_type type, const char *format, ...)
137 char text[SIZEOF_STR];
138 int retval;
140 FORMAT_BUFFER(text, sizeof(text), format, retval, TRUE);
141 return retval >= 0 ? draw_text(view, type, text) : VIEW_MAX_LEN(view) <= 0;
144 bool
145 draw_graphic(struct view *view, enum line_type type, const chtype graphic[], size_t size, bool separator)
147 size_t skip = view->pos.col > view->col ? view->pos.col - view->col : 0;
148 int max = VIEW_MAX_LEN(view);
149 int i;
151 if (max < size)
152 size = max;
154 set_view_attr(view, type);
155 /* Using waddch() instead of waddnstr() ensures that
156 * they'll be rendered correctly for the cursor line. */
157 for (i = skip; i < size; i++)
158 waddch(view->win, graphic[i]);
160 view->col += size;
161 if (separator) {
162 if (size < max && skip <= size)
163 waddch(view->win, ' ');
164 view->col++;
167 return VIEW_MAX_LEN(view) <= 0;
170 bool
171 draw_field(struct view *view, enum line_type type, const char *text, int width, enum align align, bool trim)
173 int max = MIN(VIEW_MAX_LEN(view), width + 1);
174 int col = view->col;
176 if (!text)
177 return draw_space(view, type, max, max);
179 if (align == ALIGN_RIGHT) {
180 int textlen = utf8_width_max(text, max);
181 int leftpad = max - textlen - 1;
183 if (leftpad > 0) {
184 if (draw_space(view, type, leftpad, leftpad))
185 return TRUE;
186 max -= leftpad;
187 col += leftpad;;
191 return draw_chars(view, type, text, max - 1, trim)
192 || draw_space(view, LINE_DEFAULT, max - (view->col - col), max);
195 static bool
196 draw_date(struct view *view, struct view_column *column, const struct time *time)
198 enum date date = column->opt.date.display;
199 const char *text = mkdate(time, date);
200 enum align align = date == DATE_RELATIVE ? ALIGN_RIGHT : ALIGN_LEFT;
202 if (date == DATE_NO)
203 return FALSE;
205 return draw_field(view, LINE_DATE, text, column->width, align, FALSE);
208 static bool
209 draw_author(struct view *view, struct view_column *column, const struct ident *author)
211 bool trim = author_trim(column->width);
212 const char *text = mkauthor(author, column->opt.author.width, column->opt.author.display);
214 if (column->opt.author.display == AUTHOR_NO)
215 return FALSE;
217 return draw_field(view, LINE_AUTHOR, text, column->width, ALIGN_LEFT, trim);
220 static bool
221 draw_id(struct view *view, struct view_column *column, const char *id)
223 static const enum line_type colors[] = {
224 LINE_PALETTE_0,
225 LINE_PALETTE_1,
226 LINE_PALETTE_2,
227 LINE_PALETTE_3,
228 LINE_PALETTE_4,
229 LINE_PALETTE_5,
230 LINE_PALETTE_6,
232 enum line_type type = LINE_ID;
234 if (!column->opt.id.display)
235 return FALSE;
237 if (column->opt.id.color)
238 type = colors[((long) id) % ARRAY_SIZE(colors)];
240 return draw_field(view, type, id, column->width, ALIGN_LEFT, FALSE);
243 static bool
244 draw_filename(struct view *view, struct view_column *column, const char *filename, mode_t mode)
246 size_t width = filename ? utf8_width(filename) : 0;
247 bool trim = width >= column->width;
248 enum line_type type = S_ISDIR(mode) ? LINE_DIRECTORY : LINE_FILE;
249 int column_width = column->width ? column->width : width;
251 if (column->opt.file_name.display == FILENAME_NO)
252 return FALSE;
254 return draw_field(view, type, filename, column_width, ALIGN_LEFT, trim);
257 static bool
258 draw_file_size(struct view *view, struct view_column *column, unsigned long size, mode_t mode)
260 const char *str = S_ISDIR(mode) ? NULL : mkfilesize(size, column->opt.file_size.display);
262 if (!column->width || column->opt.file_size.display == FILE_SIZE_NO)
263 return FALSE;
265 return draw_field(view, LINE_FILE_SIZE, str, column->width, ALIGN_RIGHT, FALSE);
268 static bool
269 draw_mode(struct view *view, struct view_column *column, mode_t mode)
271 const char *str = mkmode(mode);
273 if (!column->width || !column->opt.mode.display)
274 return FALSE;
276 return draw_field(view, LINE_MODE, str, column->width, ALIGN_LEFT, FALSE);
279 static bool
280 draw_lineno_custom(struct view *view, struct view_column *column, unsigned int lineno)
282 char number[10];
283 unsigned long digits3 = column->width < 3 ? 3 : column->width;
284 int max = MIN(VIEW_MAX_LEN(view), digits3);
285 char *text = NULL;
286 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
288 if (!column->opt.line_number.display)
289 return FALSE;
291 if (lineno == 1 || (lineno % column->opt.line_number.interval) == 0) {
292 static char fmt[] = "%ld";
294 fmt[1] = '0' + (digits3 <= 9 ? digits3 : 1);
295 if (string_format(number, fmt, lineno))
296 text = number;
298 if (text)
299 draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
300 else
301 draw_space(view, LINE_LINE_NUMBER, max, digits3);
302 return draw_graphic(view, LINE_DEFAULT, &separator, 1, TRUE);
305 bool
306 draw_lineno(struct view *view, struct view_column *column, unsigned int lineno)
308 lineno += view->pos.offset + 1;
309 return draw_lineno_custom(view, column, lineno);
312 static bool
313 draw_ref(struct view *view, struct view_column *column, const struct ref *ref)
315 enum line_type type = !ref || !ref->valid ? LINE_DEFAULT : get_line_type_from_ref(ref);
316 const char *name = ref ? ref->name : NULL;
318 return draw_field(view, type, name, column->width, ALIGN_LEFT, FALSE);
321 static bool
322 draw_refs(struct view *view, struct view_column *column, const struct ref_list *refs)
324 size_t i;
326 if (!column->opt.commit_title.refs || !refs)
327 return FALSE;
329 for (i = 0; i < refs->size; i++) {
330 struct ref *ref = refs->refs[i];
331 enum line_type type = get_line_type_from_ref(ref);
332 const struct ref_format *format = get_ref_format(ref);
334 if (!strcmp(format->start, "hide:") && !*format->end)
335 continue;
337 if (draw_formatted(view, type, "%s%s%s", format->start, ref->name, format->end))
338 return TRUE;
340 if (draw_text(view, LINE_DEFAULT, " "))
341 return TRUE;
344 return FALSE;
347 static bool
348 draw_status(struct view *view, struct view_column *column,
349 enum line_type type, const char *status)
351 const char *label = mkstatus(status ? *status : 0, column->opt.status.display);
353 return draw_field(view, type, label, column->width, ALIGN_LEFT, FALSE);
357 * Revision graph
360 static const enum line_type graph_colors[] = {
361 LINE_PALETTE_0,
362 LINE_PALETTE_1,
363 LINE_PALETTE_2,
364 LINE_PALETTE_3,
365 LINE_PALETTE_4,
366 LINE_PALETTE_5,
367 LINE_PALETTE_6,
370 static enum line_type get_graph_color(struct graph_symbol *symbol)
372 if (symbol->commit)
373 return LINE_GRAPH_COMMIT;
374 assert(symbol->color < ARRAY_SIZE(graph_colors));
375 return graph_colors[symbol->color];
378 static bool
379 draw_graph_utf8(struct view *view, struct graph_symbol *symbol, enum line_type color, bool first)
381 const char *chars = graph_symbol_to_utf8(symbol);
383 return draw_text(view, color, chars + !!first);
386 static bool
387 draw_graph_ascii(struct view *view, struct graph_symbol *symbol, enum line_type color, bool first)
389 const char *chars = graph_symbol_to_ascii(symbol);
391 return draw_text(view, color, chars + !!first);
394 static bool
395 draw_graph_chtype(struct view *view, struct graph_symbol *symbol, enum line_type color, bool first)
397 const chtype *chars = graph_symbol_to_chtype(symbol);
399 return draw_graphic(view, color, chars + !!first, 2 - !!first, FALSE);
402 typedef bool (*draw_graph_fn)(struct view *, struct graph_symbol *, enum line_type, bool);
404 static bool
405 draw_graph(struct view *view, const struct graph_canvas *canvas)
407 static const draw_graph_fn fns[] = {
408 draw_graph_ascii,
409 draw_graph_chtype,
410 draw_graph_utf8
412 draw_graph_fn fn = fns[opt_line_graphics];
413 int i;
415 for (i = 0; i < canvas->size; i++) {
416 struct graph_symbol *symbol = &canvas->symbols[i];
417 enum line_type color = get_graph_color(symbol);
419 if (fn(view, symbol, color, i == 0))
420 return TRUE;
423 return draw_text(view, LINE_DEFAULT, " ");
426 static bool
427 draw_commit_title(struct view *view, struct view_column *column,
428 const struct graph_canvas *graph, const struct ref_list *refs,
429 const char *commit_title)
431 if (graph && column->opt.commit_title.graph &&
432 draw_graph(view, graph))
433 return TRUE;
434 if (draw_refs(view, column, refs))
435 return TRUE;
436 return draw_text_overflow(view, commit_title, LINE_DEFAULT,
437 column->opt.commit_title.overflow, 0);
440 static bool
441 draw_diff_stat_part(struct view *view, enum line_type *type, const char **text, char c, enum line_type next_type)
443 const char *sep = c == '|' ? strrchr(*text, c) : strchr(*text, c);
445 if (sep != NULL) {
446 draw_text_expanded(view, *type, *text, sep - *text, FALSE);
447 *text = sep;
448 *type = next_type;
451 return sep != NULL;
454 static void
455 draw_diff_stat(struct view *view, enum line_type *type, const char **text)
457 draw_diff_stat_part(view, type, text, '|', LINE_DEFAULT);
458 if (draw_diff_stat_part(view, type, text, 'B', LINE_DEFAULT)) {
459 /* Handle binary diffstat: Bin <deleted> -> <added> bytes */
460 draw_diff_stat_part(view, type, text, ' ', LINE_DIFF_DEL);
461 draw_diff_stat_part(view, type, text, '-', LINE_DEFAULT);
462 draw_diff_stat_part(view, type, text, ' ', LINE_DIFF_ADD);
463 draw_diff_stat_part(view, type, text, 'b', LINE_DEFAULT);
465 } else {
466 draw_diff_stat_part(view, type, text, '+', LINE_DIFF_ADD);
467 draw_diff_stat_part(view, type, text, '-', LINE_DIFF_DEL);
471 bool
472 view_column_draw(struct view *view, struct line *line, unsigned int lineno)
474 struct view_column *column = view->columns;
475 struct view_column_data column_data = {};
477 if (!view->ops->get_column_data(view, line, &column_data))
478 return TRUE;
480 if (column_data.section)
481 column = column_data.section;
483 for (; column; column = column->next) {
484 mode_t mode = column_data.mode ? *column_data.mode : 0;
486 if (column->hidden)
487 continue;
489 switch (column->type) {
490 case VIEW_COLUMN_DATE:
491 if (draw_date(view, column, column_data.date))
492 return TRUE;
493 continue;
495 case VIEW_COLUMN_AUTHOR:
496 if (draw_author(view, column, column_data.author))
497 return TRUE;
498 continue;
500 case VIEW_COLUMN_REF:
501 if (draw_ref(view, column, column_data.ref))
502 return TRUE;
503 continue;
505 case VIEW_COLUMN_ID:
506 if (draw_id(view, column, column_data.reflog ? column_data.reflog : column_data.id))
507 return TRUE;
508 continue;
510 case VIEW_COLUMN_LINE_NUMBER:
511 if (draw_lineno(view, column, column_data.line_number ? *column_data.line_number : lineno))
512 return TRUE;
513 continue;
515 case VIEW_COLUMN_MODE:
516 if (draw_mode(view, column, mode))
517 return TRUE;
518 continue;
520 case VIEW_COLUMN_FILE_SIZE:
521 if (draw_file_size(view, column, column_data.file_size ? *column_data.file_size : 0, mode))
522 return TRUE;
523 continue;
525 case VIEW_COLUMN_COMMIT_TITLE:
526 if (draw_commit_title(view, column, column_data.graph,
527 column_data.refs, column_data.commit_title))
528 return TRUE;
529 continue;
531 case VIEW_COLUMN_FILE_NAME:
532 if (draw_filename(view, column, column_data.file_name, mode))
533 return TRUE;
534 continue;
536 case VIEW_COLUMN_SECTION:
537 if (draw_text(view, column->opt.section.type, column->opt.section.text))
538 return TRUE;
539 continue;
541 case VIEW_COLUMN_STATUS:
542 if (draw_status(view, column, line->type, column_data.status))
543 return TRUE;
544 continue;
546 case VIEW_COLUMN_TEXT:
548 enum line_type type = line->type;
549 const char *text = column_data.text;
551 if (line->wrapped && draw_text(view, LINE_DELIMITER, "+"))
552 return TRUE;
554 if (line->graph_indent) {
555 size_t indent = get_graph_indent(text);
557 if (draw_text_expanded(view, LINE_DEFAULT, text, indent, FALSE))
558 return TRUE;
559 text += indent;
561 if (type == LINE_DIFF_STAT)
562 draw_diff_stat(view, &type, &text);
563 if (line->commit_title) {
564 if (draw_text_overflow(view, text, LINE_DEFAULT,
565 column->opt.text.commit_title_overflow, 4))
566 return TRUE;
567 } else if (draw_text(view, type, text)) {
568 return TRUE;
571 continue;
575 return TRUE;
578 bool
579 draw_view_line(struct view *view, unsigned int lineno)
581 struct line *line;
582 bool selected = (view->pos.offset + lineno == view->pos.lineno);
584 /* FIXME: Disabled during code split.
585 assert(view_is_displayed(view));
588 if (view->pos.offset + lineno >= view->lines)
589 return FALSE;
591 line = &view->line[view->pos.offset + lineno];
593 wmove(view->win, lineno, 0);
594 if (line->cleareol)
595 wclrtoeol(view->win);
596 view->col = 0;
597 view->curline = line;
598 view->curtype = LINE_NONE;
599 line->selected = FALSE;
600 line->dirty = line->cleareol = 0;
602 if (selected) {
603 set_view_attr(view, LINE_CURSOR);
604 line->selected = TRUE;
605 view->ops->select(view, line);
608 return view->ops->draw(view, line, lineno);
611 void
612 redraw_view_dirty(struct view *view)
614 bool dirty = FALSE;
615 int lineno;
617 for (lineno = 0; lineno < view->height; lineno++) {
618 if (view->pos.offset + lineno >= view->lines)
619 break;
620 if (!view->line[view->pos.offset + lineno].dirty)
621 continue;
622 dirty = TRUE;
623 if (!draw_view_line(view, lineno))
624 break;
627 if (!dirty)
628 return;
629 wnoutrefresh(view->win);
632 void
633 redraw_view_from(struct view *view, int lineno)
635 assert(0 <= lineno && lineno < view->height);
637 if (view->columns && view_column_info_changed(view, FALSE)) {
638 int i;
640 view_column_reset(view);
641 for (i = 0; i < view->lines; i++) {
642 view_column_info_update(view, &view->line[i]);
646 for (; lineno < view->height; lineno++) {
647 if (!draw_view_line(view, lineno))
648 break;
651 wnoutrefresh(view->win);
654 void
655 redraw_view(struct view *view)
657 werase(view->win);
658 redraw_view_from(view, 0);
661 /* vim: set ts=8 sw=8 noexpandtab: */