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
17 #include "tig/types.h"
19 #include "tig/parse.h"
22 #include "tig/refdb.h"
23 #include "tig/watch.h"
24 #include "tig/graph.h"
26 #include "tig/request.h"
31 #include "tig/options.h"
33 #include "tig/display.h"
34 #include "tig/prompt.h"
37 #include "tig/blame.h"
44 #include "tig/pager.h"
46 #include "tig/stage.h"
47 #include "tig/stash.h"
48 #include "tig/status.h"
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
);
59 view_request(struct view
*view
, enum request request
)
61 if (!view
|| !view
->lines
)
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
);
74 if (request
== REQ_REFRESH
&& !view_can_refresh(view
)) {
75 report("This view can not be refreshed");
79 return view
->ops
->request(view
, request
, &view
->line
[view
->pos
.lineno
]);
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"), \
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
)
112 const char *toggle_argv
[] = { "toggle", NULL
, NULL
};
115 if (!prompt_menu("Toggle option", menu
, &i
))
118 toggle_argv
[1] = menu
[i
].data
;
119 run_prompt_command(view
, toggle_argv
);
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
;
137 report("Unknown run request");
141 if (!argv_format(view
->env
, &argv
, req
->argv
, FALSE
, TRUE
)) {
142 report("Failed to format arguments");
146 if (req
->flags
.internal
) {
147 request
= run_prompt_command(view
, argv
);
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
)) {
163 if (confirmed
&& argv_remove_quotes(argv
))
164 open_external_viewer(argv
, NULL
, req
->flags
.silent
,
165 !req
->flags
.exit
, FALSE
, "");
172 if (request
== REQ_NONE
) {
173 if (req
->flags
.confirm
&& !confirmed
)
176 else if (req
->flags
.exit
)
179 else if (!req
->flags
.internal
&& watch_dirty(&view
->watch
))
180 request
= REQ_REFRESH
;
187 * User request switch noodle
191 view_driver(struct view
*view
, enum request request
)
195 if (request
== REQ_NONE
)
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
)
206 request
= view_request(view
, request
);
207 if (request
== REQ_NONE
)
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
);
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
);
235 open_grep_view(view
);
239 open_main_view(view
, OPEN_DEFAULT
);
242 open_diff_view(view
, OPEN_DEFAULT
);
245 open_log_view(view
, OPEN_DEFAULT
);
248 open_tree_view(view
, OPEN_DEFAULT
);
251 open_help_view(view
, OPEN_DEFAULT
);
254 open_refs_view(view
, OPEN_DEFAULT
);
257 open_blame_view(view
, OPEN_DEFAULT
);
260 open_blob_view(view
, OPEN_DEFAULT
);
262 case REQ_VIEW_STATUS
:
263 open_status_view(view
, OPEN_DEFAULT
);
266 open_stage_view(view
, NULL
, 0, OPEN_DEFAULT
);
269 open_pager_view(view
, OPEN_DEFAULT
);
272 open_stash_view(view
, OPEN_DEFAULT
);
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
);
289 move_view(view
, request
);
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");
303 current_view
= next_view
;
304 /* Blur out the title of the previous view. */
305 update_view_title(view
);
310 report("Refreshing is not supported by the %s view", view
->name
);
314 report("Moving to parent is not supported by the the %s view", view
->name
);
318 report("Going back is not supported for by %s view", view
->name
);
322 if (displayed_views() == 2)
323 maximize_view(view
, TRUE
);
331 case REQ_SEARCH_BACK
:
332 search_view(view
, request
);
337 find_next(view
, request
);
340 case REQ_STOP_LOADING
:
341 foreach_view(view
, i
) {
343 report("Stopped loading the %s view", view
->name
),
344 end_update(view
, TRUE
);
348 case REQ_SHOW_VERSION
:
349 report("tig-%s (built %s)", TIG_VERSION
, __DATE__
);
352 case REQ_SCREEN_REDRAW
:
353 redraw_display(TRUE
);
357 report("Nothing to edit");
361 report("Nothing to enter");
365 /* XXX: Mark closed views by letting view->prev point to the
366 * view itself. Parents to closed view should never be
368 if (view
->prev
&& view
->prev
!= view
) {
369 maximize_view(view
->prev
, TRUE
);
371 watch_unregister(&view
->watch
);
379 report("Unknown key, press %s for help",
380 get_view_key(view
, REQ_VIEW_HELP
));
391 static const char usage_string
[] =
392 "tig " TIG_VERSION
" (" __DATE__
")\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"
402 " or: tig < [git command output]\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";
410 usage(const char *message
)
412 die("%s\n\n%s", message
, usage_string
);
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
;
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");
438 filter_options(const char *argv
[], bool rev_parse
)
440 const char **flags
= NULL
;
443 update_options_from_argv(argv
);
446 opt_cmdline_args
= argv
;
450 filter_rev_parse(&opt_file_args
, "--no-revs", "--no-flags", argv
);
451 filter_rev_parse(&flags
, "--flags", "--no-revs", argv
);
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
);
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
);
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
;
481 request
= pager_mode
? REQ_VIEW_PAGER
: REQ_VIEW_MAIN
;
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
;
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
;
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
;
522 } else if (!strcmp(opt
, "-v") || !strcmp(opt
, "--version")) {
523 printf("tig version %s\n", TIG_VERSION
);
526 } else if (!strcmp(opt
, "-h") || !strcmp(opt
, "--help")) {
527 printf("%s\n", usage_string
);
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;
539 if (!argv_append(&filter_argv
, opt
))
540 die("command too long");
544 filter_options(filter_argv
, rev_parse
);
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
);
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
);
566 open_diff_view(NULL
, OPEN_STDIN
);
570 report("Ignoring stdin.");
577 #ifdef NCURSES_MOUSE_VERSION
579 find_clicked_view(MEVENT
*event
)
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
) {
602 handle_mouse_event(void)
607 if (getmouse(&event
) != OK
)
610 view
= find_clicked_view(&event
);
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" */
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
);
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
);
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
);
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.");
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
)) {
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();
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. */
712 report("Unknown key, press %s for help",
713 get_view_key(view
, REQ_VIEW_HELP
));
716 request
= open_prompt(view
);
719 case REQ_SEARCH_BACK
:
721 const char *prompt
= request
== REQ_SEARCH
? "/" : "?";
722 char *search
= read_prompt(prompt
);
725 string_ncopy(argv_env
.search
, search
, strlen(search
));
726 else if (*argv_env
.search
)
727 request
= request
== REQ_SEARCH
?
744 /* vim: set ts=8 sw=8 noexpandtab: */