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
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"
30 #include "tig/search.h"
32 #include "tig/options.h"
34 #include "tig/display.h"
35 #include "tig/prompt.h"
38 #include "tig/blame.h"
45 #include "tig/pager.h"
47 #include "tig/stage.h"
48 #include "tig/stash.h"
49 #include "tig/status.h"
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
);
60 view_request(struct view
*view
, enum request request
)
62 if (!view
|| !view
->lines
)
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
);
75 if (request
== REQ_REFRESH
&& !view_can_refresh(view
)) {
76 report("This view can not be refreshed");
80 return view
->ops
->request(view
, request
, &view
->line
[view
->pos
.lineno
]);
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"), \
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
)
113 const char *toggle_argv
[] = { "toggle", NULL
, NULL
};
116 if (!prompt_menu("Toggle option", menu
, &i
))
119 toggle_argv
[1] = menu
[i
].data
;
120 run_prompt_command(view
, toggle_argv
);
129 open_run_request(struct view
*view
, enum request request
)
131 struct run_request
*req
= get_run_request(request
);
134 report("Unknown run request");
138 return exec_run_request(view
, req
);
142 * User request switch noodle
146 view_driver(struct view
*view
, enum request request
)
150 if (request
== REQ_NONE
)
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
)
161 request
= view_request(view
, request
);
162 if (request
== REQ_NONE
)
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
);
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
);
190 open_grep_view(view
);
194 open_main_view(view
, OPEN_DEFAULT
);
197 open_diff_view(view
, OPEN_DEFAULT
);
200 open_log_view(view
, OPEN_DEFAULT
);
203 open_tree_view(view
, OPEN_DEFAULT
);
206 open_help_view(view
, OPEN_DEFAULT
);
209 open_refs_view(view
, OPEN_DEFAULT
);
212 open_blame_view(view
, OPEN_DEFAULT
);
215 open_blob_view(view
, OPEN_DEFAULT
);
217 case REQ_VIEW_STATUS
:
218 open_status_view(view
, OPEN_DEFAULT
);
221 open_stage_view(view
, NULL
, 0, OPEN_DEFAULT
);
224 open_pager_view(view
, OPEN_DEFAULT
);
227 open_stash_view(view
, OPEN_DEFAULT
);
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
);
244 move_view(view
, request
);
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");
258 current_view
= next_view
;
259 /* Blur out the title of the previous view. */
260 update_view_title(view
);
265 report("Refreshing is not supported by the %s view", view
->name
);
269 report("Moving to parent is not supported by the %s view", view
->name
);
273 report("Going back is not supported by the %s view", view
->name
);
277 if (displayed_views() == 2)
278 maximize_view(view
, true);
286 case REQ_SEARCH_BACK
:
287 search_view(view
, request
);
292 find_next(view
, request
);
295 case REQ_STOP_LOADING
:
296 foreach_view(view
, i
) {
298 report("Stopped loading the %s view", view
->name
),
299 end_update(view
, true);
303 case REQ_SHOW_VERSION
:
304 report("tig-%s (built %s)", TIG_VERSION
, __DATE__
);
307 case REQ_SCREEN_REDRAW
:
308 redraw_display(true);
312 report("Nothing to edit");
316 report("Nothing to enter");
320 /* XXX: Mark closed views by letting view->prev point to the
321 * view itself. Parents to closed view should never be
323 if (view
->prev
&& view
->prev
!= view
) {
324 maximize_view(view
->prev
, true);
326 watch_unregister(&view
->watch
);
335 report("Unknown key, press %s for help",
336 get_view_key(view
, REQ_VIEW_HELP
));
347 static const char usage_string
[] =
348 "tig " TIG_VERSION
" (" __DATE__
")\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"
358 " or: tig < [git command output]\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";
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
;
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");
394 filter_options(const char *argv
[], bool rev_parse
)
396 const char **flags
= NULL
;
399 update_options_from_argv(argv
);
402 opt_cmdline_args
= argv
;
406 filter_rev_parse(&opt_file_args
, "--no-revs", "--no-flags", argv
);
407 filter_rev_parse(&flags
, "--flags", "--no-revs", argv
);
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
);
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
);
430 argv
[flags_pos
++] = arg
;
433 argv
[flags_pos
] = NULL
;
435 filter_rev_parse(&opt_rev_args
, "--symbolic", "--revs-only", argv
);
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
;
448 request
= pager_mode
? REQ_VIEW_PAGER
: REQ_VIEW_MAIN
;
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
;
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
;
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;
489 } else if (!strcmp(opt
, "-v") || !strcmp(opt
, "--version")) {
490 printf("tig version %s\n", TIG_VERSION
);
493 } else if (!strcmp(opt
, "-h") || !strcmp(opt
, "--help")) {
494 printf("%s\n", usage_string
);
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;
506 if (!argv_append(&filter_argv
, opt
))
507 die("command too long");
511 filter_options(filter_argv
, rev_parse
);
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
);
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
);
533 open_diff_view(NULL
, OPEN_STDIN
);
537 report("Ignoring stdin.");
544 #ifdef NCURSES_MOUSE_VERSION
546 find_clicked_view(MEVENT
*event
)
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
) {
569 handle_mouse_event(void)
574 if (getmouse(&event
) != OK
)
577 view
= find_clicked_view(&event
);
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" */
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
);
607 enum request request
;
608 struct keymap
*keymap
;
614 static enum input_status
615 key_combo_handler(struct input
*input
, struct key
*key
)
617 struct key_combo
*combo
= input
->data
;
620 #ifdef NCURSES_MOUSE_VERSION
621 if (key_to_value(key
) == KEY_MOUSE
) {
622 combo
->request
= handle_mouse_event();
627 if (combo
->keys
&& key_to_value(key
) == KEY_ESC
)
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
;
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
;
650 die_if_failed(enum status_code code
, const char *msg
)
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
);
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
);
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.");
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. */
713 report("Unknown key, press %s for help",
714 get_view_key(view
, REQ_VIEW_HELP
));
718 request
= open_prompt(view
);
730 /* vim: set ts=8 sw=8 noexpandtab: */