Fix infinite loop when parsing view columns
[tig.git] / src / view.c
blob056ecba485ea2b7e3574eba2098e5e614ddc10fe
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 int forward_stdin = (flags & OPEN_FORWARD_STDIN) ? IO_RD_FORWARD_STDIN : 0;
568 int with_stderr = (flags & OPEN_WITH_STDERR) ? IO_RD_WITH_STDERR : 0;
569 int 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 char *line;
620 /* Clear the view and redraw everything since the tree sorting
621 * might have rearranged things. */
622 bool redraw = view->lines == 0;
623 bool can_read = TRUE;
624 struct encoding *encoding = view->encoding ? view->encoding : default_encoding;
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 (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
644 if (encoding) {
645 line = encoding_convert(encoding, line);
648 if (!view->ops->read(view, line)) {
649 report("Allocation failure");
650 end_update(view, TRUE);
651 return FALSE;
655 if (io_error(view->pipe)) {
656 report("Failed to read: %s", io_strerror(view->pipe));
657 end_update(view, TRUE);
659 } else if (io_eof(view->pipe)) {
660 end_update(view, FALSE);
663 if (restore_view_position(view))
664 redraw = TRUE;
666 if (!view_is_displayed(view))
667 return TRUE;
669 if (redraw || view->force_redraw)
670 redraw_view_from(view, 0);
671 else
672 redraw_view_dirty(view);
673 view->force_redraw = FALSE;
675 /* Update the title _after_ the redraw so that if the redraw picks up a
676 * commit reference in view->ref it'll be available here. */
677 update_view_title(view);
678 return TRUE;
681 void
682 update_view_title(struct view *view)
684 WINDOW *window = view->title;
685 struct line *line = &view->line[view->pos.lineno];
686 unsigned int view_lines, lines;
688 assert(view_is_displayed(view));
690 if (view == display[current_view])
691 wbkgdset(window, get_view_attr(view, LINE_TITLE_FOCUS));
692 else
693 wbkgdset(window, get_view_attr(view, LINE_TITLE_BLUR));
695 werase(window);
696 mvwprintw(window, 0, 0, "[%s]", view->name);
698 if (*view->ref) {
699 wprintw(window, " %s", view->ref);
702 if (!view_has_flags(view, VIEW_CUSTOM_STATUS) && view_has_line(view, line) &&
703 line->lineno) {
704 wprintw(window, " - %s %d of %zd",
705 view->ops->type,
706 line->lineno,
707 view->lines - view->custom_lines);
710 if (view->pipe) {
711 time_t secs = time(NULL) - view->start_time;
713 /* Three git seconds are a long time ... */
714 if (secs > 2)
715 wprintw(window, " loading %lds", secs);
718 view_lines = view->pos.offset + view->height;
719 lines = view->lines ? MIN(view_lines, view->lines) * 100 / view->lines : 0;
720 mvwprintw(window, 0, view->width - count_digits(lines) - 1, "%d%%", lines);
722 wnoutrefresh(window);
726 * View opening
729 void
730 split_view(struct view *prev, struct view *view)
732 display[1] = view;
733 current_view = opt_focus_child ? 1 : 0;
734 view->parent = prev;
735 resize_display();
737 if (prev->pos.lineno - prev->pos.offset >= prev->height) {
738 /* Take the title line into account. */
739 int lines = prev->pos.lineno - prev->pos.offset - prev->height + 1;
741 /* Scroll the view that was split if the current line is
742 * outside the new limited view. */
743 do_scroll_view(prev, lines);
746 if (view != prev && view_is_displayed(prev)) {
747 /* "Blur" the previous view. */
748 update_view_title(prev);
752 void
753 maximize_view(struct view *view, bool redraw)
755 memset(display, 0, sizeof(display));
756 current_view = 0;
757 display[current_view] = view;
758 resize_display();
759 if (redraw) {
760 redraw_display(FALSE);
761 report_clear();
765 void
766 load_view(struct view *view, struct view *prev, enum open_flags flags)
768 bool refresh = !view_no_refresh(view, flags);
770 /* When prev == view it means this is the first loaded view. */
771 if (prev && view != prev) {
772 view->prev = prev;
775 if (!refresh && view_can_refresh(view) &&
776 watch_update_single(&view->watch, WATCH_EVENT_SWITCH_VIEW)) {
777 refresh = watch_dirty(&view->watch);
778 if (refresh)
779 flags |= OPEN_REFRESH;
782 if (refresh) {
783 if (view->pipe)
784 end_update(view, TRUE);
785 if (view->ops->private_size) {
786 if (!view->private) {
787 view->private = calloc(1, view->ops->private_size);
788 } else {
789 if (view->ops->done)
790 view->ops->done(view);
791 memset(view->private, 0, view->ops->private_size);
795 if (!view->ops->open(view, flags))
796 return;
799 if (prev) {
800 bool split = !!(flags & OPEN_SPLIT);
802 if (split) {
803 split_view(prev, view);
804 } else {
805 maximize_view(view, FALSE);
809 restore_view_position(view);
811 if (view->pipe && view->lines == 0) {
812 /* Clear the old view and let the incremental updating refill
813 * the screen. */
814 werase(view->win);
815 if (!(flags & (OPEN_RELOAD | OPEN_REFRESH)))
816 clear_position(&view->prev_pos);
817 report_clear();
818 } else if (view_is_displayed(view)) {
819 redraw_view(view);
820 report_clear();
824 #define refresh_view(view) load_view(view, NULL, OPEN_REFRESH)
825 #define reload_view(view) load_view(view, NULL, OPEN_RELOAD)
827 void
828 open_view(struct view *prev, struct view *view, enum open_flags flags)
830 bool reload = !!(flags & (OPEN_RELOAD | OPEN_PREPARED));
831 int nviews = displayed_views();
833 assert(flags ^ OPEN_REFRESH);
835 if (view == prev && nviews == 1 && !reload) {
836 report("Already in %s view", view->name);
837 return;
840 if (!view_has_flags(view, VIEW_NO_GIT_DIR) && !repo.git_dir[0]) {
841 report("The %s view is disabled in pager mode", view->name);
842 return;
845 if (!view->keymap)
846 view->keymap = get_keymap(view->name, strlen(view->name));
847 load_view(view, prev ? prev : view, flags);
850 void
851 open_argv(struct view *prev, struct view *view, const char *argv[], const char *dir, enum open_flags flags)
853 if (view->pipe)
854 end_update(view, TRUE);
855 view->dir = dir;
857 if (!argv_copy(&view->argv, argv)) {
858 report("Failed to open %s view: %s", view->name, io_strerror(&view->io));
859 } else {
860 open_view(prev, view, flags | OPEN_PREPARED);
865 * Various utilities.
868 static struct view *sorting_view;
870 #define sort_order_reverse(state, result) \
871 ((state)->reverse ? -(result) : (result))
873 #define sort_order(state, cmp, o1, o2) \
874 sort_order_reverse(state, (!(o1) || !(o2)) ? !!(o2) - !!(o1) : cmp(o1, o2))
876 #define number_compare(size1, size2) (*(size1) - *(size2))
878 #define mode_is_dir(mode) ((mode) && S_ISDIR(*(mode)))
880 static int
881 sort_view_compare(const void *l1, const void *l2)
883 const struct line *line1 = l1;
884 const struct line *line2 = l2;
885 struct view_column_data column_data1 = {};
886 struct view_column_data column_data2 = {};
887 struct sort_state *sort = &sorting_view->sort;
889 if (!sorting_view->ops->get_column_data(sorting_view, line1, &column_data1))
890 return -1;
891 else if (!sorting_view->ops->get_column_data(sorting_view, line2, &column_data2))
892 return 1;
894 switch (get_sort_field(sorting_view)) {
895 case VIEW_COLUMN_AUTHOR:
896 return sort_order(sort, ident_compare, column_data1.author, column_data2.author);
898 case VIEW_COLUMN_DATE:
899 return sort_order(sort, timecmp, column_data1.date, column_data2.date);
901 case VIEW_COLUMN_ID:
902 if (column_data1.reflog && column_data2.reflog)
903 return sort_order(sort, strcmp, column_data1.reflog, column_data2.reflog);
904 return sort_order(sort, strcmp, column_data1.id, column_data2.id);
906 case VIEW_COLUMN_FILE_NAME:
907 if (mode_is_dir(column_data1.mode) != mode_is_dir(column_data2.mode))
908 return sort_order_reverse(sort, mode_is_dir(column_data1.mode) ? -1 : 1);
909 return sort_order(sort, strcmp, column_data1.file_name, column_data2.file_name);
911 case VIEW_COLUMN_FILE_SIZE:
912 return sort_order(sort, number_compare, column_data1.file_size, column_data2.file_size);
914 case VIEW_COLUMN_LINE_NUMBER:
915 return sort_order_reverse(sort, line1->lineno - line2->lineno);
917 case VIEW_COLUMN_MODE:
918 return sort_order(sort, number_compare, column_data1.mode, column_data2.mode);
920 case VIEW_COLUMN_REF:
921 return sort_order(sort, ref_compare, column_data1.ref, column_data2.ref);
923 case VIEW_COLUMN_COMMIT_TITLE:
924 return sort_order(sort, strcmp, column_data1.commit_title, column_data2.commit_title);
926 case VIEW_COLUMN_SECTION:
927 return sort_order(sort, strcmp, column_data1.section->opt.section.text,
928 column_data2.section->opt.section.text);
930 case VIEW_COLUMN_STATUS:
931 return sort_order(sort, number_compare, column_data1.status, column_data2.status);
933 case VIEW_COLUMN_TEXT:
934 return sort_order(sort, strcmp, column_data1.text, column_data2.text);
937 return 0;
940 void
941 sort_view(struct view *view, bool change_field)
943 struct sort_state *state = &view->sort;
945 if (change_field) {
946 while (TRUE) {
947 state->current = state->current->next
948 ? state->current->next : view->columns;
949 if (get_sort_field(view) == VIEW_COLUMN_ID &&
950 !state->current->opt.id.display)
951 continue;
952 break;
954 } else {
955 state->reverse = !state->reverse;
958 sorting_view = view;
959 qsort(view->line, view->lines, sizeof(*view->line), sort_view_compare);
962 static const char *
963 view_column_text(struct view *view, struct view_column_data *column_data,
964 struct view_column *column)
966 const char *text = "";
968 switch (column->type) {
969 case VIEW_COLUMN_AUTHOR:
970 if (column_data->author)
971 text = mkauthor(column_data->author, column->opt.author.width, column->opt.author.display);
972 break;
974 case VIEW_COLUMN_COMMIT_TITLE:
975 text = column_data->commit_title;
976 break;
978 case VIEW_COLUMN_DATE:
979 if (column_data->date)
980 text = mkdate(column_data->date, column->opt.date.display);
981 break;
983 case VIEW_COLUMN_REF:
984 if (column_data->ref)
985 text = column_data->ref->name;
986 break;
988 case VIEW_COLUMN_FILE_NAME:
989 if (column_data->file_name)
990 text = column_data->file_name;
991 break;
993 case VIEW_COLUMN_FILE_SIZE:
994 if (column_data->file_size)
995 text = mkfilesize(*column_data->file_size, column->opt.file_size.display);
996 break;
998 case VIEW_COLUMN_ID:
999 if (column->opt.id.display)
1000 text = column_data->reflog ? column_data->reflog : column_data->id;
1001 break;
1003 case VIEW_COLUMN_LINE_NUMBER:
1004 break;
1006 case VIEW_COLUMN_MODE:
1007 if (column_data->mode)
1008 text = mkmode(*column_data->mode);
1009 break;
1011 case VIEW_COLUMN_STATUS:
1012 if (column_data->status)
1013 text = mkstatus(*column_data->status, column->opt.status.display);
1014 break;
1016 case VIEW_COLUMN_SECTION:
1017 text = column_data->section->opt.section.text;
1018 break;
1020 case VIEW_COLUMN_TEXT:
1021 text = column_data->text;
1022 break;
1025 return text ? text : "";
1028 static bool
1029 grep_refs(struct view *view, struct view_column *column, const struct ref_list *list)
1031 regmatch_t pmatch;
1032 size_t i;
1034 if (!list)
1035 return FALSE;
1037 for (i = 0; i < list->size; i++) {
1038 if (!regexec(view->regex, list->refs[i]->name, 1, &pmatch, 0))
1039 return TRUE;
1042 return FALSE;
1045 bool
1046 view_column_grep(struct view *view, struct line *line)
1048 struct view_column_data column_data = {};
1049 bool ok = view->ops->get_column_data(view, line, &column_data);
1050 struct view_column *column;
1052 if (!ok)
1053 return FALSE;
1055 for (column = view->columns; column; column = column->next) {
1056 const char *text[] = {
1057 view_column_text(view, &column_data, column),
1058 NULL
1061 if (grep_text(view, text))
1062 return TRUE;
1064 if (column->type == VIEW_COLUMN_COMMIT_TITLE &&
1065 column->opt.commit_title.refs &&
1066 grep_refs(view, column, column_data.refs))
1067 return TRUE;
1070 return FALSE;
1073 bool
1074 view_column_info_changed(struct view *view, bool update)
1076 struct view_column *column;
1077 bool changed = FALSE;
1079 for (column = view->columns; column; column = column->next) {
1080 if (memcmp(&column->prev_opt, &column->opt, sizeof(column->opt))) {
1081 if (!update)
1082 return TRUE;
1083 column->prev_opt = column->opt;
1084 changed = TRUE;
1088 return changed;
1091 void
1092 view_column_reset(struct view *view)
1094 struct view_column *column;
1096 view_column_info_changed(view, TRUE);
1097 for (column = view->columns; column; column = column->next)
1098 column->width = 0;
1101 static enum status_code
1102 parse_view_column_config(char **pos, const char **name, const char **value, bool first)
1104 size_t len = strcspn(*pos, ",");
1105 size_t optlen;
1107 if (strlen(*pos) > len)
1108 (*pos)[len] = 0;
1109 optlen = strcspn(*pos, ":=");
1111 if (first) {
1112 *name = "display";
1114 if (optlen == len) {
1115 *value = len ? *pos : "yes";
1116 *pos += len + 1;
1117 return SUCCESS;
1120 /* Fake boolean enum value. */
1121 *value = "yes";
1122 return SUCCESS;
1125 *name = *pos;
1126 if (optlen == len)
1127 *value = "yes";
1128 else
1129 *value = *pos + optlen + 1;
1130 (*pos)[optlen] = 0;
1131 *pos += len + 1;
1133 return SUCCESS;
1136 static enum status_code
1137 parse_view_column_option(struct view_column *column,
1138 const char *opt_name, const char *opt_value)
1140 #define DEFINE_COLUMN_OPTION_INFO(name, type, flags) \
1141 { #name, STRING_SIZE(#name), #type, &opt->name },
1143 #define DEFINE_COLUMN_OPTIONS_PARSE(name, id, options) \
1144 if (column->type == VIEW_COLUMN_##id) { \
1145 struct name##_options *opt = &column->opt.name; \
1146 struct option_info info[] = { \
1147 options(DEFINE_COLUMN_OPTION_INFO) \
1148 }; \
1149 struct option_info *option = find_option_info(info, ARRAY_SIZE(info), opt_name); \
1150 if (!option) \
1151 return error("Unknown option `%s' for column %s", opt_name, \
1152 view_column_name(VIEW_COLUMN_##id)); \
1153 return parse_option(option, #name, opt_value); \
1156 COLUMN_OPTIONS(DEFINE_COLUMN_OPTIONS_PARSE);
1158 return error("Unknown view column option: %s", opt_name);
1161 static enum status_code
1162 parse_view_column_type(struct view_column *column, const char **arg)
1164 enum view_column_type type;
1165 size_t typelen = strcspn(*arg, ":,");
1167 for (type = 0; type < view_column_type_map->size; type++)
1168 if (enum_equals(view_column_type_map->entries[type], *arg, typelen)) {
1169 *arg += typelen + !!(*arg)[typelen];
1170 column->type = type;
1171 return SUCCESS;
1174 return error("Failed to parse view column type: %.*s", (int) typelen, *arg);
1177 static struct view *
1178 find_view(const char *view_name)
1180 struct view *view;
1181 int i;
1183 foreach_view(view, i)
1184 if (!strncmp(view_name, view->name, strlen(view->name)))
1185 return view;
1187 return NULL;
1190 enum status_code
1191 parse_view_config(const char *view_name, const char *argv[])
1193 enum status_code code = SUCCESS;
1194 size_t size = argv_size(argv);
1195 struct view_column *columns;
1196 struct view_column *column;
1197 struct view *view = find_view(view_name);
1198 int i;
1200 if (!view)
1201 return error("Unknown view: %s", view_name);
1203 columns = calloc(size, sizeof(*columns));
1204 if (!columns)
1205 return ERROR_OUT_OF_MEMORY;
1207 for (i = 0, column = NULL; code == SUCCESS && i < size; i++) {
1208 const char *arg = argv[i];
1209 char buf[SIZEOF_STR] = "";
1210 char *pos, *end;
1211 bool first = TRUE;
1213 if (column)
1214 column->next = &columns[i];
1215 column = &columns[i];
1217 code = parse_view_column_type(column, &arg);
1218 if (code != SUCCESS)
1219 break;
1221 if (!(view->ops->column_bits & (1 << column->type)))
1222 return error("The %s view does not support %s column", view->name,
1223 view_column_name(column->type));
1225 if ((column->type == VIEW_COLUMN_TEXT ||
1226 column->type == VIEW_COLUMN_COMMIT_TITLE) &&
1227 i + 1 < size)
1228 return error("The %s column must always be last",
1229 view_column_name(column->type));
1231 string_ncopy(buf, arg, strlen(arg));
1233 for (pos = buf, end = pos + strlen(pos); code == SUCCESS && pos <= end; first = FALSE) {
1234 const char *name = NULL;
1235 const char *value = NULL;
1237 code = parse_view_column_config(&pos, &name, &value, first);
1238 if (code == SUCCESS)
1239 code = parse_view_column_option(column, name, value);
1242 column->prev_opt = column->opt;
1245 if (code == SUCCESS) {
1246 free(view->columns);
1247 view->columns = columns;
1248 view->sort.current = view->columns;
1249 } else {
1250 free(columns);
1253 return code;
1256 struct view_column *
1257 get_view_column(struct view *view, enum view_column_type type)
1259 struct view_column *column;
1261 for (column = view->columns; column; column = column->next)
1262 if (column->type == type)
1263 return column;
1264 return NULL;
1267 bool
1268 view_column_info_update(struct view *view, struct line *line)
1270 struct view_column_data column_data = {};
1271 struct view_column *column;
1272 bool changed = FALSE;
1274 if (!view->ops->get_column_data(view, line, &column_data))
1275 return FALSE;
1277 for (column = view->columns; column; column = column->next) {
1278 const char *text = view_column_text(view, &column_data, column);
1279 int width = 0;
1281 switch (column->type) {
1282 case VIEW_COLUMN_AUTHOR:
1283 width = column->opt.author.width;
1284 break;
1286 case VIEW_COLUMN_COMMIT_TITLE:
1287 width = column->opt.commit_title.width;
1288 break;
1290 case VIEW_COLUMN_DATE:
1291 width = column->opt.date.width;
1292 break;
1294 case VIEW_COLUMN_FILE_NAME:
1295 width = column->opt.file_name.width;
1296 break;
1298 case VIEW_COLUMN_FILE_SIZE:
1299 width = column->opt.file_size.width;
1300 break;
1302 case VIEW_COLUMN_ID:
1303 width = column->opt.id.width;
1304 if (!width)
1305 width = opt_id_width;
1306 if (!column_data.reflog && !width)
1307 width = 7;
1308 break;
1310 case VIEW_COLUMN_LINE_NUMBER:
1311 if (column_data.line_number)
1312 width = count_digits(*column_data.line_number);
1313 else
1314 width = count_digits(view->lines);
1315 if (width < 3)
1316 width = 3;
1317 break;
1319 case VIEW_COLUMN_MODE:
1320 width = column->opt.mode.width;
1321 break;
1323 case VIEW_COLUMN_REF:
1324 width = column->opt.ref.width;
1325 break;
1327 case VIEW_COLUMN_SECTION:
1328 break;
1330 case VIEW_COLUMN_STATUS:
1331 width = column->opt.status.width;
1332 break;
1334 case VIEW_COLUMN_TEXT:
1335 width = column->opt.text.width;
1336 break;
1339 if (*text && !width)
1340 width = utf8_width(text);
1342 if (width > column->width) {
1343 column->width = width;
1344 changed = TRUE;
1348 if (changed)
1349 view->force_redraw = TRUE;
1350 return changed;
1353 struct line *
1354 find_line_by_type(struct view *view, struct line *line, enum line_type type, int direction)
1356 for (; view_has_line(view, line); line += direction)
1357 if (line->type == type)
1358 return line;
1360 return NULL;
1364 * Line utilities.
1367 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
1369 struct line *
1370 add_line_at(struct view *view, unsigned long pos, const void *data, enum line_type type, size_t data_size, bool custom)
1372 struct line *line;
1373 unsigned long lineno;
1375 if (!realloc_lines(&view->line, view->lines, 1))
1376 return NULL;
1378 if (data_size) {
1379 void *alloc_data = calloc(1, data_size);
1381 if (!alloc_data)
1382 return NULL;
1384 if (data)
1385 memcpy(alloc_data, data, data_size);
1386 data = alloc_data;
1389 if (pos < view->lines) {
1390 view->lines++;
1391 line = view->line + pos;
1392 lineno = line->lineno;
1394 memmove(line + 1, line, (view->lines - pos) * sizeof(*view->line));
1395 while (pos < view->lines) {
1396 view->line[pos].lineno++;
1397 view->line[pos++].dirty = 1;
1399 } else {
1400 line = &view->line[view->lines++];
1401 lineno = view->lines - view->custom_lines;
1404 memset(line, 0, sizeof(*line));
1405 line->type = type;
1406 line->data = (void *) data;
1407 line->dirty = 1;
1409 if (custom)
1410 view->custom_lines++;
1411 else
1412 line->lineno = lineno;
1414 return line;
1417 struct line *
1418 add_line(struct view *view, const void *data, enum line_type type, size_t data_size, bool custom)
1420 return add_line_at(view, view->lines, data, type, data_size, custom);
1423 struct line *
1424 add_line_alloc_(struct view *view, void **ptr, enum line_type type, size_t data_size, bool custom)
1426 struct line *line = add_line(view, NULL, type, data_size, custom);
1428 if (line)
1429 *ptr = line->data;
1430 return line;
1433 struct line *
1434 add_line_nodata(struct view *view, enum line_type type)
1436 return add_line(view, NULL, type, 0, FALSE);
1439 struct line *
1440 add_line_text(struct view *view, const char *text, enum line_type type)
1442 struct line *line = add_line(view, text, type, strlen(text) + 1, FALSE);
1444 if (line && view->ops->column_bits)
1445 view_column_info_update(view, line);
1446 return line;
1449 struct line * PRINTF_LIKE(3, 4)
1450 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
1452 char buf[SIZEOF_STR];
1453 int retval;
1455 FORMAT_BUFFER(buf, sizeof(buf), fmt, retval, FALSE);
1456 return retval >= 0 ? add_line_text(view, buf, type) : NULL;
1460 * Global view state.
1463 /* Included last to not pollute the rest of the file. */
1464 #include "tig/main.h"
1465 #include "tig/diff.h"
1466 #include "tig/log.h"
1467 #include "tig/tree.h"
1468 #include "tig/blob.h"
1469 #include "tig/blame.h"
1470 #include "tig/refs.h"
1471 #include "tig/status.h"
1472 #include "tig/stage.h"
1473 #include "tig/stash.h"
1474 #include "tig/grep.h"
1475 #include "tig/pager.h"
1476 #include "tig/help.h"
1478 static struct view *views[] = {
1479 #define VIEW_DATA(id, name) &name##_view
1480 VIEW_INFO(VIEW_DATA)
1483 struct view *
1484 get_view(int i)
1486 return 0 <= i && i < ARRAY_SIZE(views) ? views[i] : NULL;
1489 /* vim: set ts=8 sw=8 noexpandtab: */