Reorder test directory setup and TEST_OPTS parsing
[tig.git] / src / view.c
blobd2edc93efd4089f78adac96804cafcb85c037792
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_UP:
194 case REQ_PREVIOUS:
195 steps = -1;
196 break;
198 case REQ_MOVE_DOWN:
199 case REQ_NEXT:
200 steps = 1;
201 break;
203 default:
204 die("request %d not handled in switch", request);
207 if (steps <= 0 && view->pos.lineno == 0) {
208 report("Cannot move beyond the first line");
209 return;
211 } else if (steps >= 0 && view->pos.lineno + 1 >= view->lines) {
212 report("Cannot move beyond the last line");
213 return;
216 /* Move the current line */
217 view->pos.lineno += steps;
218 assert(0 <= view->pos.lineno && view->pos.lineno < view->lines);
220 /* Check whether the view needs to be scrolled */
221 if (view->pos.lineno < view->pos.offset ||
222 view->pos.lineno >= view->pos.offset + view->height) {
223 scroll_steps = steps;
224 if (steps < 0 && -steps > view->pos.offset) {
225 scroll_steps = -view->pos.offset;
227 } else if (steps > 0) {
228 if (view->pos.lineno == view->lines - 1 &&
229 view->lines > view->height) {
230 scroll_steps = view->lines - view->pos.offset - 1;
231 if (scroll_steps >= view->height)
232 scroll_steps -= view->height - 1;
237 if (!view_is_displayed(view)) {
238 view->pos.offset += scroll_steps;
239 assert(0 <= view->pos.offset && view->pos.offset < view->lines);
240 view->ops->select(view, &view->line[view->pos.lineno]);
241 return;
244 /* Repaint the old "current" line if we be scrolling */
245 if (ABS(steps) < view->height)
246 draw_view_line(view, view->pos.lineno - steps - view->pos.offset);
248 if (scroll_steps) {
249 do_scroll_view(view, scroll_steps);
250 return;
253 /* Draw the current line */
254 draw_view_line(view, view->pos.lineno - view->pos.offset);
256 wnoutrefresh(view->win);
257 report_clear();
261 * Searching
264 DEFINE_ALLOCATOR(realloc_unsigned_ints, unsigned int, 32)
266 bool
267 grep_text(struct view *view, const char *text[])
269 regmatch_t pmatch;
270 size_t i;
272 for (i = 0; text[i]; i++)
273 if (*text[i] && !regexec(view->regex, text[i], 1, &pmatch, 0))
274 return TRUE;
275 return FALSE;
278 void
279 select_view_line(struct view *view, unsigned long lineno)
281 struct position old = view->pos;
283 if (goto_view_line(view, view->pos.offset, lineno)) {
284 if (view_is_displayed(view)) {
285 if (old.offset != view->pos.offset) {
286 redraw_view(view);
287 } else {
288 draw_view_line(view, old.lineno - view->pos.offset);
289 draw_view_line(view, view->pos.lineno - view->pos.offset);
290 wnoutrefresh(view->win);
292 } else {
293 view->ops->select(view, &view->line[view->pos.lineno]);
298 static bool
299 find_matches(struct view *view)
301 size_t lineno;
303 /* Note, lineno is unsigned long so will wrap around in which case it
304 * will become bigger than view->lines. */
305 for (lineno = 0; lineno < view->lines; lineno++) {
306 if (!view->ops->grep(view, &view->line[lineno]))
307 continue;
309 if (!realloc_unsigned_ints(&view->matched_line, view->matched_lines, 1))
310 return FALSE;
312 view->matched_line[view->matched_lines++] = lineno;
315 return TRUE;
318 void
319 find_next(struct view *view, enum request request)
321 int direction;
322 size_t i;
324 if (!*view->grep) {
325 if (!*view->env->search)
326 report("No previous search");
327 else
328 search_view(view, request);
329 return;
332 switch (request) {
333 case REQ_SEARCH:
334 case REQ_FIND_NEXT:
335 direction = 1;
336 break;
338 case REQ_SEARCH_BACK:
339 case REQ_FIND_PREV:
340 direction = -1;
341 break;
343 default:
344 return;
347 if (!view->matched_lines && !find_matches(view)) {
348 report("Allocation failure");
349 return;
352 /* Note, `i` is unsigned and will wrap around in which case it
353 * will become bigger than view->matched_lines. */
354 i = direction > 0 ? 0 : view->matched_lines - 1;
355 for (; i < view->matched_lines; i += direction) {
356 size_t lineno = view->matched_line[i];
358 if (direction > 0 && lineno <= view->pos.lineno)
359 continue;
361 if (direction < 0 && lineno >= view->pos.lineno)
362 continue;
364 select_view_line(view, lineno);
365 report("Line %zu matches '%s' (%zu of %zu)", lineno + 1, view->grep, i + 1, view->matched_lines);
366 return;
369 report("No match found for '%s'", view->grep);
372 static void
373 reset_matches(struct view *view)
375 free(view->matched_line);
376 view->matched_line = NULL;
377 view->matched_lines = 0;
380 void
381 search_view(struct view *view, enum request request)
383 int regex_err;
384 int regex_flags = opt_ignore_case ? REG_ICASE : 0;
386 if (view->regex) {
387 regfree(view->regex);
388 *view->grep = 0;
389 } else {
390 view->regex = calloc(1, sizeof(*view->regex));
391 if (!view->regex)
392 return;
395 regex_err = regcomp(view->regex, view->env->search, REG_EXTENDED | regex_flags);
396 if (regex_err != 0) {
397 char buf[SIZEOF_STR] = "unknown error";
399 regerror(regex_err, view->regex, buf, sizeof(buf));
400 report("Search failed: %s", buf);
401 return;
404 string_copy(view->grep, view->env->search);
406 reset_matches(view);
408 find_next(view, request);
412 * View history
415 static bool
416 view_history_is_empty(struct view_history *history)
418 return !history->stack;
421 struct view_state *
422 push_view_history_state(struct view_history *history, struct position *position, void *data)
424 struct view_state *state = history->stack;
426 if (state && data && history->state_alloc &&
427 !memcmp(state->data, data, history->state_alloc))
428 return NULL;
430 state = calloc(1, sizeof(*state) + history->state_alloc);
431 if (!state)
432 return NULL;
434 state->prev = history->stack;
435 history->stack = state;
436 clear_position(&history->position);
437 state->position = *position;
438 state->data = &state[1];
439 if (data && history->state_alloc)
440 memcpy(state->data, data, history->state_alloc);
441 return state;
444 bool
445 pop_view_history_state(struct view_history *history, struct position *position, void *data)
447 struct view_state *state = history->stack;
449 if (view_history_is_empty(history))
450 return FALSE;
452 history->position = state->position;
453 history->stack = state->prev;
455 if (data && history->state_alloc)
456 memcpy(data, state->data, history->state_alloc);
457 if (position)
458 *position = state->position;
460 free(state);
461 return TRUE;
464 void
465 reset_view_history(struct view_history *history)
467 while (pop_view_history_state(history, NULL, NULL))
472 * Incremental updating
475 void
476 reset_view(struct view *view)
478 int i;
480 for (i = 0; i < view->lines; i++)
481 free(view->line[i].data);
482 free(view->line);
484 reset_matches(view);
485 view->prev_pos = view->pos;
486 clear_position(&view->pos);
488 if (view->columns)
489 view_column_reset(view);
491 view->line = NULL;
492 view->lines = 0;
493 view->vid[0] = 0;
494 view->custom_lines = 0;
495 view->update_secs = 0;
498 static bool
499 restore_view_position(struct view *view)
501 /* A view without a previous view is the first view */
502 if (!view->prev && view->env->lineno && view->env->lineno <= view->lines) {
503 select_view_line(view, view->env->lineno);
504 view->env->lineno = 0;
507 /* Ensure that the view position is in a valid state. */
508 if (!check_position(&view->prev_pos) ||
509 (view->pipe && view->lines <= view->prev_pos.lineno))
510 return goto_view_line(view, view->pos.offset, view->pos.lineno);
512 /* Changing the view position cancels the restoring. */
513 /* FIXME: Changing back to the first line is not detected. */
514 if (check_position(&view->pos)) {
515 clear_position(&view->prev_pos);
516 return FALSE;
519 if (goto_view_line(view, view->prev_pos.offset, view->prev_pos.lineno) &&
520 view_is_displayed(view))
521 werase(view->win);
523 view->pos.col = view->prev_pos.col;
524 clear_position(&view->prev_pos);
526 return TRUE;
529 void
530 end_update(struct view *view, bool force)
532 if (!view->pipe)
533 return;
534 while (!view->ops->read(view, NULL))
535 if (!force)
536 return;
537 if (force)
538 io_kill(view->pipe);
539 io_done(view->pipe);
540 view->pipe = NULL;
543 static void
544 setup_update(struct view *view, const char *vid)
546 reset_view(view);
547 /* XXX: Do not use string_copy_rev(), it copies until first space. */
548 string_ncopy(view->vid, vid, strlen(vid));
549 view->pipe = &view->io;
550 view->start_time = time(NULL);
553 static bool
554 view_no_refresh(struct view *view, enum open_flags flags)
556 bool reload = !!(flags & OPEN_ALWAYS_LOAD) || !view->lines;
558 return (!reload && !strcmp(view->vid, view->ops->id)) ||
559 ((flags & OPEN_REFRESH) && !view_can_refresh(view));
562 bool
563 begin_update(struct view *view, const char *dir, const char **argv, enum open_flags flags)
565 bool extra = !!(flags & (OPEN_EXTRA));
566 bool refresh = flags & (OPEN_REFRESH | OPEN_PREPARED | OPEN_STDIN);
567 enum io_flags forward_stdin = (flags & OPEN_FORWARD_STDIN) ? IO_RD_FORWARD_STDIN : 0;
568 enum io_flags with_stderr = (flags & OPEN_WITH_STDERR) ? IO_RD_WITH_STDERR : 0;
569 enum io_flags io_flags = forward_stdin | with_stderr;
571 if (view_no_refresh(view, flags))
572 return TRUE;
574 if (view->pipe) {
575 if (extra)
576 io_done(view->pipe);
577 else
578 end_update(view, TRUE);
581 view->unrefreshable = open_in_pager_mode(flags);
583 if (!refresh && argv) {
584 bool file_filter = !view_has_flags(view, VIEW_FILE_FILTER) || opt_file_filter;
586 view->dir = dir;
587 if (!argv_format(view->env, &view->argv, argv, !view->prev, file_filter)) {
588 report("Failed to format %s arguments", view->name);
589 return FALSE;
592 /* Put the current view ref value to the view title ref
593 * member. This is needed by the blob view. Most other
594 * views sets it automatically after loading because the
595 * first line is a commit line. */
596 string_copy_rev(view->ref, view->ops->id);
599 if (view->argv && view->argv[0] &&
600 !io_exec(&view->io, IO_RD, view->dir, opt_env, view->argv, io_flags)) {
601 report("Failed to open %s view", view->name);
602 return FALSE;
605 if (open_from_stdin(flags)) {
606 if (!io_open(&view->io, "%s", ""))
607 die("Failed to open stdin");
610 if (!extra)
611 setup_update(view, view->ops->id);
613 return TRUE;
616 bool
617 update_view(struct view *view)
619 /* Clear the view and redraw everything since the tree sorting
620 * might have rearranged things. */
621 bool redraw = view->lines == 0;
622 bool can_read = TRUE;
623 struct encoding *encoding = view->encoding ? view->encoding : default_encoding;
624 struct buffer line;
626 if (!view->pipe)
627 return TRUE;
629 if (!io_can_read(view->pipe, FALSE)) {
630 if (view->lines == 0 && view_is_displayed(view)) {
631 time_t secs = time(NULL) - view->start_time;
633 if (secs > 1 && secs > view->update_secs) {
634 if (view->update_secs == 0)
635 redraw_view(view);
636 update_view_title(view);
637 view->update_secs = secs;
640 return TRUE;
643 for (; io_get(view->pipe, &line, '\n', can_read); can_read = FALSE) {
644 if (encoding && !encoding_convert(encoding, &line)) {
645 report("Encoding failure");
646 end_update(view, TRUE);
647 return FALSE;
650 if (!view->ops->read(view, &line)) {
651 report("Allocation failure");
652 end_update(view, TRUE);
653 return FALSE;
657 if (io_error(view->pipe)) {
658 report("Failed to read: %s", io_strerror(view->pipe));
659 end_update(view, TRUE);
661 } else if (io_eof(view->pipe)) {
662 end_update(view, FALSE);
665 if (restore_view_position(view))
666 redraw = TRUE;
668 if (!view_is_displayed(view))
669 return TRUE;
671 if (redraw || view->force_redraw)
672 redraw_view_from(view, 0);
673 else
674 redraw_view_dirty(view);
675 view->force_redraw = FALSE;
677 /* Update the title _after_ the redraw so that if the redraw picks up a
678 * commit reference in view->ref it'll be available here. */
679 update_view_title(view);
680 return TRUE;
683 void
684 update_view_title(struct view *view)
686 WINDOW *window = view->title;
687 struct line *line = &view->line[view->pos.lineno];
688 unsigned int view_lines, lines;
690 assert(view_is_displayed(view));
692 if (view == display[current_view])
693 wbkgdset(window, get_view_attr(view, LINE_TITLE_FOCUS));
694 else
695 wbkgdset(window, get_view_attr(view, LINE_TITLE_BLUR));
697 werase(window);
698 mvwprintw(window, 0, 0, "[%s]", view->name);
700 if (*view->ref) {
701 wprintw(window, " %s", view->ref);
704 if (!view_has_flags(view, VIEW_CUSTOM_STATUS) && view_has_line(view, line) &&
705 line->lineno) {
706 wprintw(window, " - %s %d of %zd",
707 view->ops->type,
708 line->lineno,
709 view->lines - view->custom_lines);
712 if (view->pipe) {
713 time_t secs = time(NULL) - view->start_time;
715 /* Three git seconds are a long time ... */
716 if (secs > 2)
717 wprintw(window, " loading %lds", secs);
720 view_lines = view->pos.offset + view->height;
721 lines = view->lines ? MIN(view_lines, view->lines) * 100 / view->lines : 0;
722 mvwprintw(window, 0, view->width - count_digits(lines) - 1, "%d%%", lines);
724 wnoutrefresh(window);
728 * View opening
731 void
732 split_view(struct view *prev, struct view *view)
734 display[1] = view;
735 current_view = opt_focus_child ? 1 : 0;
736 view->parent = prev;
737 resize_display();
739 if (prev->pos.lineno - prev->pos.offset >= prev->height) {
740 /* Take the title line into account. */
741 int lines = prev->pos.lineno - prev->pos.offset - prev->height + 1;
743 /* Scroll the view that was split if the current line is
744 * outside the new limited view. */
745 do_scroll_view(prev, lines);
748 if (view != prev && view_is_displayed(prev)) {
749 /* "Blur" the previous view. */
750 update_view_title(prev);
754 void
755 maximize_view(struct view *view, bool redraw)
757 memset(display, 0, sizeof(display));
758 current_view = 0;
759 display[current_view] = view;
760 resize_display();
761 if (redraw) {
762 redraw_display(FALSE);
763 report_clear();
767 void
768 load_view(struct view *view, struct view *prev, enum open_flags flags)
770 bool refresh = !view_no_refresh(view, flags);
772 /* When prev == view it means this is the first loaded view. */
773 if (prev && view != prev) {
774 view->prev = prev;
777 if (!refresh && view_can_refresh(view) &&
778 watch_update_single(&view->watch, WATCH_EVENT_SWITCH_VIEW)) {
779 refresh = watch_dirty(&view->watch);
780 if (refresh)
781 flags |= OPEN_REFRESH;
784 if (refresh) {
785 if (view->pipe)
786 end_update(view, TRUE);
787 if (view->ops->private_size) {
788 if (!view->private) {
789 view->private = calloc(1, view->ops->private_size);
790 } else {
791 if (view->ops->done)
792 view->ops->done(view);
793 memset(view->private, 0, view->ops->private_size);
797 if (!view->ops->open(view, flags))
798 return;
801 if (prev) {
802 bool split = !!(flags & OPEN_SPLIT);
804 if (split) {
805 split_view(prev, view);
806 } else {
807 maximize_view(view, FALSE);
811 restore_view_position(view);
813 if (view->pipe && view->lines == 0) {
814 /* Clear the old view and let the incremental updating refill
815 * the screen. */
816 werase(view->win);
817 if (!(flags & (OPEN_RELOAD | OPEN_REFRESH)))
818 clear_position(&view->prev_pos);
819 report_clear();
820 } else if (view_is_displayed(view)) {
821 redraw_view(view);
822 report_clear();
826 #define refresh_view(view) load_view(view, NULL, OPEN_REFRESH)
827 #define reload_view(view) load_view(view, NULL, OPEN_RELOAD)
829 void
830 open_view(struct view *prev, struct view *view, enum open_flags flags)
832 bool reload = !!(flags & (OPEN_RELOAD | OPEN_PREPARED));
833 int nviews = displayed_views();
835 assert(flags ^ OPEN_REFRESH);
837 if (view == prev && nviews == 1 && !reload) {
838 report("Already in %s view", view->name);
839 return;
842 if (!view_has_flags(view, VIEW_NO_GIT_DIR) && !repo.git_dir[0]) {
843 report("The %s view is disabled in pager mode", view->name);
844 return;
847 if (!view->keymap)
848 view->keymap = get_keymap(view->name, strlen(view->name));
849 load_view(view, prev ? prev : view, flags);
852 void
853 open_argv(struct view *prev, struct view *view, const char *argv[], const char *dir, enum open_flags flags)
855 if (view->pipe)
856 end_update(view, TRUE);
857 view->dir = dir;
859 if (!argv_copy(&view->argv, argv)) {
860 report("Failed to open %s view: %s", view->name, io_strerror(&view->io));
861 } else {
862 open_view(prev, view, flags | OPEN_PREPARED);
867 * Various utilities.
870 static struct view *sorting_view;
872 #define sort_order_reverse(state, result) \
873 ((state)->reverse ? -(result) : (result))
875 #define sort_order(state, cmp, o1, o2) \
876 sort_order_reverse(state, (!(o1) || !(o2)) ? !!(o2) - !!(o1) : cmp(o1, o2))
878 #define number_compare(size1, size2) (*(size1) - *(size2))
880 #define mode_is_dir(mode) ((mode) && S_ISDIR(*(mode)))
882 static int
883 sort_view_compare(const void *l1, const void *l2)
885 const struct line *line1 = l1;
886 const struct line *line2 = l2;
887 struct view_column_data column_data1 = {};
888 struct view_column_data column_data2 = {};
889 struct sort_state *sort = &sorting_view->sort;
891 if (!sorting_view->ops->get_column_data(sorting_view, line1, &column_data1))
892 return -1;
893 else if (!sorting_view->ops->get_column_data(sorting_view, line2, &column_data2))
894 return 1;
896 switch (get_sort_field(sorting_view)) {
897 case VIEW_COLUMN_AUTHOR:
898 return sort_order(sort, ident_compare, column_data1.author, column_data2.author);
900 case VIEW_COLUMN_DATE:
901 return sort_order(sort, timecmp, column_data1.date, column_data2.date);
903 case VIEW_COLUMN_ID:
904 if (column_data1.reflog && column_data2.reflog)
905 return sort_order(sort, strcmp, column_data1.reflog, column_data2.reflog);
906 return sort_order(sort, strcmp, column_data1.id, column_data2.id);
908 case VIEW_COLUMN_FILE_NAME:
909 if (mode_is_dir(column_data1.mode) != mode_is_dir(column_data2.mode))
910 return sort_order_reverse(sort, mode_is_dir(column_data1.mode) ? -1 : 1);
911 return sort_order(sort, strcmp, column_data1.file_name, column_data2.file_name);
913 case VIEW_COLUMN_FILE_SIZE:
914 return sort_order(sort, number_compare, column_data1.file_size, column_data2.file_size);
916 case VIEW_COLUMN_LINE_NUMBER:
917 return sort_order_reverse(sort, line1->lineno - line2->lineno);
919 case VIEW_COLUMN_MODE:
920 return sort_order(sort, number_compare, column_data1.mode, column_data2.mode);
922 case VIEW_COLUMN_REF:
923 return sort_order(sort, ref_compare, column_data1.ref, column_data2.ref);
925 case VIEW_COLUMN_COMMIT_TITLE:
926 return sort_order(sort, strcmp, column_data1.commit_title, column_data2.commit_title);
928 case VIEW_COLUMN_SECTION:
929 return sort_order(sort, strcmp, column_data1.section->opt.section.text,
930 column_data2.section->opt.section.text);
932 case VIEW_COLUMN_STATUS:
933 return sort_order(sort, number_compare, column_data1.status, column_data2.status);
935 case VIEW_COLUMN_TEXT:
936 return sort_order(sort, strcmp, column_data1.text, column_data2.text);
939 return 0;
942 void
943 sort_view(struct view *view, bool change_field)
945 struct sort_state *state = &view->sort;
947 if (change_field) {
948 while (TRUE) {
949 state->current = state->current->next
950 ? state->current->next : view->columns;
951 if (get_sort_field(view) == VIEW_COLUMN_ID &&
952 !state->current->opt.id.display)
953 continue;
954 break;
956 } else {
957 state->reverse = !state->reverse;
960 sorting_view = view;
961 qsort(view->line, view->lines, sizeof(*view->line), sort_view_compare);
964 static const char *
965 view_column_text(struct view *view, struct view_column_data *column_data,
966 struct view_column *column)
968 const char *text = "";
970 switch (column->type) {
971 case VIEW_COLUMN_AUTHOR:
972 if (column_data->author)
973 text = mkauthor(column_data->author, column->opt.author.width, column->opt.author.display);
974 break;
976 case VIEW_COLUMN_COMMIT_TITLE:
977 text = column_data->commit_title;
978 break;
980 case VIEW_COLUMN_DATE:
981 if (column_data->date)
982 text = mkdate(column_data->date, column->opt.date.display);
983 break;
985 case VIEW_COLUMN_REF:
986 if (column_data->ref)
987 text = column_data->ref->name;
988 break;
990 case VIEW_COLUMN_FILE_NAME:
991 if (column_data->file_name)
992 text = column_data->file_name;
993 break;
995 case VIEW_COLUMN_FILE_SIZE:
996 if (column_data->file_size)
997 text = mkfilesize(*column_data->file_size, column->opt.file_size.display);
998 break;
1000 case VIEW_COLUMN_ID:
1001 if (column->opt.id.display)
1002 text = column_data->reflog ? column_data->reflog : column_data->id;
1003 break;
1005 case VIEW_COLUMN_LINE_NUMBER:
1006 break;
1008 case VIEW_COLUMN_MODE:
1009 if (column_data->mode)
1010 text = mkmode(*column_data->mode);
1011 break;
1013 case VIEW_COLUMN_STATUS:
1014 if (column_data->status)
1015 text = mkstatus(*column_data->status, column->opt.status.display);
1016 break;
1018 case VIEW_COLUMN_SECTION:
1019 text = column_data->section->opt.section.text;
1020 break;
1022 case VIEW_COLUMN_TEXT:
1023 text = column_data->text;
1024 break;
1027 return text ? text : "";
1030 static bool
1031 grep_refs(struct view *view, struct view_column *column, const struct ref_list *list)
1033 regmatch_t pmatch;
1034 size_t i;
1036 if (!list)
1037 return FALSE;
1039 for (i = 0; i < list->size; i++) {
1040 if (!regexec(view->regex, list->refs[i]->name, 1, &pmatch, 0))
1041 return TRUE;
1044 return FALSE;
1047 bool
1048 view_column_grep(struct view *view, struct line *line)
1050 struct view_column_data column_data = {};
1051 bool ok = view->ops->get_column_data(view, line, &column_data);
1052 struct view_column *column;
1054 if (!ok)
1055 return FALSE;
1057 for (column = view->columns; column; column = column->next) {
1058 const char *text[] = {
1059 view_column_text(view, &column_data, column),
1060 NULL
1063 if (grep_text(view, text))
1064 return TRUE;
1066 if (column->type == VIEW_COLUMN_COMMIT_TITLE &&
1067 column->opt.commit_title.refs &&
1068 grep_refs(view, column, column_data.refs))
1069 return TRUE;
1072 return FALSE;
1075 bool
1076 view_column_info_changed(struct view *view, bool update)
1078 struct view_column *column;
1079 bool changed = FALSE;
1081 for (column = view->columns; column; column = column->next) {
1082 if (memcmp(&column->prev_opt, &column->opt, sizeof(column->opt))) {
1083 if (!update)
1084 return TRUE;
1085 column->prev_opt = column->opt;
1086 changed = TRUE;
1090 return changed;
1093 void
1094 view_column_reset(struct view *view)
1096 struct view_column *column;
1098 view_column_info_changed(view, TRUE);
1099 for (column = view->columns; column; column = column->next)
1100 column->width = 0;
1103 static enum status_code
1104 parse_view_column_config(char **pos, const char **name, const char **value, bool first)
1106 size_t len = strcspn(*pos, ",");
1107 size_t optlen;
1109 if (strlen(*pos) > len)
1110 (*pos)[len] = 0;
1111 optlen = strcspn(*pos, ":=");
1113 if (first) {
1114 *name = "display";
1116 if (optlen == len) {
1117 *value = len ? *pos : "yes";
1118 *pos += len + 1;
1119 return SUCCESS;
1122 /* Fake boolean enum value. */
1123 *value = "yes";
1124 return SUCCESS;
1127 *name = *pos;
1128 if (optlen == len)
1129 *value = "yes";
1130 else
1131 *value = *pos + optlen + 1;
1132 (*pos)[optlen] = 0;
1133 *pos += len + 1;
1135 return SUCCESS;
1138 static enum status_code
1139 parse_view_column_option(struct view_column *column,
1140 const char *opt_name, const char *opt_value)
1142 #define DEFINE_COLUMN_OPTION_INFO(name, type, flags) \
1143 { #name, STRING_SIZE(#name), #type, &opt->name },
1145 #define DEFINE_COLUMN_OPTIONS_PARSE(name, id, options) \
1146 if (column->type == VIEW_COLUMN_##id) { \
1147 struct name##_options *opt = &column->opt.name; \
1148 struct option_info info[] = { \
1149 options(DEFINE_COLUMN_OPTION_INFO) \
1150 }; \
1151 struct option_info *option = find_option_info(info, ARRAY_SIZE(info), opt_name); \
1152 if (!option) \
1153 return error("Unknown option `%s' for column %s", opt_name, \
1154 view_column_name(VIEW_COLUMN_##id)); \
1155 return parse_option(option, #name, opt_value); \
1158 COLUMN_OPTIONS(DEFINE_COLUMN_OPTIONS_PARSE);
1160 return error("Unknown view column option: %s", opt_name);
1163 static enum status_code
1164 parse_view_column_type(struct view_column *column, const char **arg)
1166 enum view_column_type type;
1167 size_t typelen = strcspn(*arg, ":,");
1169 for (type = 0; type < view_column_type_map->size; type++)
1170 if (enum_equals(view_column_type_map->entries[type], *arg, typelen)) {
1171 *arg += typelen + !!(*arg)[typelen];
1172 column->type = type;
1173 return SUCCESS;
1176 return error("Failed to parse view column type: %.*s", (int) typelen, *arg);
1179 static struct view *
1180 find_view(const char *view_name)
1182 struct view *view;
1183 int i;
1185 foreach_view(view, i)
1186 if (!strncmp(view_name, view->name, strlen(view->name)))
1187 return view;
1189 return NULL;
1192 enum status_code
1193 parse_view_config(const char *view_name, const char *argv[])
1195 enum status_code code = SUCCESS;
1196 size_t size = argv_size(argv);
1197 struct view_column *columns;
1198 struct view_column *column;
1199 struct view *view = find_view(view_name);
1200 int i;
1202 if (!view)
1203 return error("Unknown view: %s", view_name);
1205 columns = calloc(size, sizeof(*columns));
1206 if (!columns)
1207 return ERROR_OUT_OF_MEMORY;
1209 for (i = 0, column = NULL; code == SUCCESS && i < size; i++) {
1210 const char *arg = argv[i];
1211 char buf[SIZEOF_STR] = "";
1212 char *pos, *end;
1213 bool first = TRUE;
1215 if (column)
1216 column->next = &columns[i];
1217 column = &columns[i];
1219 code = parse_view_column_type(column, &arg);
1220 if (code != SUCCESS)
1221 break;
1223 if (!(view->ops->column_bits & (1 << column->type)))
1224 return error("The %s view does not support %s column", view->name,
1225 view_column_name(column->type));
1227 if ((column->type == VIEW_COLUMN_TEXT ||
1228 column->type == VIEW_COLUMN_COMMIT_TITLE) &&
1229 i + 1 < size)
1230 return error("The %s column must always be last",
1231 view_column_name(column->type));
1233 string_ncopy(buf, arg, strlen(arg));
1235 for (pos = buf, end = pos + strlen(pos); code == SUCCESS && pos <= end; first = FALSE) {
1236 const char *name = NULL;
1237 const char *value = NULL;
1239 code = parse_view_column_config(&pos, &name, &value, first);
1240 if (code == SUCCESS)
1241 code = parse_view_column_option(column, name, value);
1244 column->prev_opt = column->opt;
1247 if (code == SUCCESS) {
1248 free(view->columns);
1249 view->columns = columns;
1250 view->sort.current = view->columns;
1251 } else {
1252 free(columns);
1255 return code;
1258 struct view_column *
1259 get_view_column(struct view *view, enum view_column_type type)
1261 struct view_column *column;
1263 for (column = view->columns; column; column = column->next)
1264 if (column->type == type)
1265 return column;
1266 return NULL;
1269 bool
1270 view_column_info_update(struct view *view, struct line *line)
1272 struct view_column_data column_data = {};
1273 struct view_column *column;
1274 bool changed = FALSE;
1276 if (!view->ops->get_column_data(view, line, &column_data))
1277 return FALSE;
1279 for (column = view->columns; column; column = column->next) {
1280 const char *text = view_column_text(view, &column_data, column);
1281 int width = 0;
1283 switch (column->type) {
1284 case VIEW_COLUMN_AUTHOR:
1285 width = column->opt.author.width;
1286 break;
1288 case VIEW_COLUMN_COMMIT_TITLE:
1289 width = column->opt.commit_title.width;
1290 break;
1292 case VIEW_COLUMN_DATE:
1293 width = column->opt.date.width;
1294 break;
1296 case VIEW_COLUMN_FILE_NAME:
1297 width = column->opt.file_name.width;
1298 break;
1300 case VIEW_COLUMN_FILE_SIZE:
1301 width = column->opt.file_size.width;
1302 break;
1304 case VIEW_COLUMN_ID:
1305 width = column->opt.id.width;
1306 if (!width)
1307 width = opt_id_width;
1308 if (!column_data.reflog && !width)
1309 width = 7;
1310 break;
1312 case VIEW_COLUMN_LINE_NUMBER:
1313 if (column_data.line_number)
1314 width = count_digits(*column_data.line_number);
1315 else
1316 width = count_digits(view->lines);
1317 if (width < 3)
1318 width = 3;
1319 break;
1321 case VIEW_COLUMN_MODE:
1322 width = column->opt.mode.width;
1323 break;
1325 case VIEW_COLUMN_REF:
1326 width = column->opt.ref.width;
1327 break;
1329 case VIEW_COLUMN_SECTION:
1330 break;
1332 case VIEW_COLUMN_STATUS:
1333 width = column->opt.status.width;
1334 break;
1336 case VIEW_COLUMN_TEXT:
1337 width = column->opt.text.width;
1338 break;
1341 if (*text && !width)
1342 width = utf8_width(text);
1344 if (width > column->width) {
1345 column->width = width;
1346 changed = TRUE;
1350 if (changed)
1351 view->force_redraw = TRUE;
1352 return changed;
1355 struct line *
1356 find_line_by_type(struct view *view, struct line *line, enum line_type type, int direction)
1358 for (; view_has_line(view, line); line += direction)
1359 if (line->type == type)
1360 return line;
1362 return NULL;
1366 * Line utilities.
1369 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
1371 struct line *
1372 add_line_at(struct view *view, unsigned long pos, const void *data, enum line_type type, size_t data_size, bool custom)
1374 struct line *line;
1375 unsigned long lineno;
1377 if (!realloc_lines(&view->line, view->lines, 1))
1378 return NULL;
1380 if (data_size) {
1381 void *alloc_data = calloc(1, data_size);
1383 if (!alloc_data)
1384 return NULL;
1386 if (data)
1387 memcpy(alloc_data, data, data_size);
1388 data = alloc_data;
1391 if (pos < view->lines) {
1392 view->lines++;
1393 line = view->line + pos;
1394 lineno = line->lineno;
1396 memmove(line + 1, line, (view->lines - pos) * sizeof(*view->line));
1397 while (pos < view->lines) {
1398 view->line[pos].lineno++;
1399 view->line[pos++].dirty = 1;
1401 } else {
1402 line = &view->line[view->lines++];
1403 lineno = view->lines - view->custom_lines;
1406 memset(line, 0, sizeof(*line));
1407 line->type = type;
1408 line->data = (void *) data;
1409 line->dirty = 1;
1411 if (custom)
1412 view->custom_lines++;
1413 else
1414 line->lineno = lineno;
1416 return line;
1419 struct line *
1420 add_line(struct view *view, const void *data, enum line_type type, size_t data_size, bool custom)
1422 return add_line_at(view, view->lines, data, type, data_size, custom);
1425 struct line *
1426 add_line_alloc_(struct view *view, void **ptr, enum line_type type, size_t data_size, bool custom)
1428 struct line *line = add_line(view, NULL, type, data_size, custom);
1430 if (line)
1431 *ptr = line->data;
1432 return line;
1435 struct line *
1436 add_line_nodata(struct view *view, enum line_type type)
1438 return add_line(view, NULL, type, 0, FALSE);
1441 struct line *
1442 add_line_text(struct view *view, const char *text, enum line_type type)
1444 struct line *line = add_line(view, text, type, strlen(text) + 1, FALSE);
1446 if (line && view->ops->column_bits)
1447 view_column_info_update(view, line);
1448 return line;
1451 struct line * PRINTF_LIKE(3, 4)
1452 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
1454 char buf[SIZEOF_STR];
1455 int retval;
1457 FORMAT_BUFFER(buf, sizeof(buf), fmt, retval, FALSE);
1458 return retval >= 0 ? add_line_text(view, buf, type) : NULL;
1462 * Global view state.
1465 /* Included last to not pollute the rest of the file. */
1466 #include "tig/main.h"
1467 #include "tig/diff.h"
1468 #include "tig/log.h"
1469 #include "tig/tree.h"
1470 #include "tig/blob.h"
1471 #include "tig/blame.h"
1472 #include "tig/refs.h"
1473 #include "tig/status.h"
1474 #include "tig/stage.h"
1475 #include "tig/stash.h"
1476 #include "tig/grep.h"
1477 #include "tig/pager.h"
1478 #include "tig/help.h"
1480 static struct view *views[] = {
1481 #define VIEW_DATA(id, name) &name##_view
1482 VIEW_INFO(VIEW_DATA)
1485 struct view *
1486 get_view(int i)
1488 return 0 <= i && i < ARRAY_SIZE(views) ? views[i] : NULL;
1491 /* vim: set ts=8 sw=8 noexpandtab: */