Add option to install Tig with Homebrew
[tig.git] / src / tig.c
blobf9771e12ce0bc24db5f89b7582dee5bbdb87e18c
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 #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/repo.h"
31 #include "tig/options.h"
32 #include "tig/draw.h"
33 #include "tig/display.h"
34 #include "tig/prompt.h"
36 /* Views. */
37 #include "tig/blame.h"
38 #include "tig/blob.h"
39 #include "tig/diff.h"
40 #include "tig/grep.h"
41 #include "tig/help.h"
42 #include "tig/log.h"
43 #include "tig/main.h"
44 #include "tig/pager.h"
45 #include "tig/refs.h"
46 #include "tig/stage.h"
47 #include "tig/stash.h"
48 #include "tig/status.h"
49 #include "tig/tree.h"
51 static bool
52 forward_request_to_child(struct view *child, enum request request)
54 return displayed_views() == 2 && view_is_displayed(child) &&
55 !strcmp(child->vid, child->ops->id);
58 static enum request
59 view_request(struct view *view, enum request request)
61 if (!view || !view->lines)
62 return request;
64 if (request == REQ_ENTER && !opt_focus_child &&
65 view_has_flags(view, VIEW_SEND_CHILD_ENTER)) {
66 struct view *child = display[1];
68 if (forward_request_to_child(child, request)) {
69 view_request(child, request);
70 return REQ_NONE;
74 if (request == REQ_REFRESH && !view_can_refresh(view)) {
75 report("This view can not be refreshed");
76 return REQ_NONE;
79 return view->ops->request(view, request, &view->line[view->pos.lineno]);
83 * Option management
86 #define TOGGLE_MENU_INFO(_) \
87 _('.', "line numbers", "line-number"), \
88 _('D', "dates", "date"), \
89 _('A', "author", "author"), \
90 _('~', "graphics", "line-graphics"), \
91 _('g', "revision graph", "commit-title-graph"), \
92 _('#', "file names", "file-name"), \
93 _('*', "file sizes", "file-size"), \
94 _('W', "space changes", "ignore-space"), \
95 _('l', "commit order", "commit-order"), \
96 _('F', "reference display", "commit-title-refs"), \
97 _('C', "local change display", "show-changes"), \
98 _('X', "commit ID display", "id"), \
99 _('%', "file filtering", "file-filter"), \
100 _('$', "commit title overflow display", "commit-title-overflow"), \
101 _('d', "untracked directory info", "status-untracked-dirs"), \
102 _('|', "view split", "vertical-split"), \
104 static void
105 toggle_option(struct view *view)
107 const struct menu_item menu[] = {
108 #define DEFINE_TOGGLE_MENU(key, help, name) { key, help, name }
109 TOGGLE_MENU_INFO(DEFINE_TOGGLE_MENU)
110 { 0 }
112 const char *toggle_argv[] = { "toggle", NULL, NULL };
113 int i = 0;
115 if (!prompt_menu("Toggle option", menu, &i))
116 return;
118 toggle_argv[1] = menu[i].data;
119 run_prompt_command(view, toggle_argv);
124 * View opening
127 static enum request
128 open_run_request(struct view *view, enum request request)
130 struct run_request *req = get_run_request(request);
131 const char **argv = NULL;
132 bool confirmed = FALSE;
134 request = REQ_NONE;
136 if (!req) {
137 report("Unknown run request");
138 return request;
141 if (!argv_format(view->env, &argv, req->argv, FALSE, TRUE)) {
142 report("Failed to format arguments");
143 return REQ_NONE;
146 if (req->flags.internal) {
147 request = run_prompt_command(view, argv);
149 } else {
150 confirmed = !req->flags.confirm;
152 if (req->flags.confirm) {
153 char cmd[SIZEOF_STR], prompt[SIZEOF_STR];
154 const char *and_exit = req->flags.exit ? " and exit" : "";
156 if (argv_to_string(argv, cmd, sizeof(cmd), " ") &&
157 string_format(prompt, "Run `%s`%s?", cmd, and_exit) &&
158 prompt_yesno(prompt)) {
159 confirmed = TRUE;
163 if (confirmed && argv_remove_quotes(argv))
164 open_external_viewer(argv, NULL, req->flags.silent,
165 !req->flags.exit, FALSE, "");
168 if (argv)
169 argv_free(argv);
170 free(argv);
172 if (request == REQ_NONE) {
173 if (req->flags.confirm && !confirmed)
174 request = REQ_NONE;
176 else if (req->flags.exit)
177 request = REQ_QUIT;
179 else if (!req->flags.internal && watch_dirty(&view->watch))
180 request = REQ_REFRESH;
183 return request;
187 * User request switch noodle
190 static int
191 view_driver(struct view *view, enum request request)
193 int i;
195 if (request == REQ_NONE)
196 return TRUE;
198 if (request >= REQ_RUN_REQUESTS) {
199 request = open_run_request(view, request);
201 // exit quickly rather than going through view_request and back
202 if (request == REQ_QUIT)
203 return FALSE;
206 request = view_request(view, request);
207 if (request == REQ_NONE)
208 return TRUE;
210 switch (request) {
211 case REQ_MOVE_UP:
212 case REQ_MOVE_DOWN:
213 case REQ_MOVE_PAGE_UP:
214 case REQ_MOVE_PAGE_DOWN:
215 case REQ_MOVE_HALF_PAGE_UP:
216 case REQ_MOVE_HALF_PAGE_DOWN:
217 case REQ_MOVE_FIRST_LINE:
218 case REQ_MOVE_LAST_LINE:
219 move_view(view, request);
220 break;
222 case REQ_SCROLL_FIRST_COL:
223 case REQ_SCROLL_LEFT:
224 case REQ_SCROLL_RIGHT:
225 case REQ_SCROLL_LINE_DOWN:
226 case REQ_SCROLL_LINE_UP:
227 case REQ_SCROLL_PAGE_DOWN:
228 case REQ_SCROLL_PAGE_UP:
229 case REQ_SCROLL_WHEEL_DOWN:
230 case REQ_SCROLL_WHEEL_UP:
231 scroll_view(view, request);
232 break;
234 case REQ_VIEW_GREP:
235 open_grep_view(view);
236 break;
238 case REQ_VIEW_MAIN:
239 open_main_view(view, OPEN_DEFAULT);
240 break;
241 case REQ_VIEW_DIFF:
242 open_diff_view(view, OPEN_DEFAULT);
243 break;
244 case REQ_VIEW_LOG:
245 open_log_view(view, OPEN_DEFAULT);
246 break;
247 case REQ_VIEW_TREE:
248 open_tree_view(view, OPEN_DEFAULT);
249 break;
250 case REQ_VIEW_HELP:
251 open_help_view(view, OPEN_DEFAULT);
252 break;
253 case REQ_VIEW_REFS:
254 open_refs_view(view, OPEN_DEFAULT);
255 break;
256 case REQ_VIEW_BLAME:
257 open_blame_view(view, OPEN_DEFAULT);
258 break;
259 case REQ_VIEW_BLOB:
260 open_blob_view(view, OPEN_DEFAULT);
261 break;
262 case REQ_VIEW_STATUS:
263 open_status_view(view, OPEN_DEFAULT);
264 break;
265 case REQ_VIEW_STAGE:
266 open_stage_view(view, NULL, 0, OPEN_DEFAULT);
267 break;
268 case REQ_VIEW_PAGER:
269 open_pager_view(view, OPEN_DEFAULT);
270 break;
271 case REQ_VIEW_STASH:
272 open_stash_view(view, OPEN_DEFAULT);
273 break;
275 case REQ_NEXT:
276 case REQ_PREVIOUS:
277 if (view->parent) {
278 int line;
280 view = view->parent;
281 line = view->pos.lineno;
282 view_request(view, request);
283 move_view(view, request);
284 if (view_is_displayed(view))
285 update_view_title(view);
286 if (line != view->pos.lineno)
287 view_request(view, REQ_ENTER);
288 } else {
289 move_view(view, request);
291 break;
293 case REQ_VIEW_NEXT:
295 int nviews = displayed_views();
296 int next_view = nviews ? (current_view + 1) % nviews : current_view;
298 if (next_view == current_view) {
299 report("Only one view is displayed");
300 break;
303 current_view = next_view;
304 /* Blur out the title of the previous view. */
305 update_view_title(view);
306 report_clear();
307 break;
309 case REQ_REFRESH:
310 report("Refreshing is not supported by the %s view", view->name);
311 break;
313 case REQ_PARENT:
314 report("Moving to parent is not supported by the the %s view", view->name);
315 break;
317 case REQ_BACK:
318 report("Going back is not supported for by %s view", view->name);
319 break;
321 case REQ_MAXIMIZE:
322 if (displayed_views() == 2)
323 maximize_view(view, TRUE);
324 break;
326 case REQ_OPTIONS:
327 toggle_option(view);
328 break;
330 case REQ_SEARCH:
331 case REQ_SEARCH_BACK:
332 search_view(view, request);
333 break;
335 case REQ_FIND_NEXT:
336 case REQ_FIND_PREV:
337 find_next(view, request);
338 break;
340 case REQ_STOP_LOADING:
341 foreach_view(view, i) {
342 if (view->pipe)
343 report("Stopped loading the %s view", view->name),
344 end_update(view, TRUE);
346 break;
348 case REQ_SHOW_VERSION:
349 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
350 return TRUE;
352 case REQ_SCREEN_REDRAW:
353 redraw_display(TRUE);
354 break;
356 case REQ_EDIT:
357 report("Nothing to edit");
358 break;
360 case REQ_ENTER:
361 report("Nothing to enter");
362 break;
364 case REQ_VIEW_CLOSE:
365 /* XXX: Mark closed views by letting view->prev point to the
366 * view itself. Parents to closed view should never be
367 * followed. */
368 if (view->prev && view->prev != view) {
369 maximize_view(view->prev, TRUE);
370 view->prev = view;
371 watch_unregister(&view->watch);
372 break;
374 /* Fall-through */
375 case REQ_QUIT:
376 return FALSE;
378 default:
379 report("Unknown key, press %s for help",
380 get_view_key(view, REQ_VIEW_HELP));
381 return TRUE;
384 return TRUE;
388 * Main
391 static const char usage_string[] =
392 "tig " TIG_VERSION " (" __DATE__ ")\n"
393 "\n"
394 "Usage: tig [options] [revs] [--] [paths]\n"
395 " or: tig log [options] [revs] [--] [paths]\n"
396 " or: tig show [options] [revs] [--] [paths]\n"
397 " or: tig blame [options] [rev] [--] path\n"
398 " or: tig grep [options] [pattern]\n"
399 " or: tig refs\n"
400 " or: tig stash\n"
401 " or: tig status\n"
402 " or: tig < [git command output]\n"
403 "\n"
404 "Options:\n"
405 " +<number> Select line <number> in the first view\n"
406 " -v, --version Show version and exit\n"
407 " -h, --help Show help message and exit";
409 void
410 usage(const char *message)
412 die("%s\n\n%s", message, usage_string);
415 static int
416 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen, void *data)
418 const char ***filter_args = data;
420 return argv_append(filter_args, name) ? OK : ERR;
423 static void
424 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
426 const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
427 const char **all_argv = NULL;
429 if (!argv_append_array(&all_argv, rev_parse_argv) ||
430 !argv_append_array(&all_argv, argv) ||
431 io_run_load(all_argv, "\n", read_filter_args, args) == ERR)
432 die("Failed to split arguments");
433 argv_free(all_argv);
434 free(all_argv);
437 static void
438 filter_options(const char *argv[], bool rev_parse)
440 const char **flags = NULL;
441 int next, flags_pos;
443 update_options_from_argv(argv);
445 if (!rev_parse) {
446 opt_cmdline_args = argv;
447 return;
450 filter_rev_parse(&opt_file_args, "--no-revs", "--no-flags", argv);
451 filter_rev_parse(&flags, "--flags", "--no-revs", argv);
453 if (flags) {
454 for (next = flags_pos = 0; flags && flags[next]; next++) {
455 const char *flag = flags[next];
457 if (argv_parse_rev_flag(flag, NULL))
458 argv_append(&opt_rev_args, flag);
459 else
460 flags[flags_pos++] = flag;
463 flags[flags_pos] = NULL;
465 opt_cmdline_args = flags;
468 filter_rev_parse(&opt_rev_args, "--symbolic", "--revs-only", argv);
471 static enum request
472 parse_options(int argc, const char *argv[], bool pager_mode)
474 enum request request;
475 const char *subcommand;
476 bool seen_dashdash = FALSE;
477 bool rev_parse = TRUE;
478 const char **filter_argv = NULL;
479 int i;
481 request = pager_mode ? REQ_VIEW_PAGER : REQ_VIEW_MAIN;
483 if (argc <= 1)
484 return request;
486 subcommand = argv[1];
487 if (!strcmp(subcommand, "status")) {
488 request = REQ_VIEW_STATUS;
490 } else if (!strcmp(subcommand, "blame")) {
491 request = REQ_VIEW_BLAME;
493 } else if (!strcmp(subcommand, "grep")) {
494 request = REQ_VIEW_GREP;
495 rev_parse = FALSE;
497 } else if (!strcmp(subcommand, "show")) {
498 request = REQ_VIEW_DIFF;
500 } else if (!strcmp(subcommand, "log")) {
501 request = REQ_VIEW_LOG;
503 } else if (!strcmp(subcommand, "stash")) {
504 request = REQ_VIEW_STASH;
506 } else if (!strcmp(subcommand, "refs")) {
507 request = REQ_VIEW_REFS;
509 } else {
510 subcommand = NULL;
513 for (i = 1 + !!subcommand; i < argc; i++) {
514 const char *opt = argv[i];
516 // stop parsing our options after -- and let rev-parse handle the rest
517 if (!seen_dashdash) {
518 if (!strcmp(opt, "--")) {
519 seen_dashdash = TRUE;
520 continue;
522 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
523 printf("tig version %s\n", TIG_VERSION);
524 exit(EXIT_SUCCESS);
526 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
527 printf("%s\n", usage_string);
528 exit(EXIT_SUCCESS);
530 } else if (strlen(opt) >= 2 && *opt == '+' && string_isnumber(opt + 1)) {
531 int lineno = atoi(opt + 1);
533 argv_env.lineno = lineno > 0 ? lineno - 1 : 0;
534 continue;
539 if (!argv_append(&filter_argv, opt))
540 die("command too long");
543 if (filter_argv)
544 filter_options(filter_argv, rev_parse);
546 return request;
549 static enum request
550 open_pager_mode(enum request request)
552 if (request == REQ_VIEW_PAGER) {
553 /* Detect if the user requested the main view. */
554 if (argv_contains(opt_rev_args, "--stdin")) {
555 open_main_view(NULL, OPEN_FORWARD_STDIN);
556 } else if (argv_contains(opt_cmdline_args, "--pretty=raw")) {
557 open_main_view(NULL, OPEN_STDIN);
558 } else {
559 open_pager_view(NULL, OPEN_STDIN);
562 } else if (request == REQ_VIEW_DIFF) {
563 if (argv_contains(opt_rev_args, "--stdin"))
564 open_diff_view(NULL, OPEN_FORWARD_STDIN);
565 else
566 open_diff_view(NULL, OPEN_STDIN);
568 } else {
569 close(STDIN_FILENO);
570 report("Ignoring stdin.");
571 return request;
574 return REQ_NONE;
577 #ifdef NCURSES_MOUSE_VERSION
578 static struct view *
579 find_clicked_view(MEVENT *event)
581 struct view *view;
582 int i;
584 foreach_displayed_view (view, i) {
585 int beg_y = 0, beg_x = 0;
587 getbegyx(view->win, beg_y, beg_x);
589 if (beg_y <= event->y && event->y < beg_y + view->height
590 && beg_x <= event->x && event->x < beg_x + view->width) {
591 if (i != current_view) {
592 current_view = i;
594 return view;
598 return NULL;
601 static enum request
602 handle_mouse_event(void)
604 MEVENT event;
605 struct view *view;
607 if (getmouse(&event) != OK)
608 return REQ_NONE;
610 view = find_clicked_view(&event);
611 if (!view)
612 return REQ_NONE;
614 if (event.bstate & BUTTON2_PRESSED)
615 return REQ_SCROLL_WHEEL_DOWN;
617 if (event.bstate & BUTTON4_PRESSED)
618 return REQ_SCROLL_WHEEL_UP;
620 if (event.bstate & BUTTON1_PRESSED) {
621 if (event.y == view->pos.lineno - view->pos.offset) {
622 /* Click is on the same line, perform an "ENTER" */
623 return REQ_ENTER;
625 } else {
626 int y = getbegy(view->win);
627 unsigned long lineno = (event.y - y) + view->pos.offset;
629 select_view_line(view, lineno);
630 update_view_title(view);
631 report_clear();
635 return REQ_NONE;
637 #endif
640 main(int argc, const char *argv[])
642 const char *codeset = ENCODING_UTF8;
643 bool pager_mode = !isatty(STDIN_FILENO);
644 enum request request = parse_options(argc, argv, pager_mode);
645 struct view *view;
647 prompt_init();
649 if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
650 die("Failed to setup signal handler");
652 if (setlocale(LC_ALL, "")) {
653 codeset = nl_langinfo(CODESET);
656 if (load_repo_info() == ERR)
657 die("Failed to load repo info.");
659 if (load_options() == ERR)
660 die("Failed to load user config.");
662 if (load_git_config() == ERR)
663 die("Failed to load repo config.");
665 /* Require a git repository unless when running in pager mode. */
666 if (!repo.git_dir[0] && request != REQ_VIEW_PAGER)
667 die("Not a git repository");
669 if (codeset && strcmp(codeset, ENCODING_UTF8)) {
670 char translit[SIZEOF_STR];
672 if (string_format(translit, "%s%s", codeset, ICONV_TRANSLIT))
673 opt_iconv_out = iconv_open(translit, ENCODING_UTF8);
674 else
675 opt_iconv_out = iconv_open(codeset, ENCODING_UTF8);
676 if (opt_iconv_out == ICONV_NONE)
677 die("Failed to initialize character set conversion");
680 if (load_refs(FALSE) == ERR)
681 die("Failed to load refs.");
683 init_display();
685 if (pager_mode)
686 request = open_pager_mode(request);
688 if (getenv("TIG_SCRIPT")) {
689 const char *script_command[] = { "script", getenv("TIG_SCRIPT"), NULL };
691 run_prompt_command(NULL, script_command);
694 while (view_driver(display[current_view], request)) {
695 struct key key;
696 int key_value = get_input(0, &key, TRUE);
698 #ifdef NCURSES_MOUSE_VERSION
699 if (key_value == KEY_MOUSE) {
700 request = handle_mouse_event();
701 continue;
703 #endif
705 view = display[current_view];
706 request = get_keybinding(view->keymap, &key, 1);
708 /* Some low-level request handling. This keeps access to
709 * status_win restricted. */
710 switch (request) {
711 case REQ_NONE:
712 report("Unknown key, press %s for help",
713 get_view_key(view, REQ_VIEW_HELP));
714 break;
715 case REQ_PROMPT:
716 request = open_prompt(view);
717 break;
718 case REQ_SEARCH:
719 case REQ_SEARCH_BACK:
721 const char *prompt = request == REQ_SEARCH ? "/" : "?";
722 char *search = read_prompt(prompt);
724 if (search)
725 string_ncopy(argv_env.search, search, strlen(search));
726 else if (*argv_env.search)
727 request = request == REQ_SEARCH ?
728 REQ_FIND_NEXT :
729 REQ_FIND_PREV;
730 else
731 request = REQ_NONE;
732 break;
734 default:
735 break;
739 exit(EXIT_SUCCESS);
741 return 0;
744 /* vim: set ts=8 sw=8 noexpandtab: */