view: ensure that width variable is always initialized
[tig.git] / src / tig.c
blob93100d95fb9ccda6dba2b37d66096ce2276bd59a
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/graph.h"
24 #include "tig/git.h"
25 #include "tig/request.h"
26 #include "tig/line.h"
27 #include "tig/keys.h"
28 #include "tig/view.h"
29 #include "tig/repo.h"
30 #include "tig/options.h"
31 #include "tig/draw.h"
32 #include "tig/display.h"
33 #include "tig/prompt.h"
35 /* Views. */
36 #include "tig/blame.h"
37 #include "tig/blob.h"
38 #include "tig/diff.h"
39 #include "tig/grep.h"
40 #include "tig/help.h"
41 #include "tig/log.h"
42 #include "tig/main.h"
43 #include "tig/pager.h"
44 #include "tig/refs.h"
45 #include "tig/stage.h"
46 #include "tig/stash.h"
47 #include "tig/status.h"
48 #include "tig/tree.h"
50 static bool
51 forward_request_to_child(struct view *child, enum request request)
53 return displayed_views() == 2 && view_is_displayed(child) &&
54 !strcmp(child->vid, child->ops->id);
57 static enum request
58 view_request(struct view *view, enum request request)
60 if (!view || !view->lines)
61 return request;
63 if (request == REQ_ENTER && !opt_focus_child &&
64 view_has_flags(view, VIEW_SEND_CHILD_ENTER)) {
65 struct view *child = display[1];
67 if (forward_request_to_child(child, request)) {
68 view_request(child, request);
69 return REQ_NONE;
73 if (request == REQ_REFRESH && view->unrefreshable) {
74 report("This view can not be refreshed");
75 return REQ_NONE;
78 return view->ops->request(view, request, &view->line[view->pos.lineno]);
82 * Option management
85 #define TOGGLE_MENU_INFO(_) \
86 _('.', "line numbers", "show-line-numbers"), \
87 _('D', "dates", "show-date"), \
88 _('A', "author", "show-author"), \
89 _('~', "graphics", "line-graphics"), \
90 _('g', "revision graph", "show-rev-graph"), \
91 _('#', "file names", "show-filename"), \
92 _('*', "file sizes", "show-file-size"), \
93 _('W', "space changes", "ignore-space"), \
94 _('l', "commit order", "commit-order"), \
95 _('F', "reference display", "show-refs"), \
96 _('C', "local change display", "show-changes"), \
97 _('X', "commit ID display", "show-id"), \
98 _('%', "file filtering", "file-filter"), \
99 _('$', "commit title overflow display", "title-overflow"), \
100 _('d', "untracked directory info", "status-untracked-dirs"), \
101 _('|', "view split", "vertical-split"), \
103 static void
104 toggle_option(struct view *view)
106 const struct menu_item menu[] = {
107 #define DEFINE_TOGGLE_MENU(key, help, name) { key, help, name }
108 TOGGLE_MENU_INFO(DEFINE_TOGGLE_MENU)
109 { 0 }
111 const char *toggle_argv[] = { "toggle", NULL, NULL };
112 int i = 0;
114 if (!prompt_menu("Toggle option", menu, &i))
115 return;
117 toggle_argv[1] = menu[i].data;
118 run_prompt_command(view, toggle_argv);
123 * View opening
126 static enum request
127 open_run_request(struct view *view, enum request request)
129 struct run_request *req = get_run_request(request);
130 const char **argv = NULL;
131 bool confirmed = FALSE;
133 request = REQ_NONE;
135 if (!req) {
136 report("Unknown run request");
137 return request;
140 if (!argv_format(view->env, &argv, req->argv, FALSE, TRUE)) {
141 report("Failed to format arguments");
142 return REQ_NONE;
145 if (req->flags.internal) {
146 request = run_prompt_command(view, argv);
148 } else {
149 confirmed = !req->flags.confirm;
151 if (req->flags.confirm) {
152 char cmd[SIZEOF_STR], prompt[SIZEOF_STR];
153 const char *and_exit = req->flags.exit ? " and exit" : "";
155 if (argv_to_string(argv, cmd, sizeof(cmd), " ") &&
156 string_format(prompt, "Run `%s`%s?", cmd, and_exit) &&
157 prompt_yesno(prompt)) {
158 confirmed = TRUE;
162 if (confirmed && argv_remove_quotes(argv)) {
163 if (req->flags.silent)
164 io_run_bg(argv);
165 else
166 open_external_viewer(argv, NULL, !req->flags.exit, "");
170 if (argv)
171 argv_free(argv);
172 free(argv);
174 if (request == REQ_NONE) {
175 if (req->flags.confirm && !confirmed)
176 request = REQ_NONE;
178 else if (req->flags.exit)
179 request = REQ_QUIT;
181 else if (!req->flags.internal && view_has_flags(view, VIEW_REFRESH) && !view->unrefreshable)
182 request = REQ_REFRESH;
184 return request;
188 * User request switch noodle
191 static int
192 view_driver(struct view *view, enum request request)
194 int i;
196 if (request == REQ_NONE)
197 return TRUE;
199 if (request >= REQ_RUN_REQUESTS) {
200 request = open_run_request(view, request);
202 // exit quickly rather than going through view_request and back
203 if (request == REQ_QUIT)
204 return FALSE;
207 request = view_request(view, request);
208 if (request == REQ_NONE)
209 return TRUE;
211 switch (request) {
212 case REQ_MOVE_UP:
213 case REQ_MOVE_DOWN:
214 case REQ_MOVE_PAGE_UP:
215 case REQ_MOVE_PAGE_DOWN:
216 case REQ_MOVE_FIRST_LINE:
217 case REQ_MOVE_LAST_LINE:
218 move_view(view, request);
219 break;
221 case REQ_SCROLL_FIRST_COL:
222 case REQ_SCROLL_LEFT:
223 case REQ_SCROLL_RIGHT:
224 case REQ_SCROLL_LINE_DOWN:
225 case REQ_SCROLL_LINE_UP:
226 case REQ_SCROLL_PAGE_DOWN:
227 case REQ_SCROLL_PAGE_UP:
228 case REQ_SCROLL_WHEEL_DOWN:
229 case REQ_SCROLL_WHEEL_UP:
230 scroll_view(view, request);
231 break;
233 case REQ_VIEW_GREP:
234 open_grep_view(view);
235 break;
237 case REQ_VIEW_MAIN:
238 open_main_view(view, OPEN_DEFAULT);
239 break;
240 case REQ_VIEW_DIFF:
241 open_diff_view(view, OPEN_DEFAULT);
242 break;
243 case REQ_VIEW_LOG:
244 open_log_view(view, OPEN_DEFAULT);
245 break;
246 case REQ_VIEW_TREE:
247 open_tree_view(view, OPEN_DEFAULT);
248 break;
249 case REQ_VIEW_HELP:
250 open_help_view(view, OPEN_DEFAULT);
251 break;
252 case REQ_VIEW_REFS:
253 open_refs_view(view, OPEN_DEFAULT);
254 break;
255 case REQ_VIEW_BLAME:
256 open_blame_view(view, OPEN_DEFAULT);
257 break;
258 case REQ_VIEW_BLOB:
259 open_blob_view(view, OPEN_DEFAULT);
260 break;
261 case REQ_VIEW_STATUS:
262 open_status_view(view, OPEN_DEFAULT);
263 break;
264 case REQ_VIEW_STAGE:
265 open_stage_view(view, OPEN_DEFAULT);
266 break;
267 case REQ_VIEW_PAGER:
268 open_pager_view(view, OPEN_DEFAULT);
269 break;
270 case REQ_VIEW_STASH:
271 open_stash_view(view, OPEN_DEFAULT);
272 break;
274 case REQ_NEXT:
275 case REQ_PREVIOUS:
276 if (view->parent) {
277 int line;
279 view = view->parent;
280 line = view->pos.lineno;
281 view_request(view, request);
282 move_view(view, request);
283 if (view_is_displayed(view))
284 update_view_title(view);
285 if (line != view->pos.lineno)
286 view_request(view, REQ_ENTER);
287 } else {
288 move_view(view, request);
290 break;
292 case REQ_VIEW_NEXT:
294 int nviews = displayed_views();
295 int next_view = (current_view + 1) % nviews;
297 if (next_view == current_view) {
298 report("Only one view is displayed");
299 break;
302 current_view = next_view;
303 /* Blur out the title of the previous view. */
304 update_view_title(view);
305 report_clear();
306 break;
308 case REQ_REFRESH:
309 report("Refreshing is not supported by the %s view", view->name);
310 break;
312 case REQ_PARENT:
313 report("Moving to parent is not supported by the the %s view", view->name);
314 break;
316 case REQ_BACK:
317 report("Going back is not supported for by %s view", view->name);
318 break;
320 case REQ_JUMP_COMMIT:
321 report("Jumping to commits is not supported by the %s view", view->name);
322 break;
324 case REQ_MAXIMIZE:
325 if (displayed_views() == 2)
326 maximize_view(view, TRUE);
327 break;
329 case REQ_OPTIONS:
330 toggle_option(view);
331 break;
333 case REQ_SEARCH:
334 case REQ_SEARCH_BACK:
335 search_view(view, request);
336 break;
338 case REQ_FIND_NEXT:
339 case REQ_FIND_PREV:
340 find_next(view, request);
341 break;
343 case REQ_STOP_LOADING:
344 foreach_view(view, i) {
345 if (view->pipe)
346 report("Stopped loading the %s view", view->name),
347 end_update(view, TRUE);
349 break;
351 case REQ_SHOW_VERSION:
352 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
353 return TRUE;
355 case REQ_SCREEN_REDRAW:
356 redraw_display(TRUE);
357 break;
359 case REQ_EDIT:
360 report("Nothing to edit");
361 break;
363 case REQ_ENTER:
364 report("Nothing to enter");
365 break;
367 case REQ_VIEW_CLOSE:
368 /* XXX: Mark closed views by letting view->prev point to the
369 * view itself. Parents to closed view should never be
370 * followed. */
371 if (view->prev && view->prev != view) {
372 maximize_view(view->prev, TRUE);
373 view->prev = view;
374 break;
376 /* Fall-through */
377 case REQ_QUIT:
378 return FALSE;
380 default:
381 report("Unknown key, press %s for help",
382 get_view_key(view, REQ_VIEW_HELP));
383 return TRUE;
386 return TRUE;
390 * Main
393 static const char usage_string[] =
394 "tig " TIG_VERSION " (" __DATE__ ")\n"
395 "\n"
396 "Usage: tig [options] [revs] [--] [paths]\n"
397 " or: tig log [options] [revs] [--] [paths]\n"
398 " or: tig show [options] [revs] [--] [paths]\n"
399 " or: tig blame [options] [rev] [--] path\n"
400 " or: tig grep [options] [pattern]\n"
401 " or: tig stash\n"
402 " or: tig status\n"
403 " or: tig < [git command output]\n"
404 "\n"
405 "Options:\n"
406 " +<number> Select line <number> in the first view\n"
407 " -v, --version Show version and exit\n"
408 " -h, --help Show help message and exit";
410 void
411 usage(const char *message)
413 die("%s\n\n%s", message, usage_string);
416 static int
417 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen, void *data)
419 const char ***filter_args = data;
421 return argv_append(filter_args, name) ? OK : ERR;
424 static void
425 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
427 const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
428 const char **all_argv = NULL;
430 if (!argv_append_array(&all_argv, rev_parse_argv) ||
431 !argv_append_array(&all_argv, argv) ||
432 io_run_load(all_argv, "\n", read_filter_args, args) == ERR)
433 die("Failed to split arguments");
434 argv_free(all_argv);
435 free(all_argv);
438 static void
439 filter_options(const char *argv[], bool rev_parse)
441 const char **flags = NULL;
442 int next, flags_pos;
444 update_options_from_argv(argv);
446 if (!rev_parse) {
447 opt_cmdline_argv = argv;
448 return;
451 filter_rev_parse(&opt_file_argv, "--no-revs", "--no-flags", argv);
452 filter_rev_parse(&flags, "--flags", "--no-revs", argv);
454 if (flags) {
455 for (next = flags_pos = 0; flags && flags[next]; next++) {
456 const char *flag = flags[next];
458 if (argv_parse_rev_flag(flag, NULL))
459 argv_append(&opt_rev_argv, flag);
460 else
461 flags[flags_pos++] = flag;
464 flags[flags_pos] = NULL;
466 opt_cmdline_argv = flags;
469 filter_rev_parse(&opt_rev_argv, "--symbolic", "--revs-only", argv);
472 static enum request
473 parse_options(int argc, const char *argv[], bool pager_mode)
475 enum request request;
476 const char *subcommand;
477 bool seen_dashdash = FALSE;
478 bool rev_parse = TRUE;
479 const char **filter_argv = NULL;
480 int i;
482 request = pager_mode ? REQ_VIEW_PAGER : REQ_VIEW_MAIN;
484 if (argc <= 1)
485 return request;
487 subcommand = argv[1];
488 if (!strcmp(subcommand, "status")) {
489 request = REQ_VIEW_STATUS;
491 } else if (!strcmp(subcommand, "blame")) {
492 request = REQ_VIEW_BLAME;
494 } else if (!strcmp(subcommand, "grep")) {
495 request = REQ_VIEW_GREP;
496 rev_parse = FALSE;
498 } else if (!strcmp(subcommand, "show")) {
499 request = REQ_VIEW_DIFF;
501 } else if (!strcmp(subcommand, "log")) {
502 request = REQ_VIEW_LOG;
504 } else if (!strcmp(subcommand, "stash")) {
505 request = REQ_VIEW_STASH;
507 } else {
508 subcommand = NULL;
511 for (i = 1 + !!subcommand; i < argc; i++) {
512 const char *opt = argv[i];
514 // stop parsing our options after -- and let rev-parse handle the rest
515 if (!seen_dashdash) {
516 if (!strcmp(opt, "--")) {
517 seen_dashdash = TRUE;
518 continue;
520 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
521 printf("tig version %s\n", TIG_VERSION);
522 exit(EXIT_SUCCESS);
524 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
525 printf("%s\n", usage_string);
526 exit(EXIT_SUCCESS);
528 } else if (strlen(opt) >= 2 && *opt == '+' && string_isnumber(opt + 1)) {
529 int lineno = atoi(opt + 1);
531 argv_env.lineno = lineno > 0 ? lineno - 1 : 0;
532 continue;
537 if (!argv_append(&filter_argv, opt))
538 die("command too long");
541 if (filter_argv)
542 filter_options(filter_argv, rev_parse);
544 return request;
547 static enum request
548 open_pager_mode(enum request request)
550 if (request == REQ_VIEW_PAGER) {
551 /* Detect if the user requested the main view. */
552 if (argv_contains(opt_rev_argv, "--stdin")) {
553 open_main_view(NULL, OPEN_FORWARD_STDIN);
554 } else if (argv_contains(opt_cmdline_argv, "--pretty=raw")) {
555 open_main_view(NULL, OPEN_STDIN);
556 } else {
557 open_pager_view(NULL, OPEN_STDIN);
560 } else if (request == REQ_VIEW_DIFF) {
561 if (argv_contains(opt_rev_argv, "--stdin"))
562 open_diff_view(NULL, OPEN_FORWARD_STDIN);
563 else
564 open_diff_view(NULL, OPEN_STDIN);
566 } else {
567 close(STDIN_FILENO);
568 report("Ignoring stdin.");
569 return request;
572 return REQ_NONE;
575 #ifdef NCURSES_MOUSE_VERSION
576 static struct view *
577 find_clicked_view(MEVENT *event)
579 struct view *view;
580 int i;
582 foreach_displayed_view (view, i) {
583 int beg_y = 0, beg_x = 0;
585 getbegyx(view->win, beg_y, beg_x);
587 if (beg_y <= event->y && event->y < beg_y + view->height
588 && beg_x <= event->x && event->x < beg_x + view->width) {
589 if (i != current_view) {
590 current_view = i;
592 return view;
596 return NULL;
599 static enum request
600 handle_mouse_event(void)
602 MEVENT event;
603 struct view *view;
605 if (getmouse(&event) != OK)
606 return REQ_NONE;
608 view = find_clicked_view(&event);
609 if (!view)
610 return REQ_NONE;
612 if (event.bstate & BUTTON2_PRESSED)
613 return REQ_SCROLL_WHEEL_DOWN;
615 if (event.bstate & BUTTON4_PRESSED)
616 return REQ_SCROLL_WHEEL_UP;
618 if (event.bstate & BUTTON1_PRESSED) {
619 if (event.y == view->pos.lineno - view->pos.offset) {
620 /* Click is on the same line, perform an "ENTER" */
621 return REQ_ENTER;
623 } else {
624 int y = getbegy(view->win);
625 unsigned long lineno = (event.y - y) + view->pos.offset;
627 select_view_line(view, lineno);
628 update_view_title(view);
629 report_clear();
633 return REQ_NONE;
635 #endif
638 main(int argc, const char *argv[])
640 const char *codeset = ENCODING_UTF8;
641 bool pager_mode = !isatty(STDIN_FILENO);
642 enum request request = parse_options(argc, argv, pager_mode);
643 struct view *view;
645 if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
646 die("Failed to setup signal handler");
648 if (setlocale(LC_ALL, "")) {
649 codeset = nl_langinfo(CODESET);
652 if (load_repo_info() == ERR)
653 die("Failed to load repo info.");
655 if (load_options() == ERR)
656 die("Failed to load user config.");
658 if (load_git_config() == ERR)
659 die("Failed to load repo config.");
661 /* Require a git repository unless when running in pager mode. */
662 if (!repo.git_dir[0] && request != REQ_VIEW_PAGER)
663 die("Not a git repository");
665 if (codeset && strcmp(codeset, ENCODING_UTF8)) {
666 char translit[SIZEOF_STR];
668 if (string_format(translit, "%s%s", codeset, ICONV_TRANSLIT))
669 opt_iconv_out = iconv_open(translit, ENCODING_UTF8);
670 else
671 opt_iconv_out = iconv_open(codeset, ENCODING_UTF8);
672 if (opt_iconv_out == ICONV_NONE)
673 die("Failed to initialize character set conversion");
676 if (load_refs(FALSE) == ERR)
677 die("Failed to load refs.");
679 init_display();
681 if (pager_mode)
682 request = open_pager_mode(request);
684 while (view_driver(display[current_view], request)) {
685 struct key_input input;
686 int key = get_input(0, &input, TRUE);
688 #ifdef NCURSES_MOUSE_VERSION
689 if (key == KEY_MOUSE) {
690 request = handle_mouse_event();
691 continue;
693 #endif
695 view = display[current_view];
696 request = get_keybinding(view->keymap, &input);
698 /* Some low-level request handling. This keeps access to
699 * status_win restricted. */
700 switch (request) {
701 case REQ_NONE:
702 report("Unknown key, press %s for help",
703 get_view_key(view, REQ_VIEW_HELP));
704 break;
705 case REQ_PROMPT:
706 request = open_prompt(view);
707 break;
708 case REQ_SEARCH:
709 case REQ_SEARCH_BACK:
711 const char *prompt = request == REQ_SEARCH ? "/" : "?";
712 char *search = read_prompt(prompt);
714 if (search)
715 string_ncopy(argv_env.search, search, strlen(search));
716 else if (*argv_env.search)
717 request = request == REQ_SEARCH ?
718 REQ_FIND_NEXT :
719 REQ_FIND_PREV;
720 else
721 request = REQ_NONE;
722 break;
724 default:
725 break;
729 exit(EXIT_SUCCESS);
731 return 0;
734 /* vim: set ts=8 sw=8 noexpandtab: */