Fix tests that passed expected input to assert_equals
[tig.git] / src / tig.c
blob2f4dee25425c34b8563a25e8185511e380f7a41a
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 #define WARN_MISSING_CURSES_CONFIGURATION
16 #include "tig/tig.h"
17 #include "tig/types.h"
18 #include "tig/util.h"
19 #include "tig/parse.h"
20 #include "tig/io.h"
21 #include "tig/argv.h"
22 #include "tig/refdb.h"
23 #include "tig/watch.h"
24 #include "tig/graph.h"
25 #include "tig/git.h"
26 #include "tig/request.h"
27 #include "tig/line.h"
28 #include "tig/keys.h"
29 #include "tig/view.h"
30 #include "tig/search.h"
31 #include "tig/repo.h"
32 #include "tig/options.h"
33 #include "tig/draw.h"
34 #include "tig/display.h"
35 #include "tig/prompt.h"
37 /* Views. */
38 #include "tig/blame.h"
39 #include "tig/blob.h"
40 #include "tig/diff.h"
41 #include "tig/grep.h"
42 #include "tig/help.h"
43 #include "tig/log.h"
44 #include "tig/main.h"
45 #include "tig/pager.h"
46 #include "tig/refs.h"
47 #include "tig/stage.h"
48 #include "tig/stash.h"
49 #include "tig/status.h"
50 #include "tig/tree.h"
52 static bool
53 forward_request_to_child(struct view *child, enum request request)
55 return displayed_views() == 2 && view_is_displayed(child) &&
56 !strcmp(child->vid, child->ops->id);
59 static enum request
60 view_request(struct view *view, enum request request)
62 if (!view || !view->lines)
63 return request;
65 if (request == REQ_ENTER && !opt_focus_child &&
66 view_has_flags(view, VIEW_SEND_CHILD_ENTER)) {
67 struct view *child = display[1];
69 if (forward_request_to_child(child, request)) {
70 view_request(child, request);
71 return REQ_NONE;
75 if (request == REQ_REFRESH && !view_can_refresh(view)) {
76 report("This view can not be refreshed");
77 return REQ_NONE;
80 return view->ops->request(view, request, &view->line[view->pos.lineno]);
84 * Option management
87 #define TOGGLE_MENU_INFO(_) \
88 _('.', "line numbers", "line-number"), \
89 _('D', "dates", "date"), \
90 _('A', "author", "author"), \
91 _('~', "graphics", "line-graphics"), \
92 _('g', "revision graph", "commit-title-graph"), \
93 _('#', "file names", "file-name"), \
94 _('*', "file sizes", "file-size"), \
95 _('W', "space changes", "ignore-space"), \
96 _('l', "commit order", "commit-order"), \
97 _('F', "reference display", "commit-title-refs"), \
98 _('C', "local change display", "show-changes"), \
99 _('X', "commit ID display", "id"), \
100 _('%', "file filtering", "file-filter"), \
101 _('$', "commit title overflow display", "commit-title-overflow"), \
102 _('d', "untracked directory info", "status-untracked-dirs"), \
103 _('|', "view split", "vertical-split"), \
105 static void
106 toggle_option(struct view *view)
108 const struct menu_item menu[] = {
109 #define DEFINE_TOGGLE_MENU(key, help, name) { key, help, name }
110 TOGGLE_MENU_INFO(DEFINE_TOGGLE_MENU)
111 { 0 }
113 const char *toggle_argv[] = { "toggle", NULL, NULL };
114 int i = 0;
116 if (!prompt_menu("Toggle option", menu, &i))
117 return;
119 toggle_argv[1] = menu[i].data;
120 run_prompt_command(view, toggle_argv);
125 * View opening
128 static enum request
129 open_run_request(struct view *view, enum request request)
131 struct run_request *req = get_run_request(request);
133 if (!req) {
134 report("Unknown run request");
135 return REQ_NONE;
138 return exec_run_request(view, req);
142 * User request switch noodle
145 static bool
146 view_driver(struct view *view, enum request request)
148 int i;
150 if (request == REQ_NONE)
151 return true;
153 if (request >= REQ_RUN_REQUESTS) {
154 request = open_run_request(view, request);
156 // exit quickly rather than going through view_request and back
157 if (request == REQ_QUIT)
158 return false;
161 request = view_request(view, request);
162 if (request == REQ_NONE)
163 return true;
165 switch (request) {
166 case REQ_MOVE_UP:
167 case REQ_MOVE_DOWN:
168 case REQ_MOVE_PAGE_UP:
169 case REQ_MOVE_PAGE_DOWN:
170 case REQ_MOVE_HALF_PAGE_UP:
171 case REQ_MOVE_HALF_PAGE_DOWN:
172 case REQ_MOVE_FIRST_LINE:
173 case REQ_MOVE_LAST_LINE:
174 move_view(view, request);
175 break;
177 case REQ_SCROLL_FIRST_COL:
178 case REQ_SCROLL_LEFT:
179 case REQ_SCROLL_RIGHT:
180 case REQ_SCROLL_LINE_DOWN:
181 case REQ_SCROLL_LINE_UP:
182 case REQ_SCROLL_PAGE_DOWN:
183 case REQ_SCROLL_PAGE_UP:
184 case REQ_SCROLL_WHEEL_DOWN:
185 case REQ_SCROLL_WHEEL_UP:
186 scroll_view(view, request);
187 break;
189 case REQ_VIEW_GREP:
190 open_grep_view(view);
191 break;
193 case REQ_VIEW_MAIN:
194 open_main_view(view, OPEN_DEFAULT);
195 break;
196 case REQ_VIEW_DIFF:
197 open_diff_view(view, OPEN_DEFAULT);
198 break;
199 case REQ_VIEW_LOG:
200 open_log_view(view, OPEN_DEFAULT);
201 break;
202 case REQ_VIEW_TREE:
203 open_tree_view(view, OPEN_DEFAULT);
204 break;
205 case REQ_VIEW_HELP:
206 open_help_view(view, OPEN_DEFAULT);
207 break;
208 case REQ_VIEW_REFS:
209 open_refs_view(view, OPEN_DEFAULT);
210 break;
211 case REQ_VIEW_BLAME:
212 open_blame_view(view, OPEN_DEFAULT);
213 break;
214 case REQ_VIEW_BLOB:
215 open_blob_view(view, OPEN_DEFAULT);
216 break;
217 case REQ_VIEW_STATUS:
218 open_status_view(view, OPEN_DEFAULT);
219 break;
220 case REQ_VIEW_STAGE:
221 open_stage_view(view, NULL, 0, OPEN_DEFAULT);
222 break;
223 case REQ_VIEW_PAGER:
224 open_pager_view(view, OPEN_DEFAULT);
225 break;
226 case REQ_VIEW_STASH:
227 open_stash_view(view, OPEN_DEFAULT);
228 break;
230 case REQ_NEXT:
231 case REQ_PREVIOUS:
232 if (view->parent) {
233 int line;
235 view = view->parent;
236 line = view->pos.lineno;
237 view_request(view, request);
238 move_view(view, request);
239 if (view_is_displayed(view))
240 update_view_title(view);
241 if (line != view->pos.lineno)
242 view_request(view, REQ_ENTER);
243 } else {
244 move_view(view, request);
246 break;
248 case REQ_VIEW_NEXT:
250 int nviews = displayed_views();
251 int next_view = nviews ? (current_view + 1) % nviews : current_view;
253 if (next_view == current_view) {
254 report("Only one view is displayed");
255 break;
258 current_view = next_view;
259 /* Blur out the title of the previous view. */
260 update_view_title(view);
261 report_clear();
262 break;
264 case REQ_REFRESH:
265 report("Refreshing is not supported by the %s view", view->name);
266 break;
268 case REQ_PARENT:
269 report("Moving to parent is not supported by the %s view", view->name);
270 break;
272 case REQ_BACK:
273 report("Going back is not supported by the %s view", view->name);
274 break;
276 case REQ_MAXIMIZE:
277 if (displayed_views() == 2)
278 maximize_view(view, true);
279 break;
281 case REQ_OPTIONS:
282 toggle_option(view);
283 break;
285 case REQ_SEARCH:
286 case REQ_SEARCH_BACK:
287 search_view(view, request);
288 break;
290 case REQ_FIND_NEXT:
291 case REQ_FIND_PREV:
292 find_next(view, request);
293 break;
295 case REQ_STOP_LOADING:
296 foreach_view(view, i) {
297 if (view->pipe)
298 report("Stopped loading the %s view", view->name),
299 end_update(view, true);
301 break;
303 case REQ_SHOW_VERSION:
304 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
305 return true;
307 case REQ_SCREEN_REDRAW:
308 redraw_display(true);
309 break;
311 case REQ_EDIT:
312 report("Nothing to edit");
313 break;
315 case REQ_ENTER:
316 report("Nothing to enter");
317 break;
319 case REQ_VIEW_CLOSE:
320 /* XXX: Mark closed views by letting view->prev point to the
321 * view itself. Parents to closed view should never be
322 * followed. */
323 if (view->prev && view->prev != view) {
324 maximize_view(view->prev, true);
325 view->prev = view;
326 watch_unregister(&view->watch);
327 view->parent = NULL;
328 break;
330 /* Fall-through */
331 case REQ_QUIT:
332 return false;
334 default:
335 report("Unknown key, press %s for help",
336 get_view_key(view, REQ_VIEW_HELP));
337 return true;
340 return true;
344 * Main
347 static const char usage_string[] =
348 "tig " TIG_VERSION " (" __DATE__ ")\n"
349 "\n"
350 "Usage: tig [options] [revs] [--] [paths]\n"
351 " or: tig log [options] [revs] [--] [paths]\n"
352 " or: tig show [options] [revs] [--] [paths]\n"
353 " or: tig blame [options] [rev] [--] path\n"
354 " or: tig grep [options] [pattern]\n"
355 " or: tig refs\n"
356 " or: tig stash\n"
357 " or: tig status\n"
358 " or: tig < [git command output]\n"
359 "\n"
360 "Options:\n"
361 " +<number> Select line <number> in the first view\n"
362 " -v, --version Show version and exit\n"
363 " -h, --help Show help message and exit";
365 void
366 usage(const char *message)
368 die("%s\n\n%s", message, usage_string);
371 static enum status_code
372 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen, void *data)
374 const char ***filter_args = data;
376 return argv_append(filter_args, name) ? SUCCESS : ERROR_OUT_OF_MEMORY;
379 static void
380 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
382 const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
383 const char **all_argv = NULL;
385 if (!argv_append_array(&all_argv, rev_parse_argv) ||
386 !argv_append_array(&all_argv, argv) ||
387 io_run_load(all_argv, "\n", read_filter_args, args) != SUCCESS)
388 die("Failed to split arguments");
389 argv_free(all_argv);
390 free(all_argv);
393 static void
394 filter_options(const char *argv[], bool rev_parse)
396 const char **flags = NULL;
397 int next, flags_pos;
399 update_options_from_argv(argv);
401 if (!rev_parse) {
402 opt_cmdline_args = argv;
403 return;
406 filter_rev_parse(&opt_file_args, "--no-revs", "--no-flags", argv);
407 filter_rev_parse(&flags, "--flags", "--no-revs", argv);
409 if (flags) {
410 for (next = flags_pos = 0; flags && flags[next]; next++) {
411 const char *flag = flags[next];
413 if (argv_parse_rev_flag(flag, NULL))
414 argv_append(&opt_rev_args, flag);
415 else
416 flags[flags_pos++] = flag;
419 flags[flags_pos] = NULL;
421 opt_cmdline_args = flags;
424 for (next = flags_pos = 0; argv[next]; next++) {
425 const char *arg = argv[next];
427 if (!strcmp(arg, "--all"))
428 argv_append(&opt_rev_args, arg);
429 else
430 argv[flags_pos++] = arg;
433 argv[flags_pos] = NULL;
435 filter_rev_parse(&opt_rev_args, "--symbolic", "--revs-only", argv);
438 static enum request
439 parse_options(int argc, const char *argv[], bool pager_mode)
441 enum request request;
442 const char *subcommand;
443 bool seen_dashdash = false;
444 bool rev_parse = true;
445 const char **filter_argv = NULL;
446 int i;
448 request = pager_mode ? REQ_VIEW_PAGER : REQ_VIEW_MAIN;
450 if (argc <= 1)
451 return request;
453 subcommand = argv[1];
454 if (!strcmp(subcommand, "status")) {
455 request = REQ_VIEW_STATUS;
457 } else if (!strcmp(subcommand, "blame")) {
458 request = REQ_VIEW_BLAME;
460 } else if (!strcmp(subcommand, "grep")) {
461 request = REQ_VIEW_GREP;
462 rev_parse = false;
464 } else if (!strcmp(subcommand, "show")) {
465 request = REQ_VIEW_DIFF;
467 } else if (!strcmp(subcommand, "log")) {
468 request = REQ_VIEW_LOG;
470 } else if (!strcmp(subcommand, "stash")) {
471 request = REQ_VIEW_STASH;
473 } else if (!strcmp(subcommand, "refs")) {
474 request = REQ_VIEW_REFS;
476 } else {
477 subcommand = NULL;
480 for (i = 1 + !!subcommand; i < argc; i++) {
481 const char *opt = argv[i];
483 // stop parsing our options after -- and let rev-parse handle the rest
484 if (!seen_dashdash) {
485 if (!strcmp(opt, "--")) {
486 seen_dashdash = true;
487 continue;
489 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
490 printf("tig version %s\n", TIG_VERSION);
491 exit(EXIT_SUCCESS);
493 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
494 printf("%s\n", usage_string);
495 exit(EXIT_SUCCESS);
497 } else if (strlen(opt) >= 2 && *opt == '+' && string_isnumber(opt + 1)) {
498 int lineno = atoi(opt + 1);
500 argv_env.goto_lineno = lineno > 0 ? lineno - 1 : 0;
501 continue;
506 if (!argv_append(&filter_argv, opt))
507 die("command too long");
510 if (filter_argv)
511 filter_options(filter_argv, rev_parse);
513 return request;
516 static enum request
517 open_pager_mode(enum request request)
519 if (request == REQ_VIEW_PAGER) {
520 /* Detect if the user requested the main view. */
521 if (argv_contains(opt_rev_args, "--stdin")) {
522 open_main_view(NULL, OPEN_FORWARD_STDIN);
523 } else if (argv_contains(opt_cmdline_args, "--pretty=raw")) {
524 open_main_view(NULL, OPEN_STDIN);
525 } else {
526 open_pager_view(NULL, OPEN_STDIN);
529 } else if (request == REQ_VIEW_DIFF) {
530 if (argv_contains(opt_rev_args, "--stdin"))
531 open_diff_view(NULL, OPEN_FORWARD_STDIN);
532 else
533 open_diff_view(NULL, OPEN_STDIN);
535 } else {
536 close(STDIN_FILENO);
537 report("Ignoring stdin.");
538 return request;
541 return REQ_NONE;
544 #ifdef NCURSES_MOUSE_VERSION
545 static struct view *
546 find_clicked_view(MEVENT *event)
548 struct view *view;
549 int i;
551 foreach_displayed_view (view, i) {
552 int beg_y = 0, beg_x = 0;
554 getbegyx(view->win, beg_y, beg_x);
556 if (beg_y <= event->y && event->y < beg_y + view->height
557 && beg_x <= event->x && event->x < beg_x + view->width) {
558 if (i != current_view) {
559 current_view = i;
561 return view;
565 return NULL;
568 static enum request
569 handle_mouse_event(void)
571 MEVENT event;
572 struct view *view;
574 if (getmouse(&event) != OK)
575 return REQ_NONE;
577 view = find_clicked_view(&event);
578 if (!view)
579 return REQ_NONE;
581 if (event.bstate & BUTTON2_PRESSED)
582 return REQ_SCROLL_WHEEL_DOWN;
584 if (event.bstate & BUTTON4_PRESSED)
585 return REQ_SCROLL_WHEEL_UP;
587 if (event.bstate & BUTTON1_PRESSED) {
588 if (event.y == view->pos.lineno - view->pos.offset) {
589 /* Click is on the same line, perform an "ENTER" */
590 return REQ_ENTER;
592 } else {
593 int y = getbegy(view->win);
594 unsigned long lineno = (event.y - y) + view->pos.offset;
596 select_view_line(view, lineno);
597 update_view_title(view);
598 report_clear();
602 return REQ_NONE;
604 #endif
606 struct key_combo {
607 enum request request;
608 struct keymap *keymap;
609 size_t bufpos;
610 size_t keys;
611 struct key key[16];
614 static enum input_status
615 key_combo_handler(struct input *input, struct key *key)
617 struct key_combo *combo = input->data;
618 int matches = 0;
620 #ifdef NCURSES_MOUSE_VERSION
621 if (key_to_value(key) == KEY_MOUSE) {
622 combo->request = handle_mouse_event();
623 return INPUT_STOP;
625 #endif
627 if (combo->keys && key_to_value(key) == KEY_ESC)
628 return INPUT_CANCEL;
630 string_format_from(input->buf, &combo->bufpos, "%s%s",
631 combo->bufpos ? " " : "Keys: ", get_key_name(key, 1, false));
632 combo->key[combo->keys++] = *key;
633 combo->request = get_keybinding(combo->keymap, combo->key, combo->keys, &matches);
635 if (combo->request == REQ_UNKNOWN)
636 return matches > 0 ? INPUT_OK : INPUT_STOP;
637 return INPUT_STOP;
640 enum request
641 read_key_combo(struct keymap *keymap)
643 struct key_combo combo = { REQ_NONE, keymap, 0 };
644 char *value = read_prompt_incremental("", false, false, key_combo_handler, &combo);
646 return value ? combo.request : REQ_NONE;
649 static inline void
650 die_if_failed(enum status_code code, const char *msg)
652 if (code != SUCCESS)
653 die("%s: %s", msg, get_status_message(code));
657 main(int argc, const char *argv[])
659 const char *codeset = ENCODING_UTF8;
660 bool pager_mode = !isatty(STDIN_FILENO);
661 enum request request = parse_options(argc, argv, pager_mode);
662 struct view *view;
664 prompt_init();
666 if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
667 die("Failed to setup signal handler");
669 if (setlocale(LC_ALL, "")) {
670 codeset = nl_langinfo(CODESET);
673 die_if_failed(load_repo_info(), "Failed to load repo info.");
674 die_if_failed(load_options(), "Failed to load user config.");
675 die_if_failed(load_git_config(), "Failed to load repo config.");
677 /* Require a git repository unless when running in pager mode. */
678 if (!repo.git_dir[0] && request != REQ_VIEW_PAGER)
679 die("Not a git repository");
681 if (codeset && strcmp(codeset, ENCODING_UTF8)) {
682 char translit[SIZEOF_STR];
684 if (string_format(translit, "%s%s", codeset, ICONV_TRANSLIT))
685 opt_iconv_out = iconv_open(translit, ENCODING_UTF8);
686 else
687 opt_iconv_out = iconv_open(codeset, ENCODING_UTF8);
688 if (opt_iconv_out == ICONV_NONE)
689 die("Failed to initialize character set conversion");
692 die_if_failed(load_refs(false), "Failed to load refs.");
694 init_display();
696 if (pager_mode)
697 request = open_pager_mode(request);
699 if (getenv("TIG_SCRIPT")) {
700 const char *script_command[] = { "script", getenv("TIG_SCRIPT"), NULL };
702 run_prompt_command(NULL, script_command);
705 while (view_driver(display[current_view], request)) {
706 view = display[current_view];
707 request = read_key_combo(view->keymap);
709 /* Some low-level request handling. This keeps access to
710 * status_win restricted. */
711 switch (request) {
712 case REQ_UNKNOWN:
713 report("Unknown key, press %s for help",
714 get_view_key(view, REQ_VIEW_HELP));
715 request = REQ_NONE;
716 break;
717 case REQ_PROMPT:
718 request = open_prompt(view);
719 break;
720 default:
721 break;
725 exit(EXIT_SUCCESS);
727 return 0;
730 /* vim: set ts=8 sw=8 noexpandtab: */