Add option to install Tig with Homebrew
[tig.git] / src / view.c
bloba0ad792bfa3459161ffcce14d2380ffc8ef73233
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/argv.h"
16 #include "tig/repo.h"
17 #include "tig/watch.h"
18 #include "tig/options.h"
19 #include "tig/view.h"
20 #include "tig/draw.h"
21 #include "tig/display.h"
24 * Navigation
27 bool
28 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
30 if (lineno >= view->lines)
31 lineno = view->lines > 0 ? view->lines - 1 : 0;
33 if (offset > lineno || offset + view->height <= lineno) {
34 unsigned long half = view->height / 2;
36 if (lineno > half)
37 offset = lineno - half;
38 else
39 offset = 0;
42 if (offset != view->pos.offset || lineno != view->pos.lineno) {
43 view->pos.offset = offset;
44 view->pos.lineno = lineno;
45 return TRUE;
48 return FALSE;
51 /* Scrolling backend */
52 void
53 do_scroll_view(struct view *view, int lines)
55 bool redraw_current_line = FALSE;
57 /* The rendering expects the new offset. */
58 view->pos.offset += lines;
60 assert(0 <= view->pos.offset && view->pos.offset < view->lines);
61 assert(lines);
63 /* Move current line into the view. */
64 if (view->pos.lineno < view->pos.offset) {
65 view->pos.lineno = view->pos.offset;
66 redraw_current_line = TRUE;
67 } else if (view->pos.lineno >= view->pos.offset + view->height) {
68 view->pos.lineno = view->pos.offset + view->height - 1;
69 redraw_current_line = TRUE;
72 assert(view->pos.offset <= view->pos.lineno && view->pos.lineno < view->lines);
74 /* Redraw the whole screen if scrolling is pointless. */
75 if (view->height < ABS(lines)) {
76 redraw_view(view);
78 } else {
79 int line = lines > 0 ? view->height - lines : 0;
80 int end = line + ABS(lines);
82 scrollok(view->win, TRUE);
83 wscrl(view->win, lines);
84 scrollok(view->win, FALSE);
86 while (line < end && draw_view_line(view, line))
87 line++;
89 if (redraw_current_line)
90 draw_view_line(view, view->pos.lineno - view->pos.offset);
91 wnoutrefresh(view->win);
94 view->has_scrolled = TRUE;
95 report_clear();
98 /* Scroll frontend */
99 void
100 scroll_view(struct view *view, enum request request)
102 int lines = 1;
104 assert(view_is_displayed(view));
106 if (request == REQ_SCROLL_WHEEL_DOWN || request == REQ_SCROLL_WHEEL_UP)
107 lines = opt_mouse_scroll;
109 switch (request) {
110 case REQ_SCROLL_FIRST_COL:
111 view->pos.col = 0;
112 redraw_view_from(view, 0);
113 report_clear();
114 return;
115 case REQ_SCROLL_LEFT:
116 if (view->pos.col == 0) {
117 report("Cannot scroll beyond the first column");
118 return;
120 if (view->pos.col <= apply_step(opt_horizontal_scroll, view->width))
121 view->pos.col = 0;
122 else
123 view->pos.col -= apply_step(opt_horizontal_scroll, view->width);
124 redraw_view_from(view, 0);
125 report_clear();
126 return;
127 case REQ_SCROLL_RIGHT:
128 view->pos.col += apply_step(opt_horizontal_scroll, view->width);
129 redraw_view(view);
130 report_clear();
131 return;
132 case REQ_SCROLL_PAGE_DOWN:
133 lines = view->height;
134 case REQ_SCROLL_WHEEL_DOWN:
135 case REQ_SCROLL_LINE_DOWN:
136 if (view->pos.offset + lines > view->lines)
137 lines = view->lines - view->pos.offset;
139 if (lines == 0 || view->pos.offset + view->height >= view->lines) {
140 report("Cannot scroll beyond the last line");
141 return;
143 break;
145 case REQ_SCROLL_PAGE_UP:
146 lines = view->height;
147 case REQ_SCROLL_LINE_UP:
148 case REQ_SCROLL_WHEEL_UP:
149 if (lines > view->pos.offset)
150 lines = view->pos.offset;
152 if (lines == 0) {
153 report("Cannot scroll beyond the first line");
154 return;
157 lines = -lines;
158 break;
160 default:
161 die("request %d not handled in switch", request);
164 do_scroll_view(view, lines);
167 /* Cursor moving */
168 void
169 move_view(struct view *view, enum request request)
171 int scroll_steps = 0;
172 int steps;
174 switch (request) {
175 case REQ_MOVE_FIRST_LINE:
176 steps = -view->pos.lineno;
177 break;
179 case REQ_MOVE_LAST_LINE:
180 steps = view->lines - view->pos.lineno - 1;
181 break;
183 case REQ_MOVE_PAGE_UP:
184 steps = view->height > view->pos.lineno
185 ? -view->pos.lineno : -view->height;
186 break;
188 case REQ_MOVE_PAGE_DOWN:
189 steps = view->pos.lineno + view->height >= view->lines
190 ? view->lines - view->pos.lineno - 1 : view->height;
191 break;
193 case REQ_MOVE_HALF_PAGE_UP:
194 steps = view->height / 2 > view->pos.lineno
195 ? -view->pos.lineno : -(view->height / 2);
196 break;
198 case REQ_MOVE_HALF_PAGE_DOWN:
199 steps = view->pos.lineno + view->height / 2 >= view->lines
200 ? view->lines - view->pos.lineno - 1 : view->height / 2;
201 break;
203 case REQ_MOVE_UP:
204 case REQ_PREVIOUS:
205 steps = -1;
206 break;
208 case REQ_MOVE_DOWN:
209 case REQ_NEXT:
210 steps = 1;
211 break;
213 default:
214 die("request %d not handled in switch", request);
217 if (steps <= 0 && view->pos.lineno == 0) {
218 report("Cannot move beyond the first line");
219 return;
221 } else if (steps >= 0 && view->pos.lineno + 1 >= view->lines) {
222 report("Cannot move beyond the last line");
223 return;
226 /* Move the current line */
227 view->pos.lineno += steps;
228 assert(0 <= view->pos.lineno && view->pos.lineno < view->lines);
230 /* Check whether the view needs to be scrolled */
231 if (view->pos.lineno < view->pos.offset ||
232 view->pos.lineno >= view->pos.offset + view->height) {
233 scroll_steps = steps;
234 if (steps < 0 && -steps > view->pos.offset) {
235 scroll_steps = -view->pos.offset;
237 } else if (steps > 0) {
238 if (view->pos.lineno == view->lines - 1 &&
239 view->lines > view->height) {
240 scroll_steps = view->lines - view->pos.offset - 1;
241 if (scroll_steps >= view->height)
242 scroll_steps -= view->height - 1;
247 if (!view_is_displayed(view)) {
248 view->pos.offset += scroll_steps;
249 assert(0 <= view->pos.offset && view->pos.offset < view->lines);
250 view->ops->select(view, &view->line[view->pos.lineno]);
251 return;
254 /* Repaint the old "current" line if we be scrolling */
255 if (ABS(steps) < view->height)
256 draw_view_line(view, view->pos.lineno - steps - view->pos.offset);
258 if (scroll_steps) {
259 do_scroll_view(view, scroll_steps);
260 return;
263 /* Draw the current line */
264 draw_view_line(view, view->pos.lineno - view->pos.offset);
266 wnoutrefresh(view->win);
267 report_clear();
271 * Searching
274 DEFINE_ALLOCATOR(realloc_unsigned_ints, unsigned int, 32)
276 bool
277 grep_text(struct view *view, const char *text[])
279 regmatch_t pmatch;
280 size_t i;
282 for (i = 0; text[i]; i++)
283 if (*text[i] && !regexec(view->regex, text[i], 1, &pmatch, 0))
284 return TRUE;
285 return FALSE;
288 void
289 select_view_line(struct view *view, unsigned long lineno)
291 struct position old = view->pos;
293 if (goto_view_line(view, view->pos.offset, lineno)) {
294 if (view_is_displayed(view)) {
295 if (old.offset != view->pos.offset) {
296 redraw_view(view);
297 } else {
298 draw_view_line(view, old.lineno - view->pos.offset);
299 draw_view_line(view, view->pos.lineno - view->pos.offset);
300 wnoutrefresh(view->win);
302 } else {
303 view->ops->select(view, &view->line[view->pos.lineno]);
308 static bool
309 find_matches(struct view *view)
311 size_t lineno;
313 /* Note, lineno is unsigned long so will wrap around in which case it
314 * will become bigger than view->lines. */
315 for (lineno = 0; lineno < view->lines; lineno++) {
316 if (!view->ops->grep(view, &view->line[lineno]))
317 continue;
319 if (!realloc_unsigned_ints(&view->matched_line, view->matched_lines, 1))
320 return FALSE;
322 view->matched_line[view->matched_lines++] = lineno;
325 return TRUE;
328 void
329 find_next(struct view *view, enum request request)
331 int direction;
332 size_t i;
334 if (!*view->grep) {
335 if (!*view->env->search)
336 report("No previous search");
337 else
338 search_view(view, request);
339 return;
342 switch (request) {
343 case REQ_SEARCH:
344 case REQ_FIND_NEXT:
345 direction = 1;
346 break;
348 case REQ_SEARCH_BACK:
349 case REQ_FIND_PREV:
350 direction = -1;
351 break;
353 default:
354 return;
357 if (!view->matched_lines && !find_matches(view)) {
358 report("Allocation failure");
359 return;
362 /* Note, `i` is unsigned and will wrap around in which case it
363 * will become bigger than view->matched_lines. */
364 i = direction > 0 ? 0 : view->matched_lines - 1;
365 for (; i < view->matched_lines; i += direction) {
366 size_t lineno = view->matched_line[i];
368 if (direction > 0 && lineno <= view->pos.lineno)
369 continue;
371 if (direction < 0 && lineno >= view->pos.lineno)
372 continue;
374 select_view_line(view, lineno);
375 report("Line %zu matches '%s' (%zu of %zu)", lineno + 1, view->grep, i + 1, view->matched_lines);
376 return;
379 report("No match found for '%s'", view->grep);
382 static void
383 reset_matches(struct view *view)
385 free(view->matched_line);
386 view->matched_line = NULL;
387 view->matched_lines = 0;
390 void
391 search_view(struct view *view, enum request request)
393 int regex_err;
394 int regex_flags = opt_ignore_case ? REG_ICASE : 0;
396 if (view->regex) {
397 regfree(view->regex);
398 *view->grep = 0;
399 } else {
400 view->regex = calloc(1, sizeof(*view->regex));
401 if (!view->regex)
402 return;
405 regex_err = regcomp(view->regex, view->env->search, REG_EXTENDED | regex_flags);
406 if (regex_err != 0) {
407 char buf[SIZEOF_STR] = "unknown error";
409 regerror(regex_err, view->regex, buf, sizeof(buf));
410 report("Search failed: %s", buf);
411 return;
414 string_copy(view->grep, view->env->search);
416 reset_matches(view);
418 find_next(view, request);
422 * View history
425 static bool
426 view_history_is_empty(struct view_history *history)
428 return !history->stack;
431 struct view_state *
432 push_view_history_state(struct view_history *history, struct position *position, void *data)
434 struct view_state *state = history->stack;
436 if (state && data && history->state_alloc &&
437 !memcmp(state->data, data, history->state_alloc))
438 return NULL;
440 state = calloc(1, sizeof(*state) + history->state_alloc);
441 if (!state)
442 return NULL;
444 state->prev = history->stack;
445 history->stack = state;
446 clear_position(&history->position);
447 state->position = *position;
448 state->data = &state[1];
449 if (data && history->state_alloc)
450 memcpy(state->data, data, history->state_alloc);
451 return state;
454 bool
455 pop_view_history_state(struct view_history *history, struct position *position, void *data)
457 struct view_state *state = history->stack;
459 if (view_history_is_empty(history))
460 return FALSE;
462 history->position = state->position;
463 history->stack = state->prev;
465 if (data && history->state_alloc)
466 memcpy(data, state->data, history->state_alloc);
467 if (position)
468 *position = state->position;
470 free(state);
471 return TRUE;
474 void
475 reset_view_history(struct view_history *history)
477 while (pop_view_history_state(history, NULL, NULL))
482 * Incremental updating
485 void
486 reset_view(struct view *view)
488 int i;
490 for (i = 0; i < view->lines; i++)
491 free(view->line[i].data);
492 free(view->line);
494 reset_matches(view);
495 view->prev_pos = view->pos;
496 /* A view without a previous view is the first view */
497 if (!view->prev && !view->lines && view->prev_pos.lineno == 0)
498 view->prev_pos.lineno = view->env->lineno;
499 clear_position(&view->pos);
501 if (view->columns)
502 view_column_reset(view);
504 view->line = NULL;
505 view->lines = 0;
506 view->vid[0] = 0;
507 view->custom_lines = 0;
508 view->update_secs = 0;
511 static bool
512 restore_view_position(struct view *view)
514 /* Ensure that the view position is in a valid state. */
515 if (!check_position(&view->prev_pos) ||
516 (view->pipe && view->lines <= view->prev_pos.lineno))
517 return goto_view_line(view, view->pos.offset, view->pos.lineno);
519 /* Changing the view position cancels the restoring. */
520 /* FIXME: Changing back to the first line is not detected. */
521 if (check_position(&view->pos)) {
522 clear_position(&view->prev_pos);
523 return FALSE;
526 if (goto_view_line(view, view->prev_pos.offset, view->prev_pos.lineno) &&
527 view_is_displayed(view))
528 werase(view->win);
530 view->pos.col = view->prev_pos.col;
531 clear_position(&view->prev_pos);
533 return TRUE;
536 void
537 end_update(struct view *view, bool force)
539 if (!view->pipe)
540 return;
541 while (!view->ops->read(view, NULL))
542 if (!force)
543 return;
544 if (force)
545 io_kill(view->pipe);
546 io_done(view->pipe);
547 view->pipe = NULL;
550 static void
551 setup_update(struct view *view, const char *vid)
553 reset_view(view);
554 /* XXX: Do not use string_copy_rev(), it copies until first space. */
555 string_ncopy(view->vid, vid, strlen(vid));
556 view->pipe = &view->io;
557 view->start_time = time(NULL);
560 static bool
561 view_no_refresh(struct view *view, enum open_flags flags)
563 bool reload = !!(flags & OPEN_ALWAYS_LOAD) || !view->lines;
565 return (!reload && !strcmp(view->vid, view->ops->id)) ||
566 ((flags & OPEN_REFRESH) && !view_can_refresh(view));
569 bool
570 begin_update(struct view *view, const char *dir, const char **argv, enum open_flags flags)
572 bool extra = !!(flags & (OPEN_EXTRA));
573 bool refresh = flags & (OPEN_REFRESH | OPEN_PREPARED | OPEN_STDIN);
574 enum io_flags forward_stdin = (flags & OPEN_FORWARD_STDIN) ? IO_RD_FORWARD_STDIN : 0;
575 enum io_flags with_stderr = (flags & OPEN_WITH_STDERR) ? IO_RD_WITH_STDERR : 0;
576 enum io_flags io_flags = forward_stdin | with_stderr;
578 if (view_no_refresh(view, flags))
579 return TRUE;
581 if (view->pipe) {
582 if (extra)
583 io_done(view->pipe);
584 else
585 end_update(view, TRUE);
588 view->unrefreshable = open_in_pager_mode(flags);
590 if (!refresh && argv) {
591 bool file_filter = !view_has_flags(view, VIEW_FILE_FILTER) || opt_file_filter;
593 view->dir = dir;
594 if (!argv_format(view->env, &view->argv, argv, !view->prev, file_filter)) {
595 report("Failed to format %s arguments", view->name);
596 return FALSE;
599 /* Put the current view ref value to the view title ref
600 * member. This is needed by the blob view. Most other
601 * views sets it automatically after loading because the
602 * first line is a commit line. */
603 string_copy_rev(view->ref, view->ops->id);
606 if (view->argv && view->argv[0] &&
607 !io_exec(&view->io, IO_RD, view->dir, opt_env, view->argv, io_flags)) {
608 report("Failed to open %s view", view->name);
609 return FALSE;
612 if (open_from_stdin(flags)) {
613 if (!io_open(&view->io, "%s", ""))
614 die("Failed to open stdin");
617 if (!extra)
618 setup_update(view, view->ops->id);
620 return TRUE;
623 bool
624 update_view(struct view *view)
626 /* Clear the view and redraw everything since the tree sorting
627 * might have rearranged things. */
628 bool redraw = view->lines == 0;
629 bool can_read = TRUE;
630 struct encoding *encoding = view->encoding ? view->encoding : default_encoding;
631 struct buffer line;
633 if (!view->pipe)
634 return TRUE;
636 if (!io_can_read(view->pipe, FALSE)) {
637 if (view->lines == 0 && view_is_displayed(view)) {
638 time_t secs = time(NULL) - view->start_time;
640 if (secs > 1 && secs > view->update_secs) {
641 if (view->update_secs == 0)
642 redraw_view(view);
643 update_view_title(view);
644 view->update_secs = secs;
647 return TRUE;
650 for (; io_get(view->pipe, &line, '\n', can_read); can_read = FALSE) {
651 if (encoding && !encoding_convert(encoding, &line)) {
652 report("Encoding failure");
653 end_update(view, TRUE);
654 return FALSE;
657 if (!view->ops->read(view, &line)) {
658 report("Allocation failure");
659 end_update(view, TRUE);
660 return FALSE;
664 if (io_error(view->pipe)) {
665 report("Failed to read: %s", io_strerror(view->pipe));
666 end_update(view, TRUE);
668 } else if (io_eof(view->pipe)) {
669 end_update(view, FALSE);
672 if (restore_view_position(view))
673 redraw = TRUE;
675 if (!view_is_displayed(view))
676 return TRUE;
678 if (redraw || view->force_redraw)
679 redraw_view_from(view, 0);
680 else
681 redraw_view_dirty(view);
682 view->force_redraw = FALSE;
684 /* Update the title _after_ the redraw so that if the redraw picks up a
685 * commit reference in view->ref it'll be available here. */
686 update_view_title(view);
687 return TRUE;
690 void
691 update_view_title(struct view *view)
693 WINDOW *window = view->title;
694 struct line *line = &view->line[view->pos.lineno];
695 unsigned int view_lines, lines;
697 assert(view_is_displayed(view));
699 if (view == display[current_view])
700 wbkgdset(window, get_view_attr(view, LINE_TITLE_FOCUS));
701 else
702 wbkgdset(window, get_view_attr(view, LINE_TITLE_BLUR));
704 werase(window);
705 mvwprintw(window, 0, 0, "[%s]", view->name);
707 if (*view->ref) {
708 wprintw(window, " %s", view->ref);
711 if (!view_has_flags(view, VIEW_CUSTOM_STATUS) && view_has_line(view, line) &&
712 line->lineno) {
713 wprintw(window, " - %s %d of %zd",
714 view->ops->type,
715 line->lineno,
716 view->lines - view->custom_lines);
719 if (view->pipe) {
720 time_t secs = time(NULL) - view->start_time;
722 /* Three git seconds are a long time ... */
723 if (secs > 2)
724 wprintw(window, " loading %lds", secs);
727 view_lines = view->pos.offset + view->height;
728 lines = view->lines ? MIN(view_lines, view->lines) * 100 / view->lines : 0;
729 mvwprintw(window, 0, view->width - count_digits(lines) - 1, "%d%%", lines);
731 wnoutrefresh(window);
735 * View opening
738 void
739 split_view(struct view *prev, struct view *view)
741 display[1] = view;
742 current_view = opt_focus_child ? 1 : 0;
743 view->parent = prev;
744 resize_display();
746 if (prev->pos.lineno - prev->pos.offset >= prev->height) {
747 /* Take the title line into account. */
748 int lines = prev->pos.lineno - prev->pos.offset - prev->height + 1;
750 /* Scroll the view that was split if the current line is
751 * outside the new limited view. */
752 do_scroll_view(prev, lines);
755 if (view != prev && view_is_displayed(prev)) {
756 /* "Blur" the previous view. */
757 update_view_title(prev);
761 void
762 maximize_view(struct view *view, bool redraw)
764 memset(display, 0, sizeof(display));
765 current_view = 0;
766 display[current_view] = view;
767 resize_display();
768 if (redraw) {
769 redraw_display(FALSE);
770 report_clear();
774 void
775 load_view(struct view *view, struct view *prev, enum open_flags flags)
777 bool refresh = !view_no_refresh(view, flags);
779 /* When prev == view it means this is the first loaded view. */
780 if (prev && view != prev) {
781 view->prev = prev;
784 if (!refresh && view_can_refresh(view) &&
785 watch_update_single(&view->watch, WATCH_EVENT_SWITCH_VIEW)) {
786 refresh = watch_dirty(&view->watch);
787 if (refresh)
788 flags |= OPEN_REFRESH;
791 if (refresh) {
792 if (view->pipe)
793 end_update(view, TRUE);
794 if (view->ops->private_size) {
795 if (!view->private) {
796 view->private = calloc(1, view->ops->private_size);
797 } else {
798 if (view->ops->done)
799 view->ops->done(view);
800 memset(view->private, 0, view->ops->private_size);
804 if (!view->ops->open(view, flags))
805 return;
808 if (prev) {
809 bool split = !!(flags & OPEN_SPLIT);
811 if (split) {
812 split_view(prev, view);
813 } else {
814 maximize_view(view, FALSE);
818 restore_view_position(view);
820 if (view->pipe && view->lines == 0) {
821 /* Clear the old view and let the incremental updating refill
822 * the screen. */
823 werase(view->win);
824 /* Do not clear the position if it is the first view. */
825 if (view->prev && !(flags & (OPEN_RELOAD | OPEN_REFRESH)))
826 clear_position(&view->prev_pos);
827 report_clear();
828 } else if (view_is_displayed(view)) {
829 redraw_view(view);
830 report_clear();
834 #define refresh_view(view) load_view(view, NULL, OPEN_REFRESH)
835 #define reload_view(view) load_view(view, NULL, OPEN_RELOAD)
837 void
838 open_view(struct view *prev, struct view *view, enum open_flags flags)
840 bool reload = !!(flags & (OPEN_RELOAD | OPEN_PREPARED));
841 int nviews = displayed_views();
843 assert(flags ^ OPEN_REFRESH);
845 if (view == prev && nviews == 1 && !reload) {
846 report("Already in %s view", view->name);
847 return;
850 if (!view_has_flags(view, VIEW_NO_GIT_DIR) && !repo.git_dir[0]) {
851 report("The %s view is disabled in pager mode", view->name);
852 return;
855 if (!view->keymap)
856 view->keymap = get_keymap(view->name, strlen(view->name));
857 load_view(view, prev ? prev : view, flags);
860 void
861 open_argv(struct view *prev, struct view *view, const char *argv[], const char *dir, enum open_flags flags)
863 if (view->pipe)
864 end_update(view, TRUE);
865 view->dir = dir;
867 if (!argv_copy(&view->argv, argv)) {
868 report("Failed to open %s view: %s", view->name, io_strerror(&view->io));
869 } else {
870 open_view(prev, view, flags | OPEN_PREPARED);
875 * Various utilities.
878 static struct view *sorting_view;
880 #define apply_comparator(cmp, o1, o2) \
881 (!(o1) || !(o2)) ? !!(o2) - !!(o1) : cmp(o1, o2)
883 #define number_compare(size1, size2) (*(size1) - *(size2))
885 #define mode_is_dir(mode) ((mode) && S_ISDIR(*(mode)))
887 static int
888 compare_view_column(enum view_column_type column, bool use_file_mode,
889 const struct line *line1, struct view_column_data *column_data1,
890 const struct line *line2, struct view_column_data *column_data2)
892 switch (column) {
893 case VIEW_COLUMN_AUTHOR:
894 return apply_comparator(ident_compare, column_data1->author, column_data2->author);
896 case VIEW_COLUMN_DATE:
897 return apply_comparator(timecmp, column_data1->date, column_data2->date);
899 case VIEW_COLUMN_ID:
900 if (column_data1->reflog && column_data2->reflog)
901 return apply_comparator(strcmp, column_data1->reflog, column_data2->reflog);
902 return apply_comparator(strcmp, column_data1->id, column_data2->id);
904 case VIEW_COLUMN_FILE_NAME:
905 if (use_file_mode && mode_is_dir(column_data1->mode) != mode_is_dir(column_data2->mode))
906 return mode_is_dir(column_data1->mode) ? -1 : 1;
907 return apply_comparator(strcmp, column_data1->file_name, column_data2->file_name);
909 case VIEW_COLUMN_FILE_SIZE:
910 return apply_comparator(number_compare, column_data1->file_size, column_data2->file_size);
912 case VIEW_COLUMN_LINE_NUMBER:
913 return line1->lineno - line2->lineno;
915 case VIEW_COLUMN_MODE:
916 return apply_comparator(number_compare, column_data1->mode, column_data2->mode);
918 case VIEW_COLUMN_REF:
919 return apply_comparator(ref_compare, column_data1->ref, column_data2->ref);
921 case VIEW_COLUMN_COMMIT_TITLE:
922 return apply_comparator(strcmp, column_data1->commit_title, column_data2->commit_title);
924 case VIEW_COLUMN_SECTION:
925 return apply_comparator(strcmp, column_data1->section->opt.section.text,
926 column_data2->section->opt.section.text);
928 case VIEW_COLUMN_STATUS:
929 return apply_comparator(number_compare, column_data1->status, column_data2->status);
931 case VIEW_COLUMN_TEXT:
932 return apply_comparator(strcmp, column_data1->text, column_data2->text);
935 return 0;
938 static enum view_column_type view_column_order[] = {
939 VIEW_COLUMN_FILE_NAME,
940 VIEW_COLUMN_STATUS,
941 VIEW_COLUMN_MODE,
942 VIEW_COLUMN_FILE_SIZE,
943 VIEW_COLUMN_DATE,
944 VIEW_COLUMN_AUTHOR,
945 VIEW_COLUMN_COMMIT_TITLE,
946 VIEW_COLUMN_LINE_NUMBER,
947 VIEW_COLUMN_SECTION,
948 VIEW_COLUMN_TEXT,
949 VIEW_COLUMN_REF,
950 VIEW_COLUMN_ID,
953 static int
954 sort_view_compare(const void *l1, const void *l2)
956 const struct line *line1 = l1;
957 const struct line *line2 = l2;
958 struct view_column_data column_data1 = {};
959 struct view_column_data column_data2 = {};
960 struct sort_state *sort = &sorting_view->sort;
961 enum view_column_type column = get_sort_field(sorting_view);
962 int cmp;
963 int i;
965 if (!sorting_view->ops->get_column_data(sorting_view, line1, &column_data1))
966 return -1;
967 else if (!sorting_view->ops->get_column_data(sorting_view, line2, &column_data2))
968 return 1;
970 cmp = compare_view_column(column, TRUE, line1, &column_data1, line2, &column_data2);
972 /* Ensure stable sorting by ordering ordering by the other
973 * columns if the selected column values are equal. */
974 for (i = 0; !cmp && i < ARRAY_SIZE(view_column_order); i++)
975 if (column != view_column_order[i])
976 cmp = compare_view_column(view_column_order[i], FALSE,
977 line1, &column_data1,
978 line2, &column_data2);
980 return sort->reverse ? -cmp : cmp;
983 void
984 sort_view(struct view *view, bool change_field)
986 struct sort_state *state = &view->sort;
988 if (change_field) {
989 while (TRUE) {
990 state->current = state->current->next
991 ? state->current->next : view->columns;
992 if (get_sort_field(view) == VIEW_COLUMN_ID &&
993 !state->current->opt.id.display)
994 continue;
995 break;
997 } else {
998 state->reverse = !state->reverse;
1001 sorting_view = view;
1002 qsort(view->line, view->lines, sizeof(*view->line), sort_view_compare);
1005 static const char *
1006 view_column_text(struct view *view, struct view_column_data *column_data,
1007 struct view_column *column)
1009 const char *text = "";
1011 switch (column->type) {
1012 case VIEW_COLUMN_AUTHOR:
1013 if (column_data->author)
1014 text = mkauthor(column_data->author, column->opt.author.width, column->opt.author.display);
1015 break;
1017 case VIEW_COLUMN_COMMIT_TITLE:
1018 text = column_data->commit_title;
1019 break;
1021 case VIEW_COLUMN_DATE:
1022 if (column_data->date)
1023 text = mkdate(column_data->date, column->opt.date.display);
1024 break;
1026 case VIEW_COLUMN_REF:
1027 if (column_data->ref)
1028 text = column_data->ref->name;
1029 break;
1031 case VIEW_COLUMN_FILE_NAME:
1032 if (column_data->file_name)
1033 text = column_data->file_name;
1034 break;
1036 case VIEW_COLUMN_FILE_SIZE:
1037 if (column_data->file_size)
1038 text = mkfilesize(*column_data->file_size, column->opt.file_size.display);
1039 break;
1041 case VIEW_COLUMN_ID:
1042 if (column->opt.id.display)
1043 text = column_data->reflog ? column_data->reflog : column_data->id;
1044 break;
1046 case VIEW_COLUMN_LINE_NUMBER:
1047 break;
1049 case VIEW_COLUMN_MODE:
1050 if (column_data->mode)
1051 text = mkmode(*column_data->mode);
1052 break;
1054 case VIEW_COLUMN_STATUS:
1055 if (column_data->status)
1056 text = mkstatus(*column_data->status, column->opt.status.display);
1057 break;
1059 case VIEW_COLUMN_SECTION:
1060 text = column_data->section->opt.section.text;
1061 break;
1063 case VIEW_COLUMN_TEXT:
1064 text = column_data->text;
1065 break;
1068 return text ? text : "";
1071 static bool
1072 grep_refs(struct view *view, struct view_column *column, const struct ref_list *list)
1074 regmatch_t pmatch;
1075 size_t i;
1077 if (!list)
1078 return FALSE;
1080 for (i = 0; i < list->size; i++) {
1081 if (!regexec(view->regex, list->refs[i]->name, 1, &pmatch, 0))
1082 return TRUE;
1085 return FALSE;
1088 bool
1089 view_column_grep(struct view *view, struct line *line)
1091 struct view_column_data column_data = {};
1092 bool ok = view->ops->get_column_data(view, line, &column_data);
1093 struct view_column *column;
1095 if (!ok)
1096 return FALSE;
1098 for (column = view->columns; column; column = column->next) {
1099 const char *text[] = {
1100 view_column_text(view, &column_data, column),
1101 NULL
1104 if (grep_text(view, text))
1105 return TRUE;
1107 if (column->type == VIEW_COLUMN_COMMIT_TITLE &&
1108 column->opt.commit_title.refs &&
1109 grep_refs(view, column, column_data.refs))
1110 return TRUE;
1113 return FALSE;
1116 bool
1117 view_column_info_changed(struct view *view, bool update)
1119 struct view_column *column;
1120 bool changed = FALSE;
1122 for (column = view->columns; column; column = column->next) {
1123 if (memcmp(&column->prev_opt, &column->opt, sizeof(column->opt))) {
1124 if (!update)
1125 return TRUE;
1126 column->prev_opt = column->opt;
1127 changed = TRUE;
1131 return changed;
1134 void
1135 view_column_reset(struct view *view)
1137 struct view_column *column;
1139 view_column_info_changed(view, TRUE);
1140 for (column = view->columns; column; column = column->next)
1141 column->width = 0;
1144 static enum status_code
1145 parse_view_column_config(char **pos, const char **name, const char **value, bool first)
1147 size_t len = strcspn(*pos, ",");
1148 size_t optlen;
1150 if (strlen(*pos) > len)
1151 (*pos)[len] = 0;
1152 optlen = strcspn(*pos, ":=");
1154 if (first) {
1155 *name = "display";
1157 if (optlen == len) {
1158 *value = len ? *pos : "yes";
1159 *pos += len + 1;
1160 return SUCCESS;
1163 /* Fake boolean enum value. */
1164 *value = "yes";
1165 return SUCCESS;
1168 *name = *pos;
1169 if (optlen == len)
1170 *value = "yes";
1171 else
1172 *value = *pos + optlen + 1;
1173 (*pos)[optlen] = 0;
1174 *pos += len + 1;
1176 return SUCCESS;
1179 static enum status_code
1180 parse_view_column_option(struct view_column *column,
1181 const char *opt_name, const char *opt_value)
1183 #define DEFINE_COLUMN_OPTION_INFO(name, type, flags) \
1184 { #name, STRING_SIZE(#name), #type, &opt->name },
1186 #define DEFINE_COLUMN_OPTIONS_PARSE(name, id, options) \
1187 if (column->type == VIEW_COLUMN_##id) { \
1188 struct name##_options *opt = &column->opt.name; \
1189 struct option_info info[] = { \
1190 options(DEFINE_COLUMN_OPTION_INFO) \
1191 }; \
1192 struct option_info *option = find_option_info(info, ARRAY_SIZE(info), opt_name); \
1193 if (!option) \
1194 return error("Unknown option `%s' for column %s", opt_name, \
1195 view_column_name(VIEW_COLUMN_##id)); \
1196 return parse_option(option, #name, opt_value); \
1199 COLUMN_OPTIONS(DEFINE_COLUMN_OPTIONS_PARSE);
1201 return error("Unknown view column option: %s", opt_name);
1204 static enum status_code
1205 parse_view_column_type(struct view_column *column, const char **arg)
1207 enum view_column_type type;
1208 size_t typelen = strcspn(*arg, ":,");
1210 for (type = 0; type < view_column_type_map->size; type++)
1211 if (enum_equals(view_column_type_map->entries[type], *arg, typelen)) {
1212 *arg += typelen + !!(*arg)[typelen];
1213 column->type = type;
1214 return SUCCESS;
1217 return error("Failed to parse view column type: %.*s", (int) typelen, *arg);
1220 static struct view *
1221 find_view(const char *view_name)
1223 struct view *view;
1224 int i;
1226 foreach_view(view, i)
1227 if (!strncmp(view_name, view->name, strlen(view->name)))
1228 return view;
1230 return NULL;
1233 enum status_code
1234 parse_view_config(const char *view_name, const char *argv[])
1236 enum status_code code = SUCCESS;
1237 size_t size = argv_size(argv);
1238 struct view_column *columns;
1239 struct view_column *column;
1240 struct view *view = find_view(view_name);
1241 int i;
1243 if (!view)
1244 return error("Unknown view: %s", view_name);
1246 columns = calloc(size, sizeof(*columns));
1247 if (!columns)
1248 return ERROR_OUT_OF_MEMORY;
1250 for (i = 0, column = NULL; code == SUCCESS && i < size; i++) {
1251 const char *arg = argv[i];
1252 char buf[SIZEOF_STR] = "";
1253 char *pos, *end;
1254 bool first = TRUE;
1256 if (column)
1257 column->next = &columns[i];
1258 column = &columns[i];
1260 code = parse_view_column_type(column, &arg);
1261 if (code != SUCCESS)
1262 break;
1264 if (!(view->ops->column_bits & (1 << column->type)))
1265 return error("The %s view does not support %s column", view->name,
1266 view_column_name(column->type));
1268 if ((column->type == VIEW_COLUMN_TEXT ||
1269 column->type == VIEW_COLUMN_COMMIT_TITLE) &&
1270 i + 1 < size)
1271 return error("The %s column must always be last",
1272 view_column_name(column->type));
1274 string_ncopy(buf, arg, strlen(arg));
1276 for (pos = buf, end = pos + strlen(pos); code == SUCCESS && pos <= end; first = FALSE) {
1277 const char *name = NULL;
1278 const char *value = NULL;
1280 code = parse_view_column_config(&pos, &name, &value, first);
1281 if (code == SUCCESS)
1282 code = parse_view_column_option(column, name, value);
1285 column->prev_opt = column->opt;
1288 if (code == SUCCESS) {
1289 free(view->columns);
1290 view->columns = columns;
1291 view->sort.current = view->columns;
1292 } else {
1293 free(columns);
1296 return code;
1299 struct view_column *
1300 get_view_column(struct view *view, enum view_column_type type)
1302 struct view_column *column;
1304 for (column = view->columns; column; column = column->next)
1305 if (column->type == type)
1306 return column;
1307 return NULL;
1310 bool
1311 view_column_info_update(struct view *view, struct line *line)
1313 struct view_column_data column_data = {};
1314 struct view_column *column;
1315 bool changed = FALSE;
1317 if (!view->ops->get_column_data(view, line, &column_data))
1318 return FALSE;
1320 for (column = view->columns; column; column = column->next) {
1321 const char *text = view_column_text(view, &column_data, column);
1322 int width = 0;
1324 switch (column->type) {
1325 case VIEW_COLUMN_AUTHOR:
1326 width = column->opt.author.width;
1327 break;
1329 case VIEW_COLUMN_COMMIT_TITLE:
1330 width = column->opt.commit_title.width;
1331 break;
1333 case VIEW_COLUMN_DATE:
1334 width = column->opt.date.width;
1335 break;
1337 case VIEW_COLUMN_FILE_NAME:
1338 width = column->opt.file_name.width;
1339 break;
1341 case VIEW_COLUMN_FILE_SIZE:
1342 width = column->opt.file_size.width;
1343 break;
1345 case VIEW_COLUMN_ID:
1346 width = column->opt.id.width;
1347 if (!width)
1348 width = opt_id_width;
1349 if (!column_data.reflog && !width)
1350 width = 7;
1351 break;
1353 case VIEW_COLUMN_LINE_NUMBER:
1354 if (column_data.line_number)
1355 width = count_digits(*column_data.line_number);
1356 else
1357 width = count_digits(view->lines);
1358 if (width < 3)
1359 width = 3;
1360 break;
1362 case VIEW_COLUMN_MODE:
1363 width = column->opt.mode.width;
1364 break;
1366 case VIEW_COLUMN_REF:
1367 width = column->opt.ref.width;
1368 break;
1370 case VIEW_COLUMN_SECTION:
1371 break;
1373 case VIEW_COLUMN_STATUS:
1374 width = column->opt.status.width;
1375 break;
1377 case VIEW_COLUMN_TEXT:
1378 width = column->opt.text.width;
1379 break;
1382 if (*text && !width)
1383 width = utf8_width(text);
1385 if (width > column->width) {
1386 column->width = width;
1387 changed = TRUE;
1391 if (changed)
1392 view->force_redraw = TRUE;
1393 return changed;
1396 struct line *
1397 find_line_by_type(struct view *view, struct line *line, enum line_type type, int direction)
1399 for (; view_has_line(view, line); line += direction)
1400 if (line->type == type)
1401 return line;
1403 return NULL;
1407 * Line utilities.
1410 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
1412 struct line *
1413 add_line_at(struct view *view, unsigned long pos, const void *data, enum line_type type, size_t data_size, bool custom)
1415 struct line *line;
1416 unsigned long lineno;
1418 if (!realloc_lines(&view->line, view->lines, 1))
1419 return NULL;
1421 if (data_size) {
1422 void *alloc_data = calloc(1, data_size);
1424 if (!alloc_data)
1425 return NULL;
1427 if (data)
1428 memcpy(alloc_data, data, data_size);
1429 data = alloc_data;
1432 if (pos < view->lines) {
1433 view->lines++;
1434 line = view->line + pos;
1435 lineno = line->lineno;
1437 memmove(line + 1, line, (view->lines - pos) * sizeof(*view->line));
1438 while (pos < view->lines) {
1439 view->line[pos].lineno++;
1440 view->line[pos++].dirty = 1;
1442 } else {
1443 line = &view->line[view->lines++];
1444 lineno = view->lines - view->custom_lines;
1447 memset(line, 0, sizeof(*line));
1448 line->type = type;
1449 line->data = (void *) data;
1450 line->dirty = 1;
1452 if (custom)
1453 view->custom_lines++;
1454 else
1455 line->lineno = lineno;
1457 return line;
1460 struct line *
1461 add_line(struct view *view, const void *data, enum line_type type, size_t data_size, bool custom)
1463 return add_line_at(view, view->lines, data, type, data_size, custom);
1466 struct line *
1467 add_line_alloc_(struct view *view, void **ptr, enum line_type type, size_t data_size, bool custom)
1469 struct line *line = add_line(view, NULL, type, data_size, custom);
1471 if (line)
1472 *ptr = line->data;
1473 return line;
1476 struct line *
1477 add_line_nodata(struct view *view, enum line_type type)
1479 return add_line(view, NULL, type, 0, FALSE);
1482 struct line *
1483 add_line_text(struct view *view, const char *text, enum line_type type)
1485 struct line *line = add_line(view, text, type, strlen(text) + 1, FALSE);
1487 if (line && view->ops->column_bits)
1488 view_column_info_update(view, line);
1489 return line;
1492 struct line * PRINTF_LIKE(3, 4)
1493 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
1495 char buf[SIZEOF_STR];
1496 int retval;
1498 FORMAT_BUFFER(buf, sizeof(buf), fmt, retval, FALSE);
1499 return retval >= 0 ? add_line_text(view, buf, type) : NULL;
1503 * Global view state.
1506 /* Included last to not pollute the rest of the file. */
1507 #include "tig/main.h"
1508 #include "tig/diff.h"
1509 #include "tig/log.h"
1510 #include "tig/tree.h"
1511 #include "tig/blob.h"
1512 #include "tig/blame.h"
1513 #include "tig/refs.h"
1514 #include "tig/status.h"
1515 #include "tig/stage.h"
1516 #include "tig/stash.h"
1517 #include "tig/grep.h"
1518 #include "tig/pager.h"
1519 #include "tig/help.h"
1521 static struct view *views[] = {
1522 #define VIEW_DATA(id, name) &name##_view
1523 VIEW_INFO(VIEW_DATA)
1526 struct view *
1527 get_view(int i)
1529 return 0 <= i && i < ARRAY_SIZE(views) ? views[i] : NULL;
1532 /* vim: set ts=8 sw=8 noexpandtab: */