Refactor loading of views that are not associated with any view state
[tig.git] / src / view.c
blob2e94a0d3c1a3b6519a742573929360aed49432c6
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/options.h"
18 #include "tig/view.h"
19 #include "tig/draw.h"
20 #include "tig/display.h"
23 * Navigation
26 bool
27 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
29 if (lineno >= view->lines)
30 lineno = view->lines > 0 ? view->lines - 1 : 0;
32 if (offset > lineno || offset + view->height <= lineno) {
33 unsigned long half = view->height / 2;
35 if (lineno > half)
36 offset = lineno - half;
37 else
38 offset = 0;
41 if (offset != view->pos.offset || lineno != view->pos.lineno) {
42 view->pos.offset = offset;
43 view->pos.lineno = lineno;
44 return TRUE;
47 return FALSE;
50 /* Scrolling backend */
51 void
52 do_scroll_view(struct view *view, int lines)
54 bool redraw_current_line = FALSE;
56 /* The rendering expects the new offset. */
57 view->pos.offset += lines;
59 assert(0 <= view->pos.offset && view->pos.offset < view->lines);
60 assert(lines);
62 /* Move current line into the view. */
63 if (view->pos.lineno < view->pos.offset) {
64 view->pos.lineno = view->pos.offset;
65 redraw_current_line = TRUE;
66 } else if (view->pos.lineno >= view->pos.offset + view->height) {
67 view->pos.lineno = view->pos.offset + view->height - 1;
68 redraw_current_line = TRUE;
71 assert(view->pos.offset <= view->pos.lineno && view->pos.lineno < view->lines);
73 /* Redraw the whole screen if scrolling is pointless. */
74 if (view->height < ABS(lines)) {
75 redraw_view(view);
77 } else {
78 int line = lines > 0 ? view->height - lines : 0;
79 int end = line + ABS(lines);
81 scrollok(view->win, TRUE);
82 wscrl(view->win, lines);
83 scrollok(view->win, FALSE);
85 while (line < end && draw_view_line(view, line))
86 line++;
88 if (redraw_current_line)
89 draw_view_line(view, view->pos.lineno - view->pos.offset);
90 wnoutrefresh(view->win);
93 view->has_scrolled = TRUE;
94 report_clear();
97 /* Scroll frontend */
98 void
99 scroll_view(struct view *view, enum request request)
101 int lines = 1;
103 assert(view_is_displayed(view));
105 if (request == REQ_SCROLL_WHEEL_DOWN || request == REQ_SCROLL_WHEEL_UP)
106 lines = opt_mouse_scroll;
108 switch (request) {
109 case REQ_SCROLL_FIRST_COL:
110 view->pos.col = 0;
111 redraw_view_from(view, 0);
112 report_clear();
113 return;
114 case REQ_SCROLL_LEFT:
115 if (view->pos.col == 0) {
116 report("Cannot scroll beyond the first column");
117 return;
119 if (view->pos.col <= apply_step(opt_horizontal_scroll, view->width))
120 view->pos.col = 0;
121 else
122 view->pos.col -= apply_step(opt_horizontal_scroll, view->width);
123 redraw_view_from(view, 0);
124 report_clear();
125 return;
126 case REQ_SCROLL_RIGHT:
127 view->pos.col += apply_step(opt_horizontal_scroll, view->width);
128 redraw_view(view);
129 report_clear();
130 return;
131 case REQ_SCROLL_PAGE_DOWN:
132 lines = view->height;
133 case REQ_SCROLL_WHEEL_DOWN:
134 case REQ_SCROLL_LINE_DOWN:
135 if (view->pos.offset + lines > view->lines)
136 lines = view->lines - view->pos.offset;
138 if (lines == 0 || view->pos.offset + view->height >= view->lines) {
139 report("Cannot scroll beyond the last line");
140 return;
142 break;
144 case REQ_SCROLL_PAGE_UP:
145 lines = view->height;
146 case REQ_SCROLL_LINE_UP:
147 case REQ_SCROLL_WHEEL_UP:
148 if (lines > view->pos.offset)
149 lines = view->pos.offset;
151 if (lines == 0) {
152 report("Cannot scroll beyond the first line");
153 return;
156 lines = -lines;
157 break;
159 default:
160 die("request %d not handled in switch", request);
163 do_scroll_view(view, lines);
166 /* Cursor moving */
167 void
168 move_view(struct view *view, enum request request)
170 int scroll_steps = 0;
171 int steps;
173 switch (request) {
174 case REQ_MOVE_FIRST_LINE:
175 steps = -view->pos.lineno;
176 break;
178 case REQ_MOVE_LAST_LINE:
179 steps = view->lines - view->pos.lineno - 1;
180 break;
182 case REQ_MOVE_PAGE_UP:
183 steps = view->height > view->pos.lineno
184 ? -view->pos.lineno : -view->height;
185 break;
187 case REQ_MOVE_PAGE_DOWN:
188 steps = view->pos.lineno + view->height >= view->lines
189 ? view->lines - view->pos.lineno - 1 : view->height;
190 break;
192 case REQ_MOVE_UP:
193 case REQ_PREVIOUS:
194 steps = -1;
195 break;
197 case REQ_MOVE_DOWN:
198 case REQ_NEXT:
199 steps = 1;
200 break;
202 default:
203 die("request %d not handled in switch", request);
206 if (steps <= 0 && view->pos.lineno == 0) {
207 report("Cannot move beyond the first line");
208 return;
210 } else if (steps >= 0 && view->pos.lineno + 1 >= view->lines) {
211 report("Cannot move beyond the last line");
212 return;
215 /* Move the current line */
216 view->pos.lineno += steps;
217 assert(0 <= view->pos.lineno && view->pos.lineno < view->lines);
219 /* Check whether the view needs to be scrolled */
220 if (view->pos.lineno < view->pos.offset ||
221 view->pos.lineno >= view->pos.offset + view->height) {
222 scroll_steps = steps;
223 if (steps < 0 && -steps > view->pos.offset) {
224 scroll_steps = -view->pos.offset;
226 } else if (steps > 0) {
227 if (view->pos.lineno == view->lines - 1 &&
228 view->lines > view->height) {
229 scroll_steps = view->lines - view->pos.offset - 1;
230 if (scroll_steps >= view->height)
231 scroll_steps -= view->height - 1;
236 if (!view_is_displayed(view)) {
237 view->pos.offset += scroll_steps;
238 assert(0 <= view->pos.offset && view->pos.offset < view->lines);
239 view->ops->select(view, &view->line[view->pos.lineno]);
240 return;
243 /* Repaint the old "current" line if we be scrolling */
244 if (ABS(steps) < view->height)
245 draw_view_line(view, view->pos.lineno - steps - view->pos.offset);
247 if (scroll_steps) {
248 do_scroll_view(view, scroll_steps);
249 return;
252 /* Draw the current line */
253 draw_view_line(view, view->pos.lineno - view->pos.offset);
255 wnoutrefresh(view->win);
256 report_clear();
260 * Searching
263 bool
264 grep_text(struct view *view, const char *text[])
266 regmatch_t pmatch;
267 size_t i;
269 for (i = 0; text[i]; i++)
270 if (*text[i] && !regexec(view->regex, text[i], 1, &pmatch, 0))
271 return TRUE;
272 return FALSE;
275 void
276 select_view_line(struct view *view, unsigned long lineno)
278 struct position old = view->pos;
280 if (goto_view_line(view, view->pos.offset, lineno)) {
281 if (view_is_displayed(view)) {
282 if (old.offset != view->pos.offset) {
283 redraw_view(view);
284 } else {
285 draw_view_line(view, old.lineno - view->pos.offset);
286 draw_view_line(view, view->pos.lineno - view->pos.offset);
287 wnoutrefresh(view->win);
289 } else {
290 view->ops->select(view, &view->line[view->pos.lineno]);
295 void
296 find_next(struct view *view, enum request request)
298 unsigned long lineno = view->pos.lineno;
299 int direction;
301 if (!*view->grep) {
302 if (!*view->env->search)
303 report("No previous search");
304 else
305 search_view(view, request);
306 return;
309 switch (request) {
310 case REQ_SEARCH:
311 case REQ_FIND_NEXT:
312 direction = 1;
313 break;
315 case REQ_SEARCH_BACK:
316 case REQ_FIND_PREV:
317 direction = -1;
318 break;
320 default:
321 return;
324 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
325 lineno += direction;
327 /* Note, lineno is unsigned long so will wrap around in which case it
328 * will become bigger than view->lines. */
329 for (; lineno < view->lines; lineno += direction) {
330 if (view->ops->grep(view, &view->line[lineno])) {
331 select_view_line(view, lineno);
332 report("Line %ld matches '%s'", lineno + 1, view->grep);
333 return;
337 report("No match found for '%s'", view->grep);
340 void
341 search_view(struct view *view, enum request request)
343 int regex_err;
344 int regex_flags = opt_ignore_case ? REG_ICASE : 0;
346 if (view->regex) {
347 regfree(view->regex);
348 *view->grep = 0;
349 } else {
350 view->regex = calloc(1, sizeof(*view->regex));
351 if (!view->regex)
352 return;
355 regex_err = regcomp(view->regex, view->env->search, REG_EXTENDED | regex_flags);
356 if (regex_err != 0) {
357 char buf[SIZEOF_STR] = "unknown error";
359 regerror(regex_err, view->regex, buf, sizeof(buf));
360 report("Search failed: %s", buf);
361 return;
364 string_copy(view->grep, view->env->search);
366 find_next(view, request);
370 * View history
373 static bool
374 view_history_is_empty(struct view_history *history)
376 return !history->stack;
379 struct view_state *
380 push_view_history_state(struct view_history *history, struct position *position, void *data)
382 struct view_state *state = history->stack;
384 if (state && data && history->state_alloc &&
385 !memcmp(state->data, data, history->state_alloc))
386 return NULL;
388 state = calloc(1, sizeof(*state) + history->state_alloc);
389 if (!state)
390 return NULL;
392 state->prev = history->stack;
393 history->stack = state;
394 clear_position(&history->position);
395 state->position = *position;
396 state->data = &state[1];
397 if (data && history->state_alloc)
398 memcpy(state->data, data, history->state_alloc);
399 return state;
402 bool
403 pop_view_history_state(struct view_history *history, struct position *position, void *data)
405 struct view_state *state = history->stack;
407 if (view_history_is_empty(history))
408 return FALSE;
410 history->position = state->position;
411 history->stack = state->prev;
413 if (data && history->state_alloc)
414 memcpy(data, state->data, history->state_alloc);
415 if (position)
416 *position = state->position;
418 free(state);
419 return TRUE;
422 void
423 reset_view_history(struct view_history *history)
425 while (pop_view_history_state(history, NULL, NULL))
430 * Incremental updating
433 void
434 reset_view(struct view *view)
436 int i;
438 for (i = 0; i < view->lines; i++)
439 free(view->line[i].data);
440 free(view->line);
442 view->prev_pos = view->pos;
443 clear_position(&view->pos);
445 view->line = NULL;
446 view->lines = 0;
447 view->vid[0] = 0;
448 view->custom_lines = 0;
449 view->update_secs = 0;
452 static bool
453 restore_view_position(struct view *view)
455 /* A view without a previous view is the first view */
456 if (!view->prev && view->env->lineno && view->env->lineno <= view->lines) {
457 select_view_line(view, view->env->lineno);
458 view->env->lineno = 0;
461 /* Ensure that the view position is in a valid state. */
462 if (!check_position(&view->prev_pos) ||
463 (view->pipe && view->lines <= view->prev_pos.lineno))
464 return goto_view_line(view, view->pos.offset, view->pos.lineno);
466 /* Changing the view position cancels the restoring. */
467 /* FIXME: Changing back to the first line is not detected. */
468 if (check_position(&view->pos)) {
469 clear_position(&view->prev_pos);
470 return FALSE;
473 if (goto_view_line(view, view->prev_pos.offset, view->prev_pos.lineno) &&
474 view_is_displayed(view))
475 werase(view->win);
477 view->pos.col = view->prev_pos.col;
478 clear_position(&view->prev_pos);
480 return TRUE;
483 void
484 end_update(struct view *view, bool force)
486 if (!view->pipe)
487 return;
488 while (!view->ops->read(view, NULL))
489 if (!force)
490 return;
491 if (force)
492 io_kill(view->pipe);
493 io_done(view->pipe);
494 view->pipe = NULL;
497 static void
498 setup_update(struct view *view, const char *vid)
500 reset_view(view);
501 /* XXX: Do not use string_copy_rev(), it copies until first space. */
502 string_ncopy(view->vid, vid, strlen(vid));
503 view->pipe = &view->io;
504 view->start_time = time(NULL);
507 static bool
508 view_no_refresh(struct view *view, enum open_flags flags)
510 bool reload = !!(flags & OPEN_ALWAYS_LOAD) || !view->lines;
512 return (!reload && !strcmp(view->vid, view->ops->id)) ||
513 ((flags & OPEN_REFRESH) && view->unrefreshable);
516 bool
517 begin_update(struct view *view, const char *dir, const char **argv, enum open_flags flags)
519 bool extra = !!(flags & (OPEN_EXTRA));
520 bool refresh = flags & (OPEN_REFRESH | OPEN_PREPARED | OPEN_STDIN);
521 bool forward_stdin = flags & OPEN_FORWARD_STDIN;
522 enum io_type io_type = forward_stdin ? IO_RD_STDIN : IO_RD;
524 if (view_no_refresh(view, flags))
525 return TRUE;
527 if (view->pipe) {
528 if (extra)
529 io_done(view->pipe);
530 else
531 end_update(view, TRUE);
534 view->unrefreshable = open_in_pager_mode(flags);
536 if (!refresh && argv) {
537 bool file_filter = !view_has_flags(view, VIEW_FILE_FILTER) || opt_file_filter;
539 view->dir = dir;
540 if (!argv_format(view->env, &view->argv, argv, !view->prev, file_filter)) {
541 report("Failed to format %s arguments", view->name);
542 return FALSE;
545 /* Put the current view ref value to the view title ref
546 * member. This is needed by the blob view. Most other
547 * views sets it automatically after loading because the
548 * first line is a commit line. */
549 string_copy_rev(view->ref, view->ops->id);
552 if (view->argv && view->argv[0] &&
553 !io_run(&view->io, io_type, view->dir, opt_env, view->argv)) {
554 report("Failed to open %s view", view->name);
555 return FALSE;
558 if (open_from_stdin(flags)) {
559 if (!io_open(&view->io, "%s", ""))
560 die("Failed to open stdin");
563 if (!extra)
564 setup_update(view, view->ops->id);
566 return TRUE;
569 bool
570 update_view(struct view *view)
572 char *line;
573 /* Clear the view and redraw everything since the tree sorting
574 * might have rearranged things. */
575 bool redraw = view->lines == 0;
576 bool can_read = TRUE;
577 struct encoding *encoding = view->encoding ? view->encoding : default_encoding;
579 if (!view->pipe)
580 return TRUE;
582 if (!io_can_read(view->pipe, FALSE)) {
583 if (view->lines == 0 && view_is_displayed(view)) {
584 time_t secs = time(NULL) - view->start_time;
586 if (secs > 1 && secs > view->update_secs) {
587 if (view->update_secs == 0)
588 redraw_view(view);
589 update_view_title(view);
590 view->update_secs = secs;
593 return TRUE;
596 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
597 if (encoding) {
598 line = encoding_convert(encoding, line);
601 if (!view->ops->read(view, line)) {
602 report("Allocation failure");
603 end_update(view, TRUE);
604 return FALSE;
608 if (!view_has_flags(view, VIEW_CUSTOM_DIGITS)) {
609 int digits = count_digits(view->lines);
611 /* Keep the displayed view in sync with line number scaling. */
612 if (digits != view->digits) {
613 view->digits = digits;
614 if (opt_show_line_numbers || view_has_flags(view, VIEW_ALWAYS_LINENO))
615 redraw = TRUE;
619 if (io_error(view->pipe)) {
620 report("Failed to read: %s", io_strerror(view->pipe));
621 end_update(view, TRUE);
623 } else if (io_eof(view->pipe)) {
624 end_update(view, FALSE);
627 if (restore_view_position(view))
628 redraw = TRUE;
630 if (!view_is_displayed(view))
631 return TRUE;
633 if (redraw || view->force_redraw)
634 redraw_view_from(view, 0);
635 else
636 redraw_view_dirty(view);
637 view->force_redraw = FALSE;
639 /* Update the title _after_ the redraw so that if the redraw picks up a
640 * commit reference in view->ref it'll be available here. */
641 update_view_title(view);
642 return TRUE;
645 void
646 update_view_title(struct view *view)
648 WINDOW *window = view->title;
649 struct line *line = &view->line[view->pos.lineno];
650 unsigned int view_lines, lines;
652 assert(view_is_displayed(view));
654 if (view == display[current_view])
655 wbkgdset(window, get_view_attr(view, LINE_TITLE_FOCUS));
656 else
657 wbkgdset(window, get_view_attr(view, LINE_TITLE_BLUR));
659 werase(window);
660 mvwprintw(window, 0, 0, "[%s]", view->name);
662 if (*view->ref) {
663 wprintw(window, " %s", view->ref);
666 if (!view_has_flags(view, VIEW_CUSTOM_STATUS) && view_has_line(view, line) &&
667 line->lineno) {
668 wprintw(window, " - %s %d of %zd",
669 view->ops->type,
670 line->lineno,
671 view->lines - view->custom_lines);
674 if (view->pipe) {
675 time_t secs = time(NULL) - view->start_time;
677 /* Three git seconds are a long time ... */
678 if (secs > 2)
679 wprintw(window, " loading %lds", secs);
682 view_lines = view->pos.offset + view->height;
683 lines = view->lines ? MIN(view_lines, view->lines) * 100 / view->lines : 0;
684 mvwprintw(window, 0, view->width - count_digits(lines) - 1, "%d%%", lines);
686 wnoutrefresh(window);
690 * View opening
693 void
694 split_view(struct view *prev, struct view *view)
696 display[1] = view;
697 current_view = opt_focus_child ? 1 : 0;
698 view->parent = prev;
699 resize_display();
701 if (prev->pos.lineno - prev->pos.offset >= prev->height) {
702 /* Take the title line into account. */
703 int lines = prev->pos.lineno - prev->pos.offset - prev->height + 1;
705 /* Scroll the view that was split if the current line is
706 * outside the new limited view. */
707 do_scroll_view(prev, lines);
710 if (view != prev && view_is_displayed(prev)) {
711 /* "Blur" the previous view. */
712 update_view_title(prev);
716 void
717 maximize_view(struct view *view, bool redraw)
719 memset(display, 0, sizeof(display));
720 current_view = 0;
721 display[current_view] = view;
722 resize_display();
723 if (redraw) {
724 redraw_display(FALSE);
725 report_clear();
729 void
730 load_view(struct view *view, struct view *prev, enum open_flags flags)
732 bool refresh = !view_no_refresh(view, flags);
734 /* When prev == view it means this is the first loaded view. */
735 if (prev && view != prev) {
736 view->prev = prev;
739 if (refresh) {
740 if (view->pipe)
741 end_update(view, TRUE);
742 if (view->ops->private_size) {
743 if (!view->private) {
744 view->private = calloc(1, view->ops->private_size);
745 } else {
746 if (view->ops->done)
747 view->ops->done(view);
748 memset(view->private, 0, view->ops->private_size);
752 if (view->ops->columns_size) {
753 if (!view->columns_info)
754 view->columns_info = calloc(1, sizeof(*view->columns_info)
755 * view->ops->columns_size);
756 else
757 memset(view->columns_info, 0, sizeof(*view->columns_info)
758 * view->ops->columns_size);
759 view_columns_info_init(view);
762 if (!view->ops->open(view, flags))
763 return;
766 if (prev) {
767 bool split = !!(flags & OPEN_SPLIT);
769 if (split) {
770 split_view(prev, view);
771 } else {
772 maximize_view(view, FALSE);
776 restore_view_position(view);
778 if (view->pipe && view->lines == 0) {
779 /* Clear the old view and let the incremental updating refill
780 * the screen. */
781 werase(view->win);
782 if (!(flags & (OPEN_RELOAD | OPEN_REFRESH)))
783 clear_position(&view->prev_pos);
784 report_clear();
785 } else if (view_is_displayed(view)) {
786 redraw_view(view);
787 report_clear();
791 #define refresh_view(view) load_view(view, NULL, OPEN_REFRESH)
792 #define reload_view(view) load_view(view, NULL, OPEN_RELOAD)
794 void
795 open_view(struct view *prev, struct view *view, enum open_flags flags)
797 bool reload = !!(flags & (OPEN_RELOAD | OPEN_PREPARED));
798 int nviews = displayed_views();
800 assert(flags ^ OPEN_REFRESH);
802 if (view == prev && nviews == 1 && !reload) {
803 report("Already in %s view", view->name);
804 return;
807 if (!view_has_flags(view, VIEW_NO_GIT_DIR) && !repo.git_dir[0]) {
808 report("The %s view is disabled in pager mode", view->name);
809 return;
812 if (!view->keymap)
813 view->keymap = get_keymap(view->name, strlen(view->name));
814 load_view(view, prev ? prev : view, flags);
817 void
818 open_argv(struct view *prev, struct view *view, const char *argv[], const char *dir, enum open_flags flags)
820 if (view->pipe)
821 end_update(view, TRUE);
822 view->dir = dir;
824 if (!argv_copy(&view->argv, argv)) {
825 report("Failed to open %s view: %s", view->name, io_strerror(&view->io));
826 } else {
827 open_view(prev, view, flags | OPEN_PREPARED);
832 * Various utilities.
835 static struct view *sorting_view;
837 #define sort_order_reverse(state, result) \
838 ((state)->reverse ? -(result) : (result))
840 #define sort_order(state, cmp, o1, o2) \
841 sort_order_reverse(state, (!(o1) || !(o2)) ? !!(o2) - !!(o1) : cmp(o1, o2))
843 #define number_compare(size1, size2) (*(size2) - *(size1))
844 #define ref_compare(ref1, ref2) strcmp((ref1)->name, (ref2)->name)
846 static int
847 sort_view_compare(const void *l1, const void *l2)
849 const struct line *line1 = l1;
850 const struct line *line2 = l2;
851 struct view_columns columns1 = {};
852 struct view_columns columns2 = {};
853 struct sort_state *sort = &sorting_view->sort;
855 if (!sorting_view->ops->get_columns(sorting_view, line1, &columns1))
856 return -1;
857 else if (!sorting_view->ops->get_columns(sorting_view, line2, &columns2))
858 return 1;
860 switch (get_sort_field(sorting_view)) {
861 case VIEW_COLUMN_AUTHOR:
862 return sort_order(sort, ident_compare, columns1.author, columns2.author);
864 case VIEW_COLUMN_DATE:
865 return sort_order(sort, timecmp, columns1.date, columns2.date);
867 case VIEW_COLUMN_ID:
868 return sort_order(sort, strcmp, columns1.id, columns2.id);
870 case VIEW_COLUMN_FILE_NAME:
871 if (columns1.mode != columns2.mode)
872 return sort_order_reverse(sort, S_ISDIR(*columns1.mode) ? -1 : 1);
873 return sort_order(sort, strcmp, columns1.file_name, columns2.file_name);
875 case VIEW_COLUMN_FILE_SIZE:
876 return sort_order(sort, number_compare, columns1.file_size, columns2.file_size);
878 case VIEW_COLUMN_LINE_NUMBER:
879 return sort_order_reverse(sort, line2->lineno - line1->lineno);
881 case VIEW_COLUMN_MODE:
882 return sort_order(sort, number_compare, columns1.mode, columns2.mode);
884 case VIEW_COLUMN_REF:
885 return sort_order(sort, ref_compare, columns1.ref, columns2.ref);
887 case VIEW_COLUMN_COMMIT_TITLE:
888 return sort_order(sort, strcmp, columns1.commit_title, columns2.commit_title);
890 case VIEW_COLUMN_TEXT:
891 return sort_order(sort, strcmp, columns1.text, columns2.text);
893 case VIEW_COLUMN_REFS:
894 case VIEW_COLUMN_GRAPH:
895 die("Unsupported search: %d", get_sort_field(sorting_view));
898 return 0;
901 void
902 sort_view(struct view *view, bool change_field)
904 struct sort_state *state = &view->sort;
906 if (change_field) {
907 while (TRUE) {
908 state->current = (state->current + 1) % view->ops->columns_size;
909 if (get_sort_field(view) == VIEW_COLUMN_ID && !opt_show_id)
910 continue;
911 if (get_sort_field(view) == VIEW_COLUMN_REFS)
912 continue;
913 if (get_sort_field(view) == VIEW_COLUMN_GRAPH)
914 continue;
915 break;
917 } else {
918 state->reverse = !state->reverse;
921 sorting_view = view;
922 qsort(view->line, view->lines, sizeof(*view->line), sort_view_compare);
925 static bool
926 grep_refs(struct view *view, const struct ref_list *list)
928 regmatch_t pmatch;
929 size_t i;
931 if (!opt_show_refs || !list)
932 return FALSE;
934 for (i = 0; i < list->size; i++) {
935 if (!regexec(view->regex, list->refs[i]->name, 1, &pmatch, 0))
936 return TRUE;
939 return FALSE;
942 bool
943 view_columns_grep(struct view *view, struct line *line)
945 struct view_columns columns = {};
946 bool has_columns = view->ops->get_columns(view, line, &columns);
947 const char *text[] = {
948 has_columns && columns.author ? mkauthor(columns.author, opt_author_width, opt_show_author) : "",
949 has_columns && columns.date ? mkdate(columns.date, opt_show_date) : "",
950 has_columns && columns.file_name ? columns.file_name : "",
951 has_columns && columns.file_size ? mkfilesize(*columns.file_size, opt_show_file_size) : "",
952 has_columns && columns.id && opt_show_id ? columns.id : "",
953 has_columns && columns.mode ? mkmode(*columns.mode) : "",
954 has_columns && columns.commit_title ? columns.commit_title : "",
955 has_columns && columns.ref ? columns.ref->name : "",
956 has_columns && columns.text ? columns.text : "",
957 NULL
960 if (has_columns && grep_refs(view, columns.refs))
961 return TRUE;
963 return grep_text(view, text);
966 bool
967 view_columns_info_changed(struct view *view, bool update)
969 bool changed = FALSE;
970 int i;
972 for (i = 0; i < view->ops->columns_size; i++) {
973 enum view_column column = view->ops->columns[i];
974 struct column_info *info = &view->columns_info[i];
975 unsigned long option = 0;
977 switch (column) {
978 case VIEW_COLUMN_AUTHOR:
979 option = opt_show_author;
980 break;
982 case VIEW_COLUMN_DATE:
983 option = opt_show_date;
984 break;
986 case VIEW_COLUMN_FILE_SIZE:
987 option = opt_show_file_size;
988 break;
990 case VIEW_COLUMN_GRAPH:
991 option = opt_show_rev_graph;
992 break;
994 case VIEW_COLUMN_LINE_NUMBER:
995 option = opt_show_line_numbers;
996 break;
998 case VIEW_COLUMN_REFS:
999 option = opt_show_refs;
1000 break;
1002 case VIEW_COLUMN_ID:
1003 option = opt_show_id;
1004 break;
1006 case VIEW_COLUMN_COMMIT_TITLE:
1007 case VIEW_COLUMN_FILE_NAME:
1008 case VIEW_COLUMN_MODE:
1009 case VIEW_COLUMN_REF:
1010 case VIEW_COLUMN_TEXT:
1011 break;
1014 if (option != info->option) {
1015 if (!update)
1016 return TRUE;
1017 info->option = option;
1018 changed = TRUE;
1022 return changed;
1025 void
1026 view_columns_info_init(struct view *view)
1028 int i;
1030 view_columns_info_changed(view, TRUE);
1031 for (i = 0; i < view->ops->columns_size; i++)
1032 view->columns_info[i].width = 0;
1035 bool
1036 view_columns_info_update(struct view *view, struct line *line)
1038 struct view_columns columns = {};
1039 bool changed = FALSE;
1040 int i;
1042 if (!view->ops->get_columns(view, line, &columns))
1043 return FALSE;
1045 for (i = 0; i < view->ops->columns_size; i++) {
1046 enum view_column column = view->ops->columns[i];
1047 const char *text = NULL;
1049 switch (column) {
1050 case VIEW_COLUMN_AUTHOR:
1051 if (columns.author)
1052 text = mkauthor(columns.author, opt_author_width, opt_show_author);
1053 break;
1055 case VIEW_COLUMN_DATE:
1056 if (columns.date)
1057 text = mkdate(columns.date, opt_show_date);
1058 break;
1060 case VIEW_COLUMN_REF:
1061 if (columns.ref)
1062 text = columns.ref->name;
1063 break;
1065 case VIEW_COLUMN_FILE_NAME:
1066 if (columns.file_name)
1067 text = columns.file_name;
1068 break;
1070 case VIEW_COLUMN_FILE_SIZE:
1071 if (columns.file_size)
1072 text = mkfilesize(*columns.file_size, opt_show_file_size);
1073 break;
1075 case VIEW_COLUMN_ID:
1076 if (columns.id && !iscommit(columns.id))
1077 text = columns.id;
1078 break;
1080 case VIEW_COLUMN_COMMIT_TITLE:
1081 case VIEW_COLUMN_GRAPH:
1082 case VIEW_COLUMN_LINE_NUMBER:
1083 case VIEW_COLUMN_MODE:
1084 case VIEW_COLUMN_REFS:
1085 case VIEW_COLUMN_TEXT:
1086 break;
1089 if (text) {
1090 int width = utf8_width(text);
1092 if (width > view->columns_info[i].width) {
1093 view->columns_info[i].width = width;
1094 changed = TRUE;
1099 if (changed)
1100 view->force_redraw = TRUE;
1101 return changed;
1104 bool
1105 view_columns_draw(struct view *view, struct line *line, unsigned int lineno)
1107 struct view_columns columns = {};
1108 int i;
1110 if (!view->ops->get_columns(view, line, &columns))
1111 return TRUE;
1113 for (i = 0; i < view->ops->columns_size; i++) {
1114 enum view_column column = view->ops->columns[i];
1115 int width = view->columns_info[i].width;
1117 switch (column) {
1118 case VIEW_COLUMN_DATE:
1119 if (draw_date(view, columns.date))
1120 return TRUE;
1121 continue;
1123 case VIEW_COLUMN_AUTHOR:
1124 if (draw_author(view, columns.author, opt_author_width ? opt_author_width : width))
1125 return TRUE;
1126 continue;
1128 case VIEW_COLUMN_REF:
1130 const struct ref *ref = columns.ref;
1131 enum line_type type = !ref || !ref->valid ? LINE_DEFAULT : get_line_type_from_ref(ref);
1132 const char *name = ref ? ref->name : NULL;
1134 if (draw_field(view, type, name, width, ALIGN_LEFT, FALSE))
1135 return TRUE;
1136 continue;
1139 case VIEW_COLUMN_REFS:
1140 if (draw_refs(view, columns.refs))
1141 return TRUE;
1142 continue;
1144 case VIEW_COLUMN_GRAPH:
1145 if (columns.graph && draw_graph(view, columns.graph))
1146 return TRUE;
1147 continue;
1149 case VIEW_COLUMN_ID:
1150 if (!width && draw_id(view, columns.id))
1151 return TRUE;
1152 else if (opt_show_id && draw_id_custom(view, LINE_ID, columns.id, width))
1153 return TRUE;
1154 continue;
1156 case VIEW_COLUMN_LINE_NUMBER:
1157 if (draw_lineno(view, lineno))
1158 return TRUE;
1159 continue;
1161 case VIEW_COLUMN_MODE:
1162 if (draw_mode(view, columns.mode ? *columns.mode : 0))
1163 return TRUE;
1164 continue;
1166 case VIEW_COLUMN_FILE_SIZE:
1167 if (draw_file_size(view, columns.file_size ? *columns.file_size : 0, width, !columns.mode || S_ISDIR(*columns.mode)))
1168 return TRUE;
1169 continue;
1171 case VIEW_COLUMN_COMMIT_TITLE:
1172 if (draw_commit_title(view, columns.commit_title, 0))
1173 return TRUE;
1174 continue;
1176 case VIEW_COLUMN_FILE_NAME:
1177 if (draw_filename(view, columns.file_name, TRUE,
1178 opt_show_filename_width ? opt_show_filename_width : width))
1179 return TRUE;
1181 continue;
1183 case VIEW_COLUMN_TEXT:
1184 if (draw_text(view, line->type, columns.text))
1185 return TRUE;
1186 continue;
1190 return TRUE;
1193 struct line *
1194 find_line_by_type(struct view *view, struct line *line, enum line_type type, int direction)
1196 for (; view_has_line(view, line); line += direction)
1197 if (line->type == type)
1198 return line;
1200 return NULL;
1204 * Line utilities.
1207 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
1209 struct line *
1210 add_line_at(struct view *view, unsigned long pos, const void *data, enum line_type type, size_t data_size, bool custom)
1212 struct line *line;
1213 unsigned long lineno;
1215 if (!realloc_lines(&view->line, view->lines, 1))
1216 return NULL;
1218 if (data_size) {
1219 void *alloc_data = calloc(1, data_size);
1221 if (!alloc_data)
1222 return NULL;
1224 if (data)
1225 memcpy(alloc_data, data, data_size);
1226 data = alloc_data;
1229 if (pos < view->lines) {
1230 view->lines++;
1231 line = view->line + pos;
1232 lineno = line->lineno;
1234 memmove(line + 1, line, (view->lines - pos) * sizeof(*view->line));
1235 while (pos < view->lines) {
1236 view->line[pos].lineno++;
1237 view->line[pos++].dirty = 1;
1239 } else {
1240 line = &view->line[view->lines++];
1241 lineno = view->lines - view->custom_lines;
1244 memset(line, 0, sizeof(*line));
1245 line->type = type;
1246 line->data = (void *) data;
1247 line->dirty = 1;
1249 if (custom)
1250 view->custom_lines++;
1251 else
1252 line->lineno = lineno;
1254 return line;
1257 struct line *
1258 add_line(struct view *view, const void *data, enum line_type type, size_t data_size, bool custom)
1260 return add_line_at(view, view->lines, data, type, data_size, custom);
1263 struct line *
1264 add_line_alloc_(struct view *view, void **ptr, enum line_type type, size_t data_size, bool custom)
1266 struct line *line = add_line(view, NULL, type, data_size, custom);
1268 if (line)
1269 *ptr = line->data;
1270 return line;
1273 struct line *
1274 add_line_nodata(struct view *view, enum line_type type)
1276 return add_line(view, NULL, type, 0, FALSE);
1279 struct line *
1280 add_line_text(struct view *view, const char *text, enum line_type type)
1282 return add_line(view, text, type, strlen(text) + 1, FALSE);
1285 struct line * PRINTF_LIKE(3, 4)
1286 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
1288 char buf[SIZEOF_STR];
1289 int retval;
1291 FORMAT_BUFFER(buf, sizeof(buf), fmt, retval, FALSE);
1292 return retval >= 0 ? add_line_text(view, buf, type) : NULL;
1296 * Global view state.
1299 /* Included last to not pollute the rest of the file. */
1300 #include "tig/main.h"
1301 #include "tig/diff.h"
1302 #include "tig/log.h"
1303 #include "tig/tree.h"
1304 #include "tig/blob.h"
1305 #include "tig/blame.h"
1306 #include "tig/branch.h"
1307 #include "tig/status.h"
1308 #include "tig/stage.h"
1309 #include "tig/stash.h"
1310 #include "tig/grep.h"
1311 #include "tig/pager.h"
1312 #include "tig/help.h"
1314 static struct view *views[] = {
1315 #define VIEW_DATA(id, name) &name##_view
1316 VIEW_INFO(VIEW_DATA)
1319 struct view *
1320 get_view(int i)
1322 return 0 <= i && i < ARRAY_SIZE(views) ? views[i] : NULL;
1325 /* vim: set ts=8 sw=8 noexpandtab: */