Fix `:goto <id>` error message
[tig.git] / src / view.c
blobcca6c58871def9235f9513980d5103ebfcb51668
1 /* Copyright (c) 2006-2015 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/search.h"
21 #include "tig/draw.h"
22 #include "tig/display.h"
25 * Navigation
28 bool
29 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
31 if (lineno >= view->lines)
32 lineno = view->lines > 0 ? view->lines - 1 : 0;
34 if (offset > lineno || offset + view->height <= lineno) {
35 unsigned long half = view->height / 2;
37 if (lineno > half)
38 offset = lineno - half;
39 else
40 offset = 0;
43 if (offset != view->pos.offset || lineno != view->pos.lineno) {
44 view->pos.offset = offset;
45 view->pos.lineno = lineno;
46 return true;
49 return false;
52 /* Scrolling backend */
53 void
54 do_scroll_view(struct view *view, int lines)
56 bool redraw_current_line = false;
58 /* The rendering expects the new offset. */
59 view->pos.offset += lines;
61 assert(0 <= view->pos.offset && view->pos.offset < view->lines);
62 assert(lines);
64 /* Move current line into the view. */
65 if (view->pos.lineno < view->pos.offset) {
66 view->pos.lineno = view->pos.offset;
67 redraw_current_line = true;
68 } else if (view->pos.lineno >= view->pos.offset + view->height) {
69 view->pos.lineno = view->pos.offset + view->height - 1;
70 redraw_current_line = true;
73 assert(view->pos.offset <= view->pos.lineno && view->pos.lineno < view->lines);
75 /* Redraw the whole screen if scrolling is pointless. */
76 if (view->height < ABS(lines)) {
77 redraw_view(view);
79 } else {
80 int line = lines > 0 ? view->height - lines : 0;
81 int end = line + ABS(lines);
83 scrollok(view->win, true);
84 wscrl(view->win, lines);
85 scrollok(view->win, false);
87 while (line < end && draw_view_line(view, line))
88 line++;
90 if (redraw_current_line)
91 draw_view_line(view, view->pos.lineno - view->pos.offset);
92 wnoutrefresh(view->win);
95 view->has_scrolled = true;
96 report_clear();
99 /* Scroll frontend */
100 void
101 scroll_view(struct view *view, enum request request)
103 int lines = 1;
105 assert(view_is_displayed(view));
107 if (request == REQ_SCROLL_WHEEL_DOWN || request == REQ_SCROLL_WHEEL_UP)
108 lines = opt_mouse_scroll;
110 switch (request) {
111 case REQ_SCROLL_FIRST_COL:
112 view->pos.col = 0;
113 redraw_view_from(view, 0);
114 report_clear();
115 return;
116 case REQ_SCROLL_LEFT:
117 if (view->pos.col == 0) {
118 report("Cannot scroll beyond the first column");
119 return;
121 if (view->pos.col <= apply_step(opt_horizontal_scroll, view->width))
122 view->pos.col = 0;
123 else
124 view->pos.col -= apply_step(opt_horizontal_scroll, view->width);
125 redraw_view_from(view, 0);
126 report_clear();
127 return;
128 case REQ_SCROLL_RIGHT:
129 view->pos.col += apply_step(opt_horizontal_scroll, view->width);
130 redraw_view(view);
131 report_clear();
132 return;
133 case REQ_SCROLL_PAGE_DOWN:
134 lines = view->height;
135 case REQ_SCROLL_WHEEL_DOWN:
136 case REQ_SCROLL_LINE_DOWN:
137 if (view->pos.offset + lines > view->lines)
138 lines = view->lines - view->pos.offset;
140 if (lines == 0 || view->pos.offset + view->height >= view->lines) {
141 report("Cannot scroll beyond the last line");
142 return;
144 break;
146 case REQ_SCROLL_PAGE_UP:
147 lines = view->height;
148 case REQ_SCROLL_LINE_UP:
149 case REQ_SCROLL_WHEEL_UP:
150 if (lines > view->pos.offset)
151 lines = view->pos.offset;
153 if (lines == 0) {
154 report("Cannot scroll beyond the first line");
155 return;
158 lines = -lines;
159 break;
161 default:
162 die("request %d not handled in switch", request);
165 do_scroll_view(view, lines);
168 /* Cursor moving */
169 void
170 move_view(struct view *view, enum request request)
172 int scroll_steps = 0;
173 int steps;
175 switch (request) {
176 case REQ_MOVE_FIRST_LINE:
177 steps = -view->pos.lineno;
178 break;
180 case REQ_MOVE_LAST_LINE:
181 steps = view->lines - view->pos.lineno - 1;
182 break;
184 case REQ_MOVE_PAGE_UP:
185 steps = view->height > view->pos.lineno
186 ? -view->pos.lineno : -view->height;
187 break;
189 case REQ_MOVE_PAGE_DOWN:
190 steps = view->pos.lineno + view->height >= view->lines
191 ? view->lines - view->pos.lineno - 1 : view->height;
192 break;
194 case REQ_MOVE_HALF_PAGE_UP:
195 steps = view->height / 2 > view->pos.lineno
196 ? -view->pos.lineno : -(view->height / 2);
197 break;
199 case REQ_MOVE_HALF_PAGE_DOWN:
200 steps = view->pos.lineno + view->height / 2 >= view->lines
201 ? view->lines - view->pos.lineno - 1 : view->height / 2;
202 break;
204 case REQ_MOVE_UP:
205 case REQ_PREVIOUS:
206 steps = -1;
207 break;
209 case REQ_MOVE_DOWN:
210 case REQ_NEXT:
211 steps = 1;
212 break;
214 default:
215 die("request %d not handled in switch", request);
218 if (steps <= 0 && view->pos.lineno == 0) {
219 report("Cannot move beyond the first line");
220 return;
222 } else if (steps >= 0 && view->pos.lineno + 1 >= view->lines) {
223 report("Cannot move beyond the last line");
224 return;
227 /* Move the current line */
228 view->pos.lineno += steps;
229 assert(0 <= view->pos.lineno && view->pos.lineno < view->lines);
231 /* Check whether the view needs to be scrolled */
232 if (view->pos.lineno < view->pos.offset ||
233 view->pos.lineno >= view->pos.offset + view->height) {
234 scroll_steps = steps;
235 if (steps < 0 && -steps > view->pos.offset) {
236 scroll_steps = -view->pos.offset;
238 } else if (steps > 0) {
239 if (view->pos.lineno == view->lines - 1 &&
240 view->lines > view->height) {
241 scroll_steps = view->lines - view->pos.offset - 1;
242 if (scroll_steps >= view->height)
243 scroll_steps -= view->height - 1;
248 if (!view_is_displayed(view)) {
249 view->pos.offset += scroll_steps;
250 assert(0 <= view->pos.offset && view->pos.offset < view->lines);
251 view->ops->select(view, &view->line[view->pos.lineno]);
252 return;
255 /* Repaint the old "current" line if we be scrolling */
256 if (ABS(steps) < view->height)
257 draw_view_line(view, view->pos.lineno - steps - view->pos.offset);
259 if (scroll_steps) {
260 do_scroll_view(view, scroll_steps);
261 return;
264 /* Draw the current line */
265 draw_view_line(view, view->pos.lineno - view->pos.offset);
267 wnoutrefresh(view->win);
268 report_clear();
271 void
272 select_view_line(struct view *view, unsigned long lineno)
274 struct position old = view->pos;
276 if (goto_view_line(view, view->pos.offset, lineno)) {
277 if (view_is_displayed(view)) {
278 if (old.offset != view->pos.offset) {
279 redraw_view(view);
280 } else {
281 draw_view_line(view, old.lineno - view->pos.offset);
282 draw_view_line(view, view->pos.lineno - view->pos.offset);
283 wnoutrefresh(view->win);
285 } else {
286 view->ops->select(view, &view->line[view->pos.lineno]);
291 void
292 goto_id(struct view *view, const char *expr, bool from_start, bool save_search)
294 struct view_column_data column_data = {0};
295 char id[SIZEOF_STR] = "";
296 size_t idlen;
297 struct line *line = &view->line[view->pos.lineno];
299 if (!(view->ops->column_bits & view_column_bit(ID))) {
300 report("Jumping to ID is not supported by the %s view", view->name);
301 return;
302 } else {
303 char *rev = argv_format_arg(view->env, expr);
304 const char *rev_parse_argv[] = {
305 "git", "rev-parse", "--revs-only", rev, NULL
307 bool ok = rev && io_run_buf(rev_parse_argv, id, sizeof(id), true);
309 free(rev);
310 if (!ok) {
311 report("Failed to parse expression '%s'", expr);
312 return;
316 if (!id[0]) {
317 if (view->ops->get_column_data(view, line, &column_data)
318 && column_data.id && string_rev_is_null(column_data.id)) {
319 select_view_line(view, view->pos.lineno + 1);
320 report_clear();
321 } else {
322 report("Expression '%s' is not a meaningful revision", expr);
324 return;
327 line = from_start ? view->line : &view->line[view->pos.lineno];
329 for (idlen = strlen(id); view_has_line(view, line); line++) {
330 struct view_column_data column_data = {0};
332 if (view->ops->get_column_data(view, line, &column_data) &&
333 column_data.id &&
334 !strncasecmp(column_data.id, id, idlen)) {
335 if (save_search)
336 string_ncopy(view->env->search, id, idlen);
337 select_view_line(view, line - view->line);
338 report_clear();
339 return;
343 report("Unable to find commit '%s'", id);
347 * View history
350 static bool
351 view_history_is_empty(struct view_history *history)
353 return !history->stack;
356 struct view_state *
357 push_view_history_state(struct view_history *history, struct position *position, void *data)
359 struct view_state *state = history->stack;
361 if (state && data && history->state_alloc &&
362 !memcmp(state->data, data, history->state_alloc))
363 return NULL;
365 state = calloc(1, sizeof(*state) + history->state_alloc);
366 if (!state)
367 return NULL;
369 state->prev = history->stack;
370 history->stack = state;
371 clear_position(&history->position);
372 state->position = *position;
373 state->data = &state[1];
374 if (data && history->state_alloc)
375 memcpy(state->data, data, history->state_alloc);
376 return state;
379 bool
380 pop_view_history_state(struct view_history *history, struct position *position, void *data)
382 struct view_state *state = history->stack;
384 if (view_history_is_empty(history))
385 return false;
387 history->position = state->position;
388 history->stack = state->prev;
390 if (data && history->state_alloc)
391 memcpy(data, state->data, history->state_alloc);
392 if (position)
393 *position = state->position;
395 free(state);
396 return true;
399 void
400 reset_view_history(struct view_history *history)
402 while (pop_view_history_state(history, NULL, NULL))
407 * Incremental updating
410 void
411 reset_view(struct view *view)
413 int i;
415 for (i = 0; i < view->lines; i++)
416 free(view->line[i].data);
417 free(view->line);
419 reset_search(view);
420 view->prev_pos = view->pos;
421 /* A view without a previous view is the first view */
422 if (!view->prev && !view->lines && view->prev_pos.lineno == 0)
423 view->prev_pos.lineno = view->env->goto_lineno;
424 clear_position(&view->pos);
426 if (view->columns)
427 view_column_reset(view);
429 view->line = NULL;
430 view->lines = 0;
431 view->vid[0] = 0;
432 view->custom_lines = 0;
433 view->update_secs = 0;
436 static bool
437 restore_view_position(struct view *view)
439 /* Ensure that the view position is in a valid state. */
440 if (!check_position(&view->prev_pos) ||
441 (view->pipe && view->lines <= view->prev_pos.lineno))
442 return goto_view_line(view, view->pos.offset, view->pos.lineno);
444 /* Changing the view position cancels the restoring. */
445 /* FIXME: Changing back to the first line is not detected. */
446 if (check_position(&view->pos)) {
447 clear_position(&view->prev_pos);
448 return false;
451 if (goto_view_line(view, view->prev_pos.offset, view->prev_pos.lineno) &&
452 view_is_displayed(view))
453 werase(view->win);
455 view->pos.col = view->prev_pos.col;
456 clear_position(&view->prev_pos);
458 return true;
461 void
462 end_update(struct view *view, bool force)
464 if (!view->pipe)
465 return;
466 while (!view->ops->read(view, NULL, force))
467 if (!force)
468 return;
469 if (force)
470 io_kill(view->pipe);
471 io_done(view->pipe);
472 view->pipe = NULL;
475 static void
476 setup_update(struct view *view, const char *vid)
478 reset_view(view);
479 /* XXX: Do not use string_copy_rev(), it copies until first space. */
480 string_ncopy(view->vid, vid, strlen(vid));
481 view->pipe = &view->io;
482 view->start_time = time(NULL);
485 static bool
486 view_no_refresh(struct view *view, enum open_flags flags)
488 bool reload = !!(flags & OPEN_ALWAYS_LOAD) || !view->lines;
490 return (!reload && !strcmp(view->vid, view->ops->id)) ||
491 ((flags & OPEN_REFRESH) && !view_can_refresh(view));
494 bool
495 view_exec(struct view *view, enum open_flags flags)
497 char opt_env_lines[64] = "";
498 char opt_env_columns[64] = "";
499 char * const opt_env[] = { opt_env_lines, opt_env_columns, NULL };
501 enum io_flags forward_stdin = (flags & OPEN_FORWARD_STDIN) ? IO_RD_FORWARD_STDIN : 0;
502 enum io_flags with_stderr = (flags & OPEN_WITH_STDERR) ? IO_RD_WITH_STDERR : 0;
503 enum io_flags io_flags = forward_stdin | with_stderr;
505 int views = displayed_views();
506 bool split = (views == 1 && !!(flags & OPEN_SPLIT)) || views == 2;
507 int height, width;
509 getmaxyx(stdscr, height, width);
510 if (split && vertical_split_is_enabled(opt_vertical_split, height, width)) {
511 bool is_base_view = display[0] == view;
512 int split_width = apply_vertical_split(width);
514 if (is_base_view)
515 width -= split_width;
516 else
517 width = split_width - 1;
520 string_format(opt_env_columns, "COLUMNS=%d", MAX(0, width));
521 string_format(opt_env_lines, "LINES=%d", height);
523 return io_exec(&view->io, IO_RD, view->dir, opt_env, view->argv, io_flags);
526 bool
527 begin_update(struct view *view, const char *dir, const char **argv, enum open_flags flags)
529 bool extra = !!(flags & (OPEN_EXTRA));
530 bool refresh = flags & (OPEN_REFRESH | OPEN_PREPARED | OPEN_STDIN);
532 if (view_no_refresh(view, flags))
533 return true;
535 if (view->pipe) {
536 if (extra)
537 io_done(view->pipe);
538 else
539 end_update(view, true);
542 view->unrefreshable = open_in_pager_mode(flags);
544 if (!refresh && argv) {
545 bool file_filter = !view_has_flags(view, VIEW_FILE_FILTER) || opt_file_filter;
547 view->dir = dir;
548 if (!argv_format(view->env, &view->argv, argv, !view->prev, file_filter)) {
549 report("Failed to format %s arguments", view->name);
550 return false;
554 if (view->argv && view->argv[0] &&
555 !view_exec(view, flags)) {
556 report("Failed to open %s view", view->name);
557 return false;
560 if (open_from_stdin(flags)) {
561 if (!io_open(&view->io, "%s", ""))
562 die("Failed to open stdin");
565 if (!extra)
566 setup_update(view, view->ops->id);
568 return true;
571 bool
572 update_view(struct view *view)
574 /* Clear the view and redraw everything since the tree sorting
575 * might have rearranged things. */
576 bool redraw = view->lines == 0;
577 bool can_read = true;
578 struct encoding *encoding = view->encoding ? view->encoding : default_encoding;
579 struct buffer line;
581 if (!view->pipe)
582 return true;
584 if (!io_can_read(view->pipe, false)) {
585 if (view->lines == 0 && view_is_displayed(view)) {
586 time_t secs = time(NULL) - view->start_time;
588 if (secs > 1 && secs > view->update_secs) {
589 if (view->update_secs == 0)
590 redraw_view(view);
591 update_view_title(view);
592 view->update_secs = secs;
595 return true;
598 for (; io_get(view->pipe, &line, '\n', can_read); can_read = false) {
599 if (encoding && !encoding_convert(encoding, &line)) {
600 report("Encoding failure");
601 end_update(view, true);
602 return false;
605 if (!view->ops->read(view, &line, false)) {
606 report("Allocation failure");
607 end_update(view, true);
608 return false;
612 if (io_error(view->pipe)) {
613 report("Failed to read: %s", io_strerror(view->pipe));
614 end_update(view, true);
616 } else if (io_eof(view->pipe)) {
617 end_update(view, false);
620 if (restore_view_position(view))
621 redraw = true;
623 if (!view_is_displayed(view))
624 return true;
626 if (redraw || view->force_redraw)
627 redraw_view_from(view, 0);
628 else
629 redraw_view_dirty(view);
630 view->force_redraw = false;
632 /* Update the title _after_ the redraw so that if the redraw picks up a
633 * commit reference in view->ref it'll be available here. */
634 update_view_title(view);
635 return true;
638 void
639 update_view_title(struct view *view)
641 WINDOW *window = view->title;
642 struct line *line = &view->line[view->pos.lineno];
643 unsigned int view_lines, lines;
645 assert(view_is_displayed(view));
647 if (view == display[current_view])
648 wbkgdset(window, get_view_attr(view, LINE_TITLE_FOCUS));
649 else
650 wbkgdset(window, get_view_attr(view, LINE_TITLE_BLUR));
652 werase(window);
653 mvwprintw(window, 0, 0, "[%s]", view->name);
655 if (*view->ref) {
656 wprintw(window, " %s", view->ref);
659 if (!view_has_flags(view, VIEW_CUSTOM_STATUS) && view_has_line(view, line) &&
660 line->lineno) {
661 wprintw(window, " - %s %d of %zd",
662 view->ops->type,
663 line->lineno,
664 view->lines - view->custom_lines);
667 if (view->pipe) {
668 time_t secs = time(NULL) - view->start_time;
670 /* Three git seconds are a long time ... */
671 if (secs > 2)
672 wprintw(window, " loading %lds", secs);
675 view_lines = view->pos.offset + view->height;
676 lines = view->lines ? MIN(view_lines, view->lines) * 100 / view->lines : 0;
677 mvwprintw(window, 0, view->width - count_digits(lines) - 1, "%d%%", lines);
679 wnoutrefresh(window);
683 * View opening
686 void
687 split_view(struct view *prev, struct view *view)
689 display[1] = view;
690 current_view = opt_focus_child ? 1 : 0;
691 view->parent = prev;
692 resize_display();
694 if (prev->pos.lineno - prev->pos.offset >= prev->height) {
695 /* Take the title line into account. */
696 int lines = prev->pos.lineno - prev->pos.offset - prev->height + 1;
698 /* Scroll the view that was split if the current line is
699 * outside the new limited view. */
700 do_scroll_view(prev, lines);
703 if (view != prev && view_is_displayed(prev)) {
704 /* "Blur" the previous view. */
705 update_view_title(prev);
708 if (view_has_flags(prev, VIEW_FLEX_WIDTH))
709 load_view(prev, NULL, OPEN_RELOAD);
712 void
713 maximize_view(struct view *view, bool redraw)
715 memset(display, 0, sizeof(display));
716 current_view = 0;
717 display[current_view] = view;
718 resize_display();
719 if (redraw) {
720 redraw_display(false);
721 report_clear();
724 if (view_has_flags(view, VIEW_FLEX_WIDTH))
725 load_view(view, NULL, OPEN_RELOAD);
728 void
729 load_view(struct view *view, struct view *prev, enum open_flags flags)
731 bool refresh = !view_no_refresh(view, flags);
733 /* When prev == view it means this is the first loaded view. */
734 if (prev && view != prev) {
735 view->prev = prev;
738 if (!refresh && view_can_refresh(view) &&
739 watch_update_single(&view->watch, WATCH_EVENT_SWITCH_VIEW)) {
740 refresh = watch_dirty(&view->watch);
741 if (refresh)
742 flags |= OPEN_REFRESH;
745 if (refresh) {
746 if (view->pipe)
747 end_update(view, true);
748 if (view->ops->private_size) {
749 if (!view->private) {
750 view->private = calloc(1, view->ops->private_size);
751 } else {
752 if (view->ops->done)
753 view->ops->done(view);
754 memset(view->private, 0, view->ops->private_size);
758 if (!view->ops->open(view, flags))
759 return;
762 if (prev) {
763 bool split = !!(flags & OPEN_SPLIT);
765 if (split) {
766 split_view(prev, view);
767 } else {
768 maximize_view(view, false);
772 restore_view_position(view);
774 if (view->pipe && view->lines == 0) {
775 /* Clear the old view and let the incremental updating refill
776 * the screen. */
777 werase(view->win);
778 /* Do not clear the position if it is the first view. */
779 if (view->prev && !(flags & (OPEN_RELOAD | OPEN_REFRESH)))
780 clear_position(&view->prev_pos);
781 report_clear();
782 } else if (view_is_displayed(view)) {
783 redraw_view(view);
784 report_clear();
788 #define refresh_view(view) load_view(view, NULL, OPEN_REFRESH)
789 #define reload_view(view) load_view(view, NULL, OPEN_RELOAD)
791 void
792 open_view(struct view *prev, struct view *view, enum open_flags flags)
794 bool reload = !!(flags & (OPEN_RELOAD | OPEN_PREPARED));
795 int nviews = displayed_views();
797 assert(flags ^ OPEN_REFRESH);
799 if (view == prev && nviews == 1 && !reload) {
800 report("Already in %s view", view->name);
801 return;
804 if (!view_has_flags(view, VIEW_NO_GIT_DIR) && !repo.git_dir[0]) {
805 report("The %s view is disabled in pager mode", view->name);
806 return;
809 if (!view->keymap)
810 view->keymap = get_keymap(view->name, strlen(view->name));
811 load_view(view, prev ? prev : view, flags);
814 void
815 open_argv(struct view *prev, struct view *view, const char *argv[], const char *dir, enum open_flags flags)
817 if (view->pipe)
818 end_update(view, true);
819 view->dir = dir;
821 if (!argv_copy(&view->argv, argv)) {
822 report("Failed to open %s view: %s", view->name, io_strerror(&view->io));
823 } else {
824 open_view(prev, view, flags | OPEN_PREPARED);
829 * Various utilities.
832 static struct view *sorting_view;
834 #define apply_comparator(cmp, o1, o2) \
835 (!(o1) || !(o2)) ? !!(o2) - !!(o1) : cmp(o1, o2)
837 #define number_compare(size1, size2) (*(size1) - *(size2))
839 #define mode_is_dir(mode) ((mode) && S_ISDIR(*(mode)))
841 static int
842 compare_view_column(enum view_column_type column, bool use_file_mode,
843 const struct line *line1, struct view_column_data *column_data1,
844 const struct line *line2, struct view_column_data *column_data2)
846 switch (column) {
847 case VIEW_COLUMN_AUTHOR:
848 return apply_comparator(ident_compare, column_data1->author, column_data2->author);
850 case VIEW_COLUMN_DATE:
851 return apply_comparator(timecmp, column_data1->date, column_data2->date);
853 case VIEW_COLUMN_ID:
854 if (column_data1->reflog && column_data2->reflog)
855 return apply_comparator(strcmp, column_data1->reflog, column_data2->reflog);
856 return apply_comparator(strcmp, column_data1->id, column_data2->id);
858 case VIEW_COLUMN_FILE_NAME:
859 if (use_file_mode && mode_is_dir(column_data1->mode) != mode_is_dir(column_data2->mode))
860 return mode_is_dir(column_data1->mode) ? -1 : 1;
861 return apply_comparator(strcmp, column_data1->file_name, column_data2->file_name);
863 case VIEW_COLUMN_FILE_SIZE:
864 return apply_comparator(number_compare, column_data1->file_size, column_data2->file_size);
866 case VIEW_COLUMN_LINE_NUMBER:
867 return line1->lineno - line2->lineno;
869 case VIEW_COLUMN_MODE:
870 return apply_comparator(number_compare, column_data1->mode, column_data2->mode);
872 case VIEW_COLUMN_REF:
873 return apply_comparator(ref_compare, column_data1->ref, column_data2->ref);
875 case VIEW_COLUMN_COMMIT_TITLE:
876 return apply_comparator(strcmp, column_data1->commit_title, column_data2->commit_title);
878 case VIEW_COLUMN_SECTION:
879 return apply_comparator(strcmp, column_data1->section->opt.section.text,
880 column_data2->section->opt.section.text);
882 case VIEW_COLUMN_STATUS:
883 return apply_comparator(number_compare, column_data1->status, column_data2->status);
885 case VIEW_COLUMN_TEXT:
886 if (column_data1->box && column_data2->box)
887 return apply_comparator(strcmp, column_data1->box->text,
888 column_data2->box->text);
889 return apply_comparator(strcmp, column_data1->text, column_data2->text);
892 return 0;
895 static enum view_column_type view_column_order[] = {
896 VIEW_COLUMN_FILE_NAME,
897 VIEW_COLUMN_STATUS,
898 VIEW_COLUMN_MODE,
899 VIEW_COLUMN_FILE_SIZE,
900 VIEW_COLUMN_DATE,
901 VIEW_COLUMN_AUTHOR,
902 VIEW_COLUMN_COMMIT_TITLE,
903 VIEW_COLUMN_LINE_NUMBER,
904 VIEW_COLUMN_SECTION,
905 VIEW_COLUMN_TEXT,
906 VIEW_COLUMN_REF,
907 VIEW_COLUMN_ID,
910 static int
911 sort_view_compare(const void *l1, const void *l2)
913 const struct line *line1 = l1;
914 const struct line *line2 = l2;
915 struct view_column_data column_data1 = {0};
916 struct view_column_data column_data2 = {0};
917 struct sort_state *sort = &sorting_view->sort;
918 enum view_column_type column = get_sort_field(sorting_view);
919 int cmp;
920 int i;
922 if (!sorting_view->ops->get_column_data(sorting_view, line1, &column_data1))
923 return -1;
924 else if (!sorting_view->ops->get_column_data(sorting_view, line2, &column_data2))
925 return 1;
927 cmp = compare_view_column(column, true, line1, &column_data1, line2, &column_data2);
929 /* Ensure stable sorting by ordering by the other
930 * columns if the selected column values are equal. */
931 for (i = 0; !cmp && i < ARRAY_SIZE(view_column_order); i++)
932 if (column != view_column_order[i])
933 cmp = compare_view_column(view_column_order[i], false,
934 line1, &column_data1,
935 line2, &column_data2);
937 return sort->reverse ? -cmp : cmp;
940 void
941 resort_view(struct view *view, bool renumber)
943 sorting_view = view;
944 qsort(view->line, view->lines, sizeof(*view->line), sort_view_compare);
946 if (renumber) {
947 size_t i, lineno;
949 for (i = 0, lineno = 1; i < view->lines; i++)
950 if (view->line[i].lineno)
951 view->line[i].lineno = lineno++;
955 void
956 sort_view(struct view *view, bool change_field)
958 struct sort_state *state = &view->sort;
960 if (change_field) {
961 while (true) {
962 state->current = state->current->next
963 ? state->current->next : view->columns;
964 if (get_sort_field(view) == VIEW_COLUMN_ID &&
965 !state->current->opt.id.display)
966 continue;
967 break;
969 } else {
970 state->reverse = !state->reverse;
973 resort_view(view, false);
976 static const char *
977 view_column_text(struct view *view, struct view_column_data *column_data,
978 struct view_column *column)
980 const char *text = "";
982 switch (column->type) {
983 case VIEW_COLUMN_AUTHOR:
984 if (column_data->author)
985 text = mkauthor(column_data->author, column->opt.author.width, column->opt.author.display);
986 break;
988 case VIEW_COLUMN_COMMIT_TITLE:
989 text = column_data->commit_title;
990 break;
992 case VIEW_COLUMN_DATE:
993 if (column_data->date)
994 text = mkdate(column_data->date, column->opt.date.display,
995 column->opt.date.local, column->opt.date.format);
996 break;
998 case VIEW_COLUMN_REF:
999 if (column_data->ref)
1000 text = column_data->ref->name;
1001 break;
1003 case VIEW_COLUMN_FILE_NAME:
1004 if (column_data->file_name)
1005 text = column_data->file_name;
1006 break;
1008 case VIEW_COLUMN_FILE_SIZE:
1009 if (column_data->file_size)
1010 text = mkfilesize(*column_data->file_size, column->opt.file_size.display);
1011 break;
1013 case VIEW_COLUMN_ID:
1014 if (column->opt.id.display)
1015 text = column_data->reflog ? column_data->reflog : column_data->id;
1016 break;
1018 case VIEW_COLUMN_LINE_NUMBER:
1019 break;
1021 case VIEW_COLUMN_MODE:
1022 if (column_data->mode)
1023 text = mkmode(*column_data->mode);
1024 break;
1026 case VIEW_COLUMN_STATUS:
1027 if (column_data->status)
1028 text = mkstatus(*column_data->status, column->opt.status.display);
1029 break;
1031 case VIEW_COLUMN_SECTION:
1032 text = column_data->section->opt.section.text;
1033 break;
1035 case VIEW_COLUMN_TEXT:
1036 text = column_data->text;
1037 break;
1040 return text ? text : "";
1043 static bool
1044 grep_refs(struct view *view, struct view_column *column, const struct ref *ref)
1046 regmatch_t pmatch;
1048 for (; ref; ref = ref->next) {
1049 if (!regexec(view->regex, ref->name, 1, &pmatch, 0))
1050 return true;
1053 return false;
1056 bool
1057 view_column_grep(struct view *view, struct line *line)
1059 struct view_column_data column_data = {0};
1060 bool ok = view->ops->get_column_data(view, line, &column_data);
1061 struct view_column *column;
1063 if (!ok)
1064 return false;
1066 for (column = view->columns; column; column = column->next) {
1067 const char *text[] = {
1068 view_column_text(view, &column_data, column),
1069 NULL
1072 if (grep_text(view, text))
1073 return true;
1075 if (column->type == VIEW_COLUMN_COMMIT_TITLE &&
1076 column->opt.commit_title.refs &&
1077 grep_refs(view, column, column_data.refs))
1078 return true;
1081 return false;
1084 bool
1085 view_column_info_changed(struct view *view, bool update)
1087 struct view_column *column;
1088 bool changed = false;
1090 for (column = view->columns; column; column = column->next) {
1091 if (memcmp(&column->prev_opt, &column->opt, sizeof(column->opt))) {
1092 if (!update)
1093 return true;
1094 column->prev_opt = column->opt;
1095 changed = true;
1099 return changed;
1102 void
1103 view_column_reset(struct view *view)
1105 struct view_column *column;
1107 view_column_info_changed(view, true);
1108 for (column = view->columns; column; column = column->next)
1109 column->width = 0;
1112 static enum status_code
1113 parse_view_column_config_expr(char **pos, const char **name, const char **value, bool first)
1115 size_t len = strcspn(*pos, ",");
1116 size_t optlen;
1118 if (strlen(*pos) > len)
1119 (*pos)[len] = 0;
1120 optlen = strcspn(*pos, ":=");
1122 if (first) {
1123 *name = "display";
1125 if (optlen == len) {
1126 *value = len ? *pos : "yes";
1127 *pos += len + 1;
1128 return SUCCESS;
1131 /* Fake boolean enum value. */
1132 *value = "yes";
1133 return SUCCESS;
1136 *name = *pos;
1137 if (optlen == len)
1138 *value = "yes";
1139 else
1140 *value = *pos + optlen + 1;
1141 (*pos)[optlen] = 0;
1142 *pos += len + 1;
1144 return SUCCESS;
1147 static enum status_code
1148 parse_view_column_option(struct view_column *column,
1149 const char *opt_name, const char *opt_value)
1151 #define DEFINE_COLUMN_OPTION_INFO(name, type, flags) \
1152 { #name, STRING_SIZE(#name), #type, &opt->name, flags },
1154 #define DEFINE_COLUMN_OPTIONS_PARSE(name, id, options) \
1155 if (column->type == VIEW_COLUMN_##id) { \
1156 struct name##_options *opt = &column->opt.name; \
1157 struct option_info info[] = { \
1158 options(DEFINE_COLUMN_OPTION_INFO) \
1159 }; \
1160 struct option_info *option = find_option_info(info, ARRAY_SIZE(info), "", opt_name); \
1161 if (!option) \
1162 return error("Unknown option `%s' for column %s", opt_name, \
1163 view_column_name(VIEW_COLUMN_##id)); \
1164 return parse_option(option, #name, opt_value); \
1167 COLUMN_OPTIONS(DEFINE_COLUMN_OPTIONS_PARSE);
1169 return error("Unknown view column option: %s", opt_name);
1172 static enum status_code
1173 parse_view_column_config_exprs(struct view_column *column, const char *arg)
1175 char buf[SIZEOF_STR] = "";
1176 char *pos, *end;
1177 bool first = true;
1178 enum status_code code = SUCCESS;
1180 string_ncopy(buf, arg, strlen(arg));
1182 for (pos = buf, end = pos + strlen(pos); code == SUCCESS && pos <= end; first = false) {
1183 const char *name = NULL;
1184 const char *value = NULL;
1186 code = parse_view_column_config_expr(&pos, &name, &value, first);
1187 if (code == SUCCESS)
1188 code = parse_view_column_option(column, name, value);
1191 return code;
1194 static enum status_code
1195 parse_view_column_type(struct view_column *column, const char **arg)
1197 enum view_column_type type;
1198 size_t typelen = strcspn(*arg, ":,");
1200 for (type = 0; type < view_column_type_map->size; type++)
1201 if (enum_equals(view_column_type_map->entries[type], *arg, typelen)) {
1202 *arg += typelen + !!(*arg)[typelen];
1203 column->type = type;
1204 return SUCCESS;
1207 return error("Failed to parse view column type: %.*s", (int) typelen, *arg);
1210 static struct view *
1211 find_view(const char *view_name)
1213 struct view *view;
1214 int i;
1216 foreach_view(view, i)
1217 if (!strncmp(view_name, view->name, strlen(view->name)))
1218 return view;
1220 return NULL;
1223 enum status_code
1224 parse_view_column_config(const char *view_name, enum view_column_type type,
1225 const char *option_name, const char *argv[])
1227 struct view_column *column;
1228 struct view *view = find_view(view_name);
1230 if (!view)
1231 return error("Unknown view: %s", view_name);
1233 if (!(view->ops->column_bits & (1 << type)))
1234 return error("The %s view does not support %s column", view->name,
1235 view_column_name(type));
1237 column = get_view_column(view, type);
1238 if (!column)
1239 return error("The %s view does not have a %s column configured", view->name,
1240 view_column_name(type));
1242 if (option_name)
1243 return parse_view_column_option(column, option_name, argv[0]);
1244 return parse_view_column_config_exprs(column, argv[0]);
1247 enum status_code
1248 parse_view_config(struct view_column **column_ref, const char *view_name, const char *argv[])
1250 enum status_code code = SUCCESS;
1251 size_t size = argv_size(argv);
1252 struct view_column *columns;
1253 struct view_column *column;
1254 struct view *view = find_view(view_name);
1255 int i;
1257 if (!view)
1258 return error("Unknown view: %s", view_name);
1260 columns = calloc(size, sizeof(*columns));
1261 if (!columns)
1262 return ERROR_OUT_OF_MEMORY;
1264 for (i = 0, column = NULL; code == SUCCESS && i < size; i++) {
1265 const char *arg = argv[i];
1267 if (column)
1268 column->next = &columns[i];
1269 column = &columns[i];
1271 code = parse_view_column_type(column, &arg);
1272 if (code != SUCCESS)
1273 break;
1275 if (!(view->ops->column_bits & (1 << column->type)))
1276 return error("The %s view does not support %s column", view->name,
1277 view_column_name(column->type));
1279 if ((column->type == VIEW_COLUMN_TEXT ||
1280 column->type == VIEW_COLUMN_COMMIT_TITLE) &&
1281 i + 1 < size)
1282 return error("The %s column must always be last",
1283 view_column_name(column->type));
1285 code = parse_view_column_config_exprs(column, arg);
1286 column->prev_opt = column->opt;
1289 if (code == SUCCESS) {
1290 free(view->columns);
1291 view->columns = columns;
1292 view->sort.current = view->columns;
1293 *column_ref = columns;
1294 } else {
1295 free(columns);
1298 return code;
1301 static enum status_code
1302 format_view_column_options(struct option_info options[], size_t options_size, char buf[], size_t bufsize)
1304 char name[SIZEOF_STR];
1305 char value[SIZEOF_STR];
1306 size_t bufpos = 0;
1307 const char *sep = ":";
1308 int i;
1310 buf[0] = 0;
1312 for (i = 0; i < options_size; i++) {
1313 struct option_info *option = &options[i];
1314 const char *assign = "=";
1316 if (!enum_name_copy(name, sizeof(name), option->name)
1317 || !format_option_value(option, value, sizeof(value)))
1318 return error("No space left in buffer");
1320 if (!strcmp(name, "display")) {
1321 name[0] = 0;
1322 assign = "";
1326 if (!strcmp(option->type, "bool") && !strcmp(value, "yes")) {
1327 if (!*name) {
1328 sep = ":yes,";
1329 continue;
1332 /* For non-display boolean options 'yes' is implied. */
1333 #if 0
1334 value[0] = 0;
1335 assign = "";
1336 #endif
1339 if (!strcmp(option->type, "int") && !strcmp(value, "0"))
1340 continue;
1342 if (!string_nformat(buf, bufsize, &bufpos, "%s%s%s%s",
1343 sep, name, assign, value))
1344 return error("No space left in buffer");
1346 sep = ",";
1349 return SUCCESS;
1352 static enum status_code
1353 format_view_column(struct view_column *column, char buf[], size_t bufsize)
1355 #define FORMAT_COLUMN_OPTION_INFO(name, type, flags) \
1356 { #name, STRING_SIZE(#name), #type, &opt->name, flags },
1358 #define FORMAT_COLUMN_OPTIONS_PARSE(col_name, id, options) \
1359 if (column->type == VIEW_COLUMN_##id) { \
1360 struct col_name##_options *opt = &column->opt.col_name; \
1361 struct option_info info[] = { \
1362 options(FORMAT_COLUMN_OPTION_INFO) \
1363 }; \
1365 return format_view_column_options(info, ARRAY_SIZE(info), buf, bufsize); \
1368 COLUMN_OPTIONS(FORMAT_COLUMN_OPTIONS_PARSE);
1370 return error("Unknown view column type: %d", column->type);
1373 enum status_code
1374 format_view_config(struct view_column *column, char buf[], size_t bufsize)
1376 const struct enum_map *map = view_column_type_map;
1377 const char *sep = "";
1378 size_t bufpos = 0;
1379 char type[SIZEOF_STR];
1380 char value[SIZEOF_STR];
1382 for (; column; column = column->next) {
1383 enum status_code code = format_view_column(column, value, sizeof(value));
1385 if (code != SUCCESS)
1386 return code;
1388 if (!enum_name_copy(type, sizeof(type), map->entries[column->type].name)
1389 || !string_nformat(buf, bufsize, &bufpos, "%s%s%s",
1390 sep, type, value))
1391 return error("No space left in buffer");
1393 sep = " ";
1396 return SUCCESS;
1399 struct view_column *
1400 get_view_column(struct view *view, enum view_column_type type)
1402 struct view_column *column;
1404 for (column = view->columns; column; column = column->next)
1405 if (column->type == type)
1406 return column;
1407 return NULL;
1410 bool
1411 view_column_info_update(struct view *view, struct line *line)
1413 struct view_column_data column_data = {0};
1414 struct view_column *column;
1415 bool changed = false;
1417 if (!view->ops->get_column_data(view, line, &column_data))
1418 return false;
1420 for (column = view->columns; column; column = column->next) {
1421 const char *text = view_column_text(view, &column_data, column);
1422 int width = 0;
1424 switch (column->type) {
1425 case VIEW_COLUMN_AUTHOR:
1426 width = column->opt.author.width;
1427 break;
1429 case VIEW_COLUMN_COMMIT_TITLE:
1430 width = column->opt.commit_title.width;
1431 break;
1433 case VIEW_COLUMN_DATE:
1434 width = column->opt.date.width;
1435 break;
1437 case VIEW_COLUMN_FILE_NAME:
1438 width = column->opt.file_name.width;
1439 break;
1441 case VIEW_COLUMN_FILE_SIZE:
1442 width = column->opt.file_size.width;
1443 break;
1445 case VIEW_COLUMN_ID:
1446 width = column->opt.id.width;
1447 if (!width)
1448 width = opt_id_width;
1449 if (!column_data.reflog && !width)
1450 width = 7;
1451 break;
1453 case VIEW_COLUMN_LINE_NUMBER:
1454 if (column_data.line_number)
1455 width = count_digits(*column_data.line_number);
1456 else
1457 width = count_digits(view->lines);
1458 if (width < 3)
1459 width = 3;
1460 break;
1462 case VIEW_COLUMN_MODE:
1463 width = column->opt.mode.width;
1464 break;
1466 case VIEW_COLUMN_REF:
1467 width = column->opt.ref.width;
1468 break;
1470 case VIEW_COLUMN_SECTION:
1471 break;
1473 case VIEW_COLUMN_STATUS:
1474 width = column->opt.status.width;
1475 break;
1477 case VIEW_COLUMN_TEXT:
1478 width = column->opt.text.width;
1479 break;
1482 if (*text && !width)
1483 width = utf8_width(text);
1485 if (width > column->width) {
1486 column->width = width;
1487 changed = true;
1491 if (changed)
1492 view->force_redraw = true;
1493 return changed;
1496 struct line *
1497 find_line_by_type(struct view *view, struct line *line, enum line_type type, int direction)
1499 for (; view_has_line(view, line); line += direction)
1500 if (line->type == type)
1501 return line;
1503 return NULL;
1507 * Line utilities.
1510 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
1512 static inline char *
1513 box_text_offset(struct box *box, size_t cells)
1515 return (char *) &box->cell[cells];
1518 void
1519 box_text_copy(struct box *box, size_t cells, const char *src, size_t srclen)
1521 char *dst = box_text_offset(box, cells);
1523 box->text = dst;
1524 strncpy(dst, src, srclen);
1527 struct line *
1528 add_line_at(struct view *view, unsigned long pos, const void *data, enum line_type type, size_t data_size, bool custom)
1530 struct line *line;
1531 unsigned long lineno;
1533 if (!realloc_lines(&view->line, view->lines, 1))
1534 return NULL;
1536 if (data_size) {
1537 void *alloc_data = calloc(1, data_size);
1539 if (!alloc_data)
1540 return NULL;
1542 if (data)
1543 memcpy(alloc_data, data, data_size);
1544 data = alloc_data;
1547 if (pos < view->lines) {
1548 view->lines++;
1549 line = view->line + pos;
1550 lineno = line->lineno;
1552 memmove(line + 1, line, (view->lines - pos) * sizeof(*view->line));
1553 while (pos < view->lines) {
1554 view->line[pos].lineno++;
1555 view->line[pos++].dirty = 1;
1557 } else {
1558 line = &view->line[view->lines++];
1559 lineno = view->lines - view->custom_lines;
1562 memset(line, 0, sizeof(*line));
1563 line->type = type;
1564 line->data = (void *) data;
1565 line->dirty = 1;
1567 if (custom)
1568 view->custom_lines++;
1569 else
1570 line->lineno = lineno;
1572 return line;
1575 struct line *
1576 add_line(struct view *view, const void *data, enum line_type type, size_t data_size, bool custom)
1578 return add_line_at(view, view->lines, data, type, data_size, custom);
1581 struct line *
1582 add_line_alloc_(struct view *view, void **ptr, enum line_type type, size_t data_size, bool custom)
1584 struct line *line = add_line(view, NULL, type, data_size, custom);
1586 if (line)
1587 *ptr = line->data;
1588 return line;
1591 struct line *
1592 add_line_nodata(struct view *view, enum line_type type)
1594 return add_line(view, NULL, type, 0, false);
1597 struct line *
1598 add_line_text_at_(struct view *view, unsigned long pos, const char *text, size_t textlen, enum line_type type, size_t cells, bool custom)
1600 struct box *box;
1601 struct line *line = add_line_at(view, pos, NULL, type, box_sizeof(NULL, cells, textlen), custom);
1603 if (!line)
1604 return NULL;
1606 box = line->data;
1607 box->cell[box->cells].length = textlen;
1608 box->cell[box->cells++].type = type;
1609 box_text_copy(box, cells, text, textlen);
1611 if (view->ops->column_bits)
1612 view_column_info_update(view, line);
1613 return line;
1616 struct line *
1617 add_line_text_at(struct view *view, unsigned long pos, const char *text, enum line_type type, size_t cells)
1619 return add_line_text_at_(view, pos, text, strlen(text), type, cells, false);
1622 struct line *
1623 add_line_text(struct view *view, const char *text, enum line_type type)
1625 return add_line_text_at(view, view->lines, text, type, 1);
1628 struct line * PRINTF_LIKE(3, 4)
1629 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
1631 char buf[SIZEOF_STR];
1632 int retval;
1634 FORMAT_BUFFER(buf, sizeof(buf), fmt, retval, false);
1635 return retval >= 0 ? add_line_text(view, buf, type) : NULL;
1638 bool
1639 append_line_format(struct view *view, struct line *line, const char *fmt, ...)
1641 struct box *box = line->data;
1642 size_t textlen = box_text_length(box);
1643 int fmtlen, retval;
1644 va_list args;
1645 char *text;
1647 va_start(args, fmt);
1648 fmtlen = vsnprintf(NULL, 0, fmt, args);
1649 va_end(args);
1651 if (fmtlen <= 0)
1652 return false;
1654 box = realloc(box, box_sizeof(box, 0, fmtlen));
1655 if (!box)
1656 return false;
1658 box->text = text = box_text_offset(box, box->cells);
1659 FORMAT_BUFFER(text + textlen, fmtlen + 1, fmt, retval, false);
1660 if (retval < 0)
1661 text[textlen] = 0;
1663 box->cell[box->cells - 1].length += fmtlen;
1664 line->data = box;
1665 line->dirty = true;
1667 if (view->ops->column_bits)
1668 view_column_info_update(view, line);
1670 return true;
1674 * Global view state.
1677 /* Included last to not pollute the rest of the file. */
1678 #include "tig/main.h"
1679 #include "tig/diff.h"
1680 #include "tig/log.h"
1681 #include "tig/tree.h"
1682 #include "tig/blob.h"
1683 #include "tig/blame.h"
1684 #include "tig/refs.h"
1685 #include "tig/status.h"
1686 #include "tig/stage.h"
1687 #include "tig/stash.h"
1688 #include "tig/grep.h"
1689 #include "tig/pager.h"
1690 #include "tig/help.h"
1692 static struct view *views[] = {
1693 #define VIEW_DATA(id, name) &name##_view
1694 VIEW_INFO(VIEW_DATA)
1697 struct view *
1698 get_view(int i)
1700 return 0 <= i && i < ARRAY_SIZE(views) ? views[i] : NULL;
1703 /* vim: set ts=8 sw=8 noexpandtab: */