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.
15 #include "tig/graph.h"
17 #include "tig/options.h"
18 #include "compat/hashtab.h"
20 static const enum line_type palette_colors
[] = {
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
);
51 #define VIEW_MAX_LEN(view) ((view)->width + (view)->pos.col - (view)->col)
54 draw_chars(struct view
*view
, enum line_type type
, const char *string
,
55 int max_len
, bool use_tilde
)
60 size_t skip
= view
->pos
.col
> view
->col
? view
->pos
.col
- view
->col
: 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
);
70 return VIEW_MAX_LEN(view
) <= 0;
73 set_view_attr(view
, type
);
75 waddnstr(view
->win
, string
, len
);
77 if (trimmed
&& use_tilde
) {
78 set_view_attr(view
, LINE_DELIMITER
);
79 waddch(view
->win
, '~');
85 return VIEW_MAX_LEN(view
) <= 0;
89 draw_space(struct view
*view
, enum line_type type
, int max
, int spaces
)
91 static char space
[] = " ";
93 spaces
= MIN(max
, spaces
);
96 int len
= MIN(spaces
, sizeof(space
) - 1);
98 if (draw_chars(view
, type
, space
, len
, false))
103 return VIEW_MAX_LEN(view
) <= 0;
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
];
112 size_t pos
= string_expand(text
, sizeof(text
), string
, opt_tab_size
);
114 if (draw_chars(view
, type
, text
, max_len
, use_tilde
))
119 return VIEW_MAX_LEN(view
) <= 0;
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);
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;
135 int overflow
= overflow_length
+ offset
;
136 int max
= MIN(VIEW_MAX_LEN(view
), overflow
);
137 const char *tmp
= text
;
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
))
146 type
= LINE_OVERFLOW
;
149 if (*text
&& draw_text(view
, type
, text
))
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
];
161 FORMAT_BUFFER(text
, sizeof(text
), format
, retval
, true);
162 return retval
>= 0 ? draw_text(view
, type
, text
) : VIEW_MAX_LEN(view
) <= 0;
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
);
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
]);
183 if (size
< max
&& skip
<= size
)
184 waddch(view
->win
, ' ');
188 return VIEW_MAX_LEN(view
) <= 0;
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);
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;
205 if (draw_space(view
, type
, leftpad
, leftpad
))
212 return draw_chars(view
, type
, text
, max
- 1, trim
)
213 || draw_space(view
, LINE_DEFAULT
, max
- (view
->col
- col
), max
);
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
;
226 return draw_field(view
, LINE_DATE
, text
, column
->width
, align
, false);
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
)
238 return draw_field(view
, LINE_AUTHOR
, text
, column
->width
, ALIGN_LEFT
, trim
);
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
)
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);
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
)
269 return draw_field(view
, type
, filename
, column_width
, ALIGN_LEFT
, trim
);
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
)
280 return draw_field(view
, LINE_FILE_SIZE
, str
, column
->width
, ALIGN_RIGHT
, false);
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
)
291 return draw_field(view
, LINE_MODE
, str
, column
->width
, ALIGN_LEFT
, false);
295 draw_lineno_custom(struct view
*view
, struct view_column
*column
, unsigned int lineno
)
298 unsigned long digits3
= column
->width
< 3 ? 3 : column
->width
;
299 int max
= MIN(VIEW_MAX_LEN(view
), digits3
);
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
)
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
))
316 draw_chars(view
, LINE_LINE_NUMBER
, text
, max
, true);
318 draw_space(view
, LINE_LINE_NUMBER
, max
, digits3
);
319 return draw_graphic(view
, LINE_DEFAULT
, &separator
, 1, true);
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
);
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);
339 draw_refs(struct view
*view
, struct view_column
*column
, const struct ref
*refs
)
341 if (!column
->opt
.commit_title
.refs
|| !refs
)
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
)
352 if (draw_formatted(view
, type
, "%s%s%s", format
->start
, ref
->name
, format
->end
))
355 if (draw_text(view
, LINE_DEFAULT
, " "))
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);
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
];
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
);
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
);
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);
409 draw_graph(struct view
*view
, const struct graph
*graph
, const struct graph_canvas
*canvas
)
411 static const graph_symbol_iterator_fn fns
[] = {
417 graph
->foreach_symbol(graph
, canvas
, fns
[opt_line_graphics
], view
);
418 return draw_text(view
, LINE_DEFAULT
, " ");
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
))
429 if (draw_refs(view
, column
, refs
))
431 return draw_text_overflow(view
, commit_title
, LINE_DEFAULT
,
432 column
->opt
.commit_title
.overflow
, 0);
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
);
441 draw_text_expanded(view
, *type
, *text
, sep
- *text
, false);
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
);
461 draw_diff_stat_part(view
, type
, text
, '+', LINE_DIFF_ADD
);
462 draw_diff_stat_part(view
, type
, text
, '-', LINE_DIFF_DEL
);
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
))
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;
484 switch (column
->type
) {
485 case VIEW_COLUMN_DATE
:
486 if (draw_date(view
, column
, column_data
.date
))
490 case VIEW_COLUMN_AUTHOR
:
491 if (draw_author(view
, column
, column_data
.author
))
495 case VIEW_COLUMN_REF
:
496 if (draw_ref(view
, column
, column_data
.ref
))
501 if (draw_id(view
, column
, column_data
.reflog
? column_data
.reflog
: column_data
.id
))
505 case VIEW_COLUMN_LINE_NUMBER
:
506 if (draw_lineno(view
, column
, column_data
.line_number
? *column_data
.line_number
: lineno
))
510 case VIEW_COLUMN_MODE
:
511 if (draw_mode(view
, column
, mode
))
515 case VIEW_COLUMN_FILE_SIZE
:
516 if (draw_file_size(view
, column
, column_data
.file_size
? *column_data
.file_size
: 0, mode
))
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
))
526 case VIEW_COLUMN_FILE_NAME
:
527 if (draw_filename(view
, column
, column_data
.file_name
, mode
))
531 case VIEW_COLUMN_SECTION
:
532 if (draw_text(view
, column
->opt
.section
.type
, column
->opt
.section
.text
))
536 case VIEW_COLUMN_STATUS
:
537 if (draw_status(view
, column
, line
->type
, column_data
.status
))
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
, "+"))
549 if (line
->graph_indent
) {
550 size_t indent
= get_graph_indent(text
);
552 if (draw_text_expanded(view
, LINE_DEFAULT
, text
, indent
, false))
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))
562 } else if (draw_text(view
, type
, text
)) {
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];
581 if (!buf
|| mvwinnstr(view
->win
, lineno
, 0, buf
, bufsize
) == ERR
||
582 regexec(view
->regex
, buf
, ARRAY_SIZE(pmatch
), pmatch
, 0)) {
587 for (i
= 0; i
< ARRAY_SIZE(pmatch
); i
++) {
588 regoff_t start
= pmatch
[i
].rm_so
;
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
),
605 draw_view_line(struct view
*view
, unsigned int lineno
)
608 bool selected
= (view
->pos
.offset
+ lineno
== view
->pos
.lineno
);
611 /* FIXME: Disabled during code split.
612 assert(view_is_displayed(view));
615 if (view
->pos
.offset
+ lineno
>= view
->lines
)
618 line
= &view
->line
[view
->pos
.offset
+ lineno
];
620 wmove(view
->win
, lineno
, 0);
622 wclrtoeol(view
->win
);
624 view
->curline
= line
;
625 view
->curtype
= LINE_NONE
;
626 line
->selected
= false;
627 line
->dirty
= line
->cleareol
= 0;
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
);
644 redraw_view_dirty(struct view
*view
)
649 for (lineno
= 0; lineno
< view
->height
; lineno
++) {
650 if (view
->pos
.offset
+ lineno
>= view
->lines
)
652 if (!view
->line
[view
->pos
.offset
+ lineno
].dirty
)
655 if (!draw_view_line(view
, lineno
))
661 wnoutrefresh(view
->win
);
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)) {
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
))
683 wnoutrefresh(view
->win
);
687 redraw_view(struct view
*view
)
690 redraw_view_from(view
, 0);
693 /* vim: set ts=8 sw=8 noexpandtab: */