Use column option when checking whether to sort by ID
[tig.git] / src / view.c
blob5ca4a95c42d51f2b0d81901fbd303c474d9b95cc
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 DEFINE_ALLOCATOR(realloc_unsigned_ints, unsigned int, 32)
265 bool
266 grep_text(struct view *view, const char *text[])
268 regmatch_t pmatch;
269 size_t i;
271 for (i = 0; text[i]; i++)
272 if (*text[i] && !regexec(view->regex, text[i], 1, &pmatch, 0))
273 return TRUE;
274 return FALSE;
277 void
278 select_view_line(struct view *view, unsigned long lineno)
280 struct position old = view->pos;
282 if (goto_view_line(view, view->pos.offset, lineno)) {
283 if (view_is_displayed(view)) {
284 if (old.offset != view->pos.offset) {
285 redraw_view(view);
286 } else {
287 draw_view_line(view, old.lineno - view->pos.offset);
288 draw_view_line(view, view->pos.lineno - view->pos.offset);
289 wnoutrefresh(view->win);
291 } else {
292 view->ops->select(view, &view->line[view->pos.lineno]);
297 static bool
298 find_matches(struct view *view)
300 size_t lineno;
302 /* Note, lineno is unsigned long so will wrap around in which case it
303 * will become bigger than view->lines. */
304 for (lineno = 0; lineno < view->lines; lineno++) {
305 if (!view->ops->grep(view, &view->line[lineno]))
306 continue;
308 if (!realloc_unsigned_ints(&view->matched_line, view->matched_lines, 1))
309 return FALSE;
311 view->matched_line[view->matched_lines++] = lineno;
314 return TRUE;
317 void
318 find_next(struct view *view, enum request request)
320 int direction;
321 size_t i;
323 if (!*view->grep) {
324 if (!*view->env->search)
325 report("No previous search");
326 else
327 search_view(view, request);
328 return;
331 switch (request) {
332 case REQ_SEARCH:
333 case REQ_FIND_NEXT:
334 direction = 1;
335 break;
337 case REQ_SEARCH_BACK:
338 case REQ_FIND_PREV:
339 direction = -1;
340 break;
342 default:
343 return;
346 if (!view->matched_lines && !find_matches(view)) {
347 report("Allocation failure");
348 return;
351 /* Note, `i` is unsigned and will wrap around in which case it
352 * will become bigger than view->matched_lines. */
353 i = direction > 0 ? 0 : view->matched_lines - 1;
354 for (; i < view->matched_lines; i += direction) {
355 size_t lineno = view->matched_line[i];
357 if (direction > 0 && lineno <= view->pos.lineno)
358 continue;
360 if (direction < 0 && lineno >= view->pos.lineno)
361 continue;
363 select_view_line(view, lineno);
364 report("Line %zu matches '%s' (%zu of %zu)", lineno + 1, view->grep, i + 1, view->matched_lines);
365 return;
368 report("No match found for '%s'", view->grep);
371 static void
372 reset_matches(struct view *view)
374 free(view->matched_line);
375 view->matched_line = NULL;
376 view->matched_lines = 0;
379 void
380 search_view(struct view *view, enum request request)
382 int regex_err;
383 int regex_flags = opt_ignore_case ? REG_ICASE : 0;
385 if (view->regex) {
386 regfree(view->regex);
387 *view->grep = 0;
388 } else {
389 view->regex = calloc(1, sizeof(*view->regex));
390 if (!view->regex)
391 return;
394 regex_err = regcomp(view->regex, view->env->search, REG_EXTENDED | regex_flags);
395 if (regex_err != 0) {
396 char buf[SIZEOF_STR] = "unknown error";
398 regerror(regex_err, view->regex, buf, sizeof(buf));
399 report("Search failed: %s", buf);
400 return;
403 string_copy(view->grep, view->env->search);
405 reset_matches(view);
407 find_next(view, request);
411 * View history
414 static bool
415 view_history_is_empty(struct view_history *history)
417 return !history->stack;
420 struct view_state *
421 push_view_history_state(struct view_history *history, struct position *position, void *data)
423 struct view_state *state = history->stack;
425 if (state && data && history->state_alloc &&
426 !memcmp(state->data, data, history->state_alloc))
427 return NULL;
429 state = calloc(1, sizeof(*state) + history->state_alloc);
430 if (!state)
431 return NULL;
433 state->prev = history->stack;
434 history->stack = state;
435 clear_position(&history->position);
436 state->position = *position;
437 state->data = &state[1];
438 if (data && history->state_alloc)
439 memcpy(state->data, data, history->state_alloc);
440 return state;
443 bool
444 pop_view_history_state(struct view_history *history, struct position *position, void *data)
446 struct view_state *state = history->stack;
448 if (view_history_is_empty(history))
449 return FALSE;
451 history->position = state->position;
452 history->stack = state->prev;
454 if (data && history->state_alloc)
455 memcpy(data, state->data, history->state_alloc);
456 if (position)
457 *position = state->position;
459 free(state);
460 return TRUE;
463 void
464 reset_view_history(struct view_history *history)
466 while (pop_view_history_state(history, NULL, NULL))
471 * Incremental updating
474 void
475 reset_view(struct view *view)
477 int i;
479 for (i = 0; i < view->lines; i++)
480 free(view->line[i].data);
481 free(view->line);
483 reset_matches(view);
484 view->prev_pos = view->pos;
485 clear_position(&view->pos);
487 if (view->columns)
488 view_column_reset(view);
490 view->line = NULL;
491 view->lines = 0;
492 view->vid[0] = 0;
493 view->custom_lines = 0;
494 view->update_secs = 0;
497 static bool
498 restore_view_position(struct view *view)
500 /* A view without a previous view is the first view */
501 if (!view->prev && view->env->lineno && view->env->lineno <= view->lines) {
502 select_view_line(view, view->env->lineno);
503 view->env->lineno = 0;
506 /* Ensure that the view position is in a valid state. */
507 if (!check_position(&view->prev_pos) ||
508 (view->pipe && view->lines <= view->prev_pos.lineno))
509 return goto_view_line(view, view->pos.offset, view->pos.lineno);
511 /* Changing the view position cancels the restoring. */
512 /* FIXME: Changing back to the first line is not detected. */
513 if (check_position(&view->pos)) {
514 clear_position(&view->prev_pos);
515 return FALSE;
518 if (goto_view_line(view, view->prev_pos.offset, view->prev_pos.lineno) &&
519 view_is_displayed(view))
520 werase(view->win);
522 view->pos.col = view->prev_pos.col;
523 clear_position(&view->prev_pos);
525 return TRUE;
528 void
529 end_update(struct view *view, bool force)
531 if (!view->pipe)
532 return;
533 while (!view->ops->read(view, NULL))
534 if (!force)
535 return;
536 if (force)
537 io_kill(view->pipe);
538 io_done(view->pipe);
539 view->pipe = NULL;
542 static void
543 setup_update(struct view *view, const char *vid)
545 reset_view(view);
546 /* XXX: Do not use string_copy_rev(), it copies until first space. */
547 string_ncopy(view->vid, vid, strlen(vid));
548 view->pipe = &view->io;
549 view->start_time = time(NULL);
552 static bool
553 view_no_refresh(struct view *view, enum open_flags flags)
555 bool reload = !!(flags & OPEN_ALWAYS_LOAD) || !view->lines;
557 return (!reload && !strcmp(view->vid, view->ops->id)) ||
558 ((flags & OPEN_REFRESH) && view->unrefreshable);
561 bool
562 begin_update(struct view *view, const char *dir, const char **argv, enum open_flags flags)
564 bool extra = !!(flags & (OPEN_EXTRA));
565 bool refresh = flags & (OPEN_REFRESH | OPEN_PREPARED | OPEN_STDIN);
566 int forward_stdin = (flags & OPEN_FORWARD_STDIN) ? IO_RD_FORWARD_STDIN : 0;
567 int with_stderr = (flags & OPEN_WITH_STDERR) ? IO_RD_WITH_STDERR : 0;
568 int io_flags = forward_stdin | with_stderr;
570 if (view_no_refresh(view, flags))
571 return TRUE;
573 if (view->pipe) {
574 if (extra)
575 io_done(view->pipe);
576 else
577 end_update(view, TRUE);
580 view->unrefreshable = open_in_pager_mode(flags);
582 if (!refresh && argv) {
583 bool file_filter = !view_has_flags(view, VIEW_FILE_FILTER) || opt_file_filter;
585 view->dir = dir;
586 if (!argv_format(view->env, &view->argv, argv, !view->prev, file_filter)) {
587 report("Failed to format %s arguments", view->name);
588 return FALSE;
591 /* Put the current view ref value to the view title ref
592 * member. This is needed by the blob view. Most other
593 * views sets it automatically after loading because the
594 * first line is a commit line. */
595 string_copy_rev(view->ref, view->ops->id);
598 if (view->argv && view->argv[0] &&
599 !io_exec(&view->io, IO_RD, view->dir, opt_env, view->argv, io_flags)) {
600 report("Failed to open %s view", view->name);
601 return FALSE;
604 if (open_from_stdin(flags)) {
605 if (!io_open(&view->io, "%s", ""))
606 die("Failed to open stdin");
609 if (!extra)
610 setup_update(view, view->ops->id);
612 return TRUE;
615 bool
616 update_view(struct view *view)
618 char *line;
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;
625 if (!view->pipe)
626 return TRUE;
628 if (!io_can_read(view->pipe, FALSE)) {
629 if (view->lines == 0 && view_is_displayed(view)) {
630 time_t secs = time(NULL) - view->start_time;
632 if (secs > 1 && secs > view->update_secs) {
633 if (view->update_secs == 0)
634 redraw_view(view);
635 update_view_title(view);
636 view->update_secs = secs;
639 return TRUE;
642 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
643 if (encoding) {
644 line = encoding_convert(encoding, line);
647 if (!view->ops->read(view, line)) {
648 report("Allocation failure");
649 end_update(view, TRUE);
650 return FALSE;
654 if (io_error(view->pipe)) {
655 report("Failed to read: %s", io_strerror(view->pipe));
656 end_update(view, TRUE);
658 } else if (io_eof(view->pipe)) {
659 end_update(view, FALSE);
662 if (restore_view_position(view))
663 redraw = TRUE;
665 if (!view_is_displayed(view))
666 return TRUE;
668 if (redraw || view->force_redraw)
669 redraw_view_from(view, 0);
670 else
671 redraw_view_dirty(view);
672 view->force_redraw = FALSE;
674 /* Update the title _after_ the redraw so that if the redraw picks up a
675 * commit reference in view->ref it'll be available here. */
676 update_view_title(view);
677 return TRUE;
680 void
681 update_view_title(struct view *view)
683 WINDOW *window = view->title;
684 struct line *line = &view->line[view->pos.lineno];
685 unsigned int view_lines, lines;
687 assert(view_is_displayed(view));
689 if (view == display[current_view])
690 wbkgdset(window, get_view_attr(view, LINE_TITLE_FOCUS));
691 else
692 wbkgdset(window, get_view_attr(view, LINE_TITLE_BLUR));
694 werase(window);
695 mvwprintw(window, 0, 0, "[%s]", view->name);
697 if (*view->ref) {
698 wprintw(window, " %s", view->ref);
701 if (!view_has_flags(view, VIEW_CUSTOM_STATUS) && view_has_line(view, line) &&
702 line->lineno) {
703 wprintw(window, " - %s %d of %zd",
704 view->ops->type,
705 line->lineno,
706 view->lines - view->custom_lines);
709 if (view->pipe) {
710 time_t secs = time(NULL) - view->start_time;
712 /* Three git seconds are a long time ... */
713 if (secs > 2)
714 wprintw(window, " loading %lds", secs);
717 view_lines = view->pos.offset + view->height;
718 lines = view->lines ? MIN(view_lines, view->lines) * 100 / view->lines : 0;
719 mvwprintw(window, 0, view->width - count_digits(lines) - 1, "%d%%", lines);
721 wnoutrefresh(window);
725 * View opening
728 void
729 split_view(struct view *prev, struct view *view)
731 display[1] = view;
732 current_view = opt_focus_child ? 1 : 0;
733 view->parent = prev;
734 resize_display();
736 if (prev->pos.lineno - prev->pos.offset >= prev->height) {
737 /* Take the title line into account. */
738 int lines = prev->pos.lineno - prev->pos.offset - prev->height + 1;
740 /* Scroll the view that was split if the current line is
741 * outside the new limited view. */
742 do_scroll_view(prev, lines);
745 if (view != prev && view_is_displayed(prev)) {
746 /* "Blur" the previous view. */
747 update_view_title(prev);
751 void
752 maximize_view(struct view *view, bool redraw)
754 memset(display, 0, sizeof(display));
755 current_view = 0;
756 display[current_view] = view;
757 resize_display();
758 if (redraw) {
759 redraw_display(FALSE);
760 report_clear();
764 void
765 load_view(struct view *view, struct view *prev, enum open_flags flags)
767 bool refresh = !view_no_refresh(view, flags);
769 /* When prev == view it means this is the first loaded view. */
770 if (prev && view != prev) {
771 view->prev = prev;
774 if (refresh) {
775 if (view->pipe)
776 end_update(view, TRUE);
777 if (view->ops->private_size) {
778 if (!view->private) {
779 view->private = calloc(1, view->ops->private_size);
780 } else {
781 if (view->ops->done)
782 view->ops->done(view);
783 memset(view->private, 0, view->ops->private_size);
787 if (!view->ops->open(view, flags))
788 return;
791 if (prev) {
792 bool split = !!(flags & OPEN_SPLIT);
794 if (split) {
795 split_view(prev, view);
796 } else {
797 maximize_view(view, FALSE);
801 restore_view_position(view);
803 if (view->pipe && view->lines == 0) {
804 /* Clear the old view and let the incremental updating refill
805 * the screen. */
806 werase(view->win);
807 if (!(flags & (OPEN_RELOAD | OPEN_REFRESH)))
808 clear_position(&view->prev_pos);
809 report_clear();
810 } else if (view_is_displayed(view)) {
811 redraw_view(view);
812 report_clear();
816 #define refresh_view(view) load_view(view, NULL, OPEN_REFRESH)
817 #define reload_view(view) load_view(view, NULL, OPEN_RELOAD)
819 void
820 open_view(struct view *prev, struct view *view, enum open_flags flags)
822 bool reload = !!(flags & (OPEN_RELOAD | OPEN_PREPARED));
823 int nviews = displayed_views();
825 assert(flags ^ OPEN_REFRESH);
827 if (view == prev && nviews == 1 && !reload) {
828 report("Already in %s view", view->name);
829 return;
832 if (!view_has_flags(view, VIEW_NO_GIT_DIR) && !repo.git_dir[0]) {
833 report("The %s view is disabled in pager mode", view->name);
834 return;
837 if (!view->keymap)
838 view->keymap = get_keymap(view->name, strlen(view->name));
839 load_view(view, prev ? prev : view, flags);
842 void
843 open_argv(struct view *prev, struct view *view, const char *argv[], const char *dir, enum open_flags flags)
845 if (view->pipe)
846 end_update(view, TRUE);
847 view->dir = dir;
849 if (!argv_copy(&view->argv, argv)) {
850 report("Failed to open %s view: %s", view->name, io_strerror(&view->io));
851 } else {
852 open_view(prev, view, flags | OPEN_PREPARED);
857 * Various utilities.
860 static struct view *sorting_view;
862 #define sort_order_reverse(state, result) \
863 ((state)->reverse ? -(result) : (result))
865 #define sort_order(state, cmp, o1, o2) \
866 sort_order_reverse(state, (!(o1) || !(o2)) ? !!(o2) - !!(o1) : cmp(o1, o2))
868 #define number_compare(size1, size2) (*(size1) - *(size2))
870 #define mode_is_dir(mode) ((mode) && S_ISDIR(*(mode)))
872 static int
873 sort_view_compare(const void *l1, const void *l2)
875 const struct line *line1 = l1;
876 const struct line *line2 = l2;
877 struct view_column_data column_data1 = {};
878 struct view_column_data column_data2 = {};
879 struct sort_state *sort = &sorting_view->sort;
881 if (!sorting_view->ops->get_column_data(sorting_view, line1, &column_data1))
882 return -1;
883 else if (!sorting_view->ops->get_column_data(sorting_view, line2, &column_data2))
884 return 1;
886 switch (get_sort_field(sorting_view)) {
887 case VIEW_COLUMN_AUTHOR:
888 return sort_order(sort, ident_compare, column_data1.author, column_data2.author);
890 case VIEW_COLUMN_DATE:
891 return sort_order(sort, timecmp, column_data1.date, column_data2.date);
893 case VIEW_COLUMN_ID:
894 return sort_order(sort, strcmp, column_data1.id, column_data2.id);
896 case VIEW_COLUMN_FILE_NAME:
897 if (mode_is_dir(column_data1.mode) != mode_is_dir(column_data2.mode))
898 return sort_order_reverse(sort, mode_is_dir(column_data1.mode) ? -1 : 1);
899 return sort_order(sort, strcmp, column_data1.file_name, column_data2.file_name);
901 case VIEW_COLUMN_FILE_SIZE:
902 return sort_order(sort, number_compare, column_data1.file_size, column_data2.file_size);
904 case VIEW_COLUMN_LINE_NUMBER:
905 return sort_order_reverse(sort, line1->lineno - line2->lineno);
907 case VIEW_COLUMN_MODE:
908 return sort_order(sort, number_compare, column_data1.mode, column_data2.mode);
910 case VIEW_COLUMN_REF:
911 return sort_order(sort, ref_compare, column_data1.ref, column_data2.ref);
913 case VIEW_COLUMN_COMMIT_TITLE:
914 return sort_order(sort, strcmp, column_data1.commit_title, column_data2.commit_title);
916 case VIEW_COLUMN_TEXT:
917 return sort_order(sort, strcmp, column_data1.text, column_data2.text);
921 return 0;
924 void
925 sort_view(struct view *view, bool change_field)
927 struct sort_state *state = &view->sort;
929 if (change_field) {
930 while (TRUE) {
931 state->current = state->current->next
932 ? state->current->next : view->columns;
933 if (get_sort_field(view) == VIEW_COLUMN_ID &&
934 !state->current->opt.id.show)
935 continue;
936 break;
938 } else {
939 state->reverse = !state->reverse;
942 sorting_view = view;
943 qsort(view->line, view->lines, sizeof(*view->line), sort_view_compare);
946 static const char *
947 view_column_text(struct view *view, struct view_column_data *column_data,
948 struct view_column *column)
950 const char *text = "";
952 switch (column->type) {
953 case VIEW_COLUMN_AUTHOR:
954 if (column_data->author)
955 text = mkauthor(column_data->author, column->opt.author.width, column->opt.author.show);
956 break;
958 case VIEW_COLUMN_COMMIT_TITLE:
959 text = column_data->commit_title;
960 break;
962 case VIEW_COLUMN_DATE:
963 if (column_data->date)
964 text = mkdate(column_data->date, column->opt.date.show);
965 break;
967 case VIEW_COLUMN_REF:
968 if (column_data->ref)
969 text = column_data->ref->name;
970 break;
972 case VIEW_COLUMN_FILE_NAME:
973 if (column_data->file_name)
974 text = column_data->file_name;
975 break;
977 case VIEW_COLUMN_FILE_SIZE:
978 if (column_data->file_size)
979 text = mkfilesize(*column_data->file_size, column->opt.file_size.show);
980 break;
982 case VIEW_COLUMN_ID:
983 if (column->opt.id.show)
984 text = column_data->id;
985 break;
987 case VIEW_COLUMN_LINE_NUMBER:
988 break;
990 case VIEW_COLUMN_MODE:
991 if (column_data->mode)
992 text = mkmode(*column_data->mode);
993 break;
995 case VIEW_COLUMN_TEXT:
996 text = column_data->text;
997 break;
1000 return text ? text : "";
1003 static bool
1004 grep_refs(struct view *view, const struct ref_list *list)
1006 regmatch_t pmatch;
1007 size_t i;
1009 if (!opt_show_refs || !list)
1010 return FALSE;
1012 for (i = 0; i < list->size; i++) {
1013 if (!regexec(view->regex, list->refs[i]->name, 1, &pmatch, 0))
1014 return TRUE;
1017 return FALSE;
1020 bool
1021 view_column_grep(struct view *view, struct line *line)
1023 struct view_column_data column_data = {};
1024 bool ok = view->ops->get_column_data(view, line, &column_data);
1025 struct view_column *column;
1027 if (!ok)
1028 return FALSE;
1030 for (column = view->columns; column; column = column->next) {
1031 const char *text[] = {
1032 view_column_text(view, &column_data, column),
1033 NULL
1036 if (grep_text(view, text))
1037 return TRUE;
1040 return grep_refs(view, column_data.refs);
1043 bool
1044 view_column_info_changed(struct view *view, bool update)
1046 struct view_column *column;
1047 bool changed = FALSE;
1049 for (column = view->columns; column; column = column->next) {
1050 if (memcmp(&column->prev_opt, &column->opt, sizeof(column->opt))) {
1051 if (!update)
1052 return TRUE;
1053 column->prev_opt = column->opt;
1054 changed = TRUE;
1058 return changed;
1061 void
1062 view_column_reset(struct view *view)
1064 struct view_column *column;
1066 view_column_info_changed(view, TRUE);
1067 for (column = view->columns; column; column = column->next)
1068 column->width = 0;
1071 bool
1072 view_column_init(struct view *view, const enum view_column_type columns[], size_t columns_size)
1074 struct view_column *column;
1075 int i;
1077 if (view->columns)
1078 return TRUE;
1080 view->columns = calloc(columns_size, sizeof(*view->columns));
1081 if (!view->columns)
1082 return FALSE;
1084 view->sort.current = view->columns;
1085 for (column = NULL, i = 0; i < columns_size; i++) {
1086 union view_column_options opt = {};
1088 if (column)
1089 column->next = &view->columns[i];
1090 column = &view->columns[i];
1091 column->type = columns[i];
1093 switch (column->type) {
1094 case VIEW_COLUMN_AUTHOR:
1095 opt.author.show = opt_show_author;
1096 opt.author.width = opt_author_width;
1097 break;
1099 case VIEW_COLUMN_COMMIT_TITLE:
1100 opt.commit_title.overflow = opt_title_overflow;
1101 opt.commit_title.refs = opt_show_refs;
1102 opt.commit_title.graph = opt_show_rev_graph;
1103 break;
1105 case VIEW_COLUMN_DATE:
1106 opt.date.show = opt_show_date;
1107 break;
1109 case VIEW_COLUMN_REF:
1110 break;
1112 case VIEW_COLUMN_FILE_NAME:
1113 opt.file_name.show = opt_show_filename;
1114 opt.file_name.width = opt_show_filename_width;
1115 break;
1117 case VIEW_COLUMN_FILE_SIZE:
1118 opt.file_size.show = opt_show_file_size;
1119 break;
1121 case VIEW_COLUMN_ID:
1122 opt.id.show = opt_show_id;
1123 opt.id.width = opt_id_width;
1124 break;
1126 case VIEW_COLUMN_LINE_NUMBER:
1127 opt.line_number.show = opt_show_line_numbers;
1128 opt.line_number.interval = opt_line_number_interval;
1129 break;
1131 case VIEW_COLUMN_MODE:
1132 break;
1134 case VIEW_COLUMN_TEXT:
1135 opt.text.commit_title_overflow = opt_title_overflow;
1136 break;
1139 column->opt = opt;
1140 column->prev_opt = opt;
1143 return TRUE;
1146 struct view_column *
1147 get_view_column(struct view *view, enum view_column_type type)
1149 struct view_column *column;
1151 for (column = view->columns; column; column = column->next)
1152 if (column->type == type)
1153 return column;
1154 return NULL;
1157 bool
1158 view_column_info_update(struct view *view, struct line *line)
1160 struct view_column_data column_data = {};
1161 struct view_column *column;
1162 bool changed = FALSE;
1164 if (!view->ops->get_column_data(view, line, &column_data))
1165 return FALSE;
1167 for (column = view->columns; column; column = column->next) {
1168 const char *text = view_column_text(view, &column_data, column);
1169 int width = 0;
1171 switch (column->type) {
1172 case VIEW_COLUMN_AUTHOR:
1173 width = column->opt.author.width;
1174 break;
1176 case VIEW_COLUMN_DATE:
1177 width = column->opt.date.width;
1178 break;
1180 case VIEW_COLUMN_REF:
1181 width = column->opt.ref.width;
1182 break;
1184 case VIEW_COLUMN_FILE_NAME:
1185 width = column->opt.file_name.width;
1186 break;
1188 case VIEW_COLUMN_FILE_SIZE:
1189 width = column->opt.file_size.width;
1190 break;
1192 case VIEW_COLUMN_ID:
1193 width = column->opt.id.width;
1194 if (iscommit(text) && !width)
1195 width = 7;
1196 break;
1198 case VIEW_COLUMN_LINE_NUMBER:
1199 if (column_data.line_number)
1200 width = count_digits(*column_data.line_number);
1201 else
1202 width = count_digits(view->lines);
1203 if (width < 3)
1204 width = 3;
1205 break;
1207 case VIEW_COLUMN_COMMIT_TITLE:
1208 case VIEW_COLUMN_MODE:
1209 case VIEW_COLUMN_TEXT:
1210 break;
1213 if (*text && !width)
1214 width = utf8_width(text);
1216 if (width > column->width) {
1217 column->width = width;
1218 changed = TRUE;
1222 if (changed)
1223 view->force_redraw = TRUE;
1224 return changed;
1227 struct line *
1228 find_line_by_type(struct view *view, struct line *line, enum line_type type, int direction)
1230 for (; view_has_line(view, line); line += direction)
1231 if (line->type == type)
1232 return line;
1234 return NULL;
1238 * Line utilities.
1241 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
1243 struct line *
1244 add_line_at(struct view *view, unsigned long pos, const void *data, enum line_type type, size_t data_size, bool custom)
1246 struct line *line;
1247 unsigned long lineno;
1249 if (!realloc_lines(&view->line, view->lines, 1))
1250 return NULL;
1252 if (data_size) {
1253 void *alloc_data = calloc(1, data_size);
1255 if (!alloc_data)
1256 return NULL;
1258 if (data)
1259 memcpy(alloc_data, data, data_size);
1260 data = alloc_data;
1263 if (pos < view->lines) {
1264 view->lines++;
1265 line = view->line + pos;
1266 lineno = line->lineno;
1268 memmove(line + 1, line, (view->lines - pos) * sizeof(*view->line));
1269 while (pos < view->lines) {
1270 view->line[pos].lineno++;
1271 view->line[pos++].dirty = 1;
1273 } else {
1274 line = &view->line[view->lines++];
1275 lineno = view->lines - view->custom_lines;
1278 memset(line, 0, sizeof(*line));
1279 line->type = type;
1280 line->data = (void *) data;
1281 line->dirty = 1;
1283 if (custom)
1284 view->custom_lines++;
1285 else
1286 line->lineno = lineno;
1288 return line;
1291 struct line *
1292 add_line(struct view *view, const void *data, enum line_type type, size_t data_size, bool custom)
1294 return add_line_at(view, view->lines, data, type, data_size, custom);
1297 struct line *
1298 add_line_alloc_(struct view *view, void **ptr, enum line_type type, size_t data_size, bool custom)
1300 struct line *line = add_line(view, NULL, type, data_size, custom);
1302 if (line)
1303 *ptr = line->data;
1304 return line;
1307 struct line *
1308 add_line_nodata(struct view *view, enum line_type type)
1310 return add_line(view, NULL, type, 0, FALSE);
1313 struct line *
1314 add_line_text(struct view *view, const char *text, enum line_type type)
1316 return add_line(view, text, type, strlen(text) + 1, FALSE);
1319 struct line * PRINTF_LIKE(3, 4)
1320 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
1322 char buf[SIZEOF_STR];
1323 int retval;
1325 FORMAT_BUFFER(buf, sizeof(buf), fmt, retval, FALSE);
1326 return retval >= 0 ? add_line_text(view, buf, type) : NULL;
1330 * Global view state.
1333 /* Included last to not pollute the rest of the file. */
1334 #include "tig/main.h"
1335 #include "tig/diff.h"
1336 #include "tig/log.h"
1337 #include "tig/tree.h"
1338 #include "tig/blob.h"
1339 #include "tig/blame.h"
1340 #include "tig/refs.h"
1341 #include "tig/status.h"
1342 #include "tig/stage.h"
1343 #include "tig/stash.h"
1344 #include "tig/grep.h"
1345 #include "tig/pager.h"
1346 #include "tig/help.h"
1348 static struct view *views[] = {
1349 #define VIEW_DATA(id, name) &name##_view
1350 VIEW_INFO(VIEW_DATA)
1353 struct view *
1354 get_view(int i)
1356 return 0 <= i && i < ARRAY_SIZE(views) ? views[i] : NULL;
1359 /* vim: set ts=8 sw=8 noexpandtab: */