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.
18 #include "tig/display.h"
19 #include "tig/options.h"
20 #include "tig/prompt.h"
21 #include "tig/pager.h"
22 #include "tig/types.h"
25 #include <readline/readline.h>
26 #include <readline/history.h>
27 #endif /* HAVE_READLINE */
30 prompt_input(const char *prompt
, struct input
*input
)
32 enum input_status status
= INPUT_OK
;
33 unsigned char chars_length
[SIZEOF_STR
];
35 size_t promptlen
= strlen(prompt
);
36 int pos
= 0, chars
= 0;
37 int last_buf_length
= promptlen
? -1 : promptlen
;
41 while (status
== INPUT_OK
|| status
== INPUT_SKIP
) {
42 int buf_length
= strlen(input
->buf
) + promptlen
;
43 int offset
= pos
|| buf_length
!= last_buf_length
? pos
+ promptlen
: -1;
45 last_buf_length
= buf_length
;
47 update_status("%s%.*s", prompt
, pos
, input
->buf
);
49 if (get_input(offset
, &key
) == OK
) {
50 int len
= strlen(key
.data
.bytes
);
52 if (pos
+ len
>= sizeof(input
->buf
)) {
53 report("Input string too long");
57 string_ncopy_do(input
->buf
+ pos
, sizeof(input
->buf
) - pos
, key
.data
.bytes
, len
);
59 chars_length
[chars
++] = len
;
60 status
= input
->handler(input
, &key
);
61 if (status
!= INPUT_OK
) {
65 int changed_pos
= strlen(input
->buf
);
67 if (changed_pos
!= pos
) {
69 chars_length
[chars
- 1] = changed_pos
- (pos
- len
);
73 status
= input
->handler(input
, &key
);
74 if (status
== INPUT_DELETE
) {
75 int len
= chars_length
[--chars
];
80 int changed_pos
= strlen(input
->buf
);
82 if (changed_pos
!= pos
) {
84 chars_length
[chars
++] = changed_pos
- pos
;
93 if (status
== INPUT_CANCEL
)
96 input
->buf
[pos
++] = 0;
102 prompt_default_handler(struct input
*input
, struct key
*key
)
104 switch (key_to_value(key
)) {
108 return *input
->buf
|| input
->allow_empty
? INPUT_STOP
: INPUT_CANCEL
;
111 return *input
->buf
? INPUT_DELETE
: INPUT_CANCEL
;
121 static enum input_status
122 prompt_yesno_handler(struct input
*input
, struct key
*key
)
124 unsigned long c
= key_to_unicode(key
);
126 if (c
== 'y' || c
== 'Y')
128 if (c
== 'n' || c
== 'N')
130 return prompt_default_handler(input
, key
);
134 prompt_yesno(const char *prompt
)
136 char prompt2
[SIZEOF_STR
];
137 struct input input
= { prompt_yesno_handler
, false, NULL
};
139 if (!string_format(prompt2
, "%s [Yy/Nn]", prompt
))
142 return !!prompt_input(prompt2
, &input
);
145 struct incremental_input
{
147 input_handler handler
;
151 static enum input_status
152 read_prompt_handler(struct input
*input
, struct key
*key
)
154 struct incremental_input
*incremental
= (struct incremental_input
*) input
;
156 if (incremental
->edit_mode
&& !key
->modifiers
.multibytes
)
157 return prompt_default_handler(input
, key
);
159 if (!unicode_width(key_to_unicode(key
), 8))
162 if (!incremental
->handler
)
165 return incremental
->handler(input
, key
);
169 read_prompt_incremental(const char *prompt
, bool edit_mode
, bool allow_empty
, input_handler handler
, void *data
)
171 static struct incremental_input incremental
= { { read_prompt_handler
} };
173 incremental
.input
.allow_empty
= allow_empty
;
174 incremental
.input
.data
= data
;
175 incremental
.handler
= handler
;
176 incremental
.edit_mode
= edit_mode
;
178 return prompt_input(prompt
, (struct input
*) &incremental
);
183 readline_display(void)
185 update_status("%s%s", rl_display_prompt
, rl_line_buffer
);
186 wmove(status_win
, 0, strlen(rl_display_prompt
) + rl_point
);
187 wrefresh(status_win
);
191 readline_variable_generator(const char *text
, int state
)
193 static const char *vars
[] = {
194 #define FORMAT_VAR(type, name, ifempty, initval) "%(" #name ")",
195 ARGV_ENV_INFO(FORMAT_VAR
)
200 static int index
, len
;
202 char *variable
= NULL
; /* No match */
204 /* If it is a new word to complete, initialize */
210 /* Return the next name which partially matches */
211 while ((name
= vars
[index
])) {
214 /* Complete or format a variable */
215 if (strncmp(name
, text
, len
) == 0) {
216 if (strlen(name
) > len
)
217 variable
= strdup(name
);
219 variable
= argv_format_arg(&argv_env
, text
);
228 readline_action_generator(const char *text
, int state
)
230 static const char *actions
[] = {
241 #define REQ_GROUP(help)
242 #define REQ_(req, help) #req
249 static int index
, len
;
251 char *match
= NULL
; /* No match */
253 /* If it is a new word to complete, initialize */
259 /* Return the next name which partially matches */
260 while ((name
= actions
[index
])) {
261 name
= enum_name(name
);
264 if (strncmp(name
, text
, len
) == 0) {
265 /* Ignore exact completion */
266 if (strlen(name
) > len
)
267 match
= strdup(name
);
276 readline_set_generator(const char *text
, int state
)
278 static const char *words
[] = {
279 #define DEFINE_OPTION_NAME(name, type, flags) #name " = ",
280 OPTION_INFO(DEFINE_OPTION_NAME
)
281 #undef DEFINE_OPTION_NAME
285 static int index
, len
;
287 char *match
= NULL
; /* No match */
289 /* If it is a new word to complete, initialize */
295 /* Return the next name which partially matches */
296 while ((name
= words
[index
])) {
297 name
= enum_name(name
);
300 if (strncmp(name
, text
, len
) == 0) {
301 /* Ignore exact completion */
302 if (strlen(name
) > len
)
303 match
= strdup(name
);
312 readline_toggle_generator(const char *text
, int state
)
314 static const char **words
;
315 static int index
, len
;
317 char *match
= NULL
; /* No match */
320 /* TODO: Only complete column options that are defined
323 #define DEFINE_OPTION_WORD(name, type, flags) argv_append(&words, #name);
324 #define DEFINE_COLUMN_OPTIONS_WORD(name, type, flags) #name,
325 #define DEFINE_COLUMN_OPTIONS_WORDS(name, id, options) \
326 if (VIEW_COLUMN_##id != VIEW_COLUMN_SECTION) { \
327 const char *vars[] = { \
328 options(DEFINE_COLUMN_OPTIONS_WORD) \
330 char buf[SIZEOF_STR]; \
332 for (i = 0; i < ARRAY_SIZE(vars); i++) { \
333 if (enum_name_prefixed(buf, sizeof(buf), #name, vars[i])) \
334 argv_append(&words, buf); \
338 OPTION_INFO(DEFINE_OPTION_WORD
)
339 COLUMN_OPTIONS(DEFINE_COLUMN_OPTIONS_WORDS
);
342 /* If it is a new word to complete, initialize */
348 /* Return the next name which partially matches */
349 while ((name
= words
[index
])) {
350 name
= enum_name(name
);
353 if (strncmp(name
, text
, len
) == 0) {
354 /* Ignore exact completion */
355 if (strlen(name
) > len
)
356 match
= strdup(name
);
365 readline_getc(FILE *stream
)
367 return get_input_char();
371 readline_completion(const char *text
, int start
, int end
)
373 /* Do not append a space after a completion */
374 rl_completion_suppress_append
= 1;
377 * If the word is at the start of the line,
378 * then it is a tig action to complete.
381 return rl_completion_matches(text
, readline_action_generator
);
384 * If the line begins with "toggle", then we complete toggle options.
386 if (start
>= 7 && strncmp(rl_line_buffer
, "toggle ", 7) == 0)
387 return rl_completion_matches(text
, readline_toggle_generator
);
390 * If the line begins with "set", then we complete set options.
391 * (unless it is already completed)
393 if (start
>= 4 && strncmp(rl_line_buffer
, "set ", 4) == 0 &&
394 !strchr(rl_line_buffer
, '='))
395 return rl_completion_matches(text
, readline_set_generator
);
398 * Otherwise it might be a variable name...
400 if (strncmp(text
, "%(", 2) == 0)
401 return rl_completion_matches(text
, readline_variable_generator
);
404 * ... or finally fall back to filename completion.
410 readline_display_matches(char **matches
, int num_matches
, int max_length
)
414 wmove(status_win
, 0, 0);
415 waddstr(status_win
, "matches: ");
417 /* matches[0] is the incomplete word */
418 for (i
= 1; i
< num_matches
+ 1; ++i
) {
419 waddstr(status_win
, matches
[i
]);
420 waddch(status_win
, ' ');
424 wrefresh(status_win
);
430 /* Allow conditional parsing of the ~/.inputrc file. */
431 rl_readline_name
= "tig";
433 /* Word break caracters (we removed '(' to match variables) */
434 rl_basic_word_break_characters
= " \t\n\"\\'`@$><=;|&{";
436 /* Custom display function */
437 rl_redisplay_function
= readline_display
;
438 rl_getc_function
= readline_getc
;
440 /* Completion support */
441 rl_attempted_completion_function
= readline_completion
;
443 rl_completion_display_matches_hook
= readline_display_matches
;
447 read_prompt(const char *prompt
)
449 static char *line
= NULL
;
456 line
= readline(prompt
);
458 if (line
&& !*line
) {
476 read_prompt(const char *prompt
)
478 return read_prompt_incremental(prompt
, true, false, NULL
, NULL
);
485 #endif /* HAVE_READLINE */
488 prompt_menu(const char *prompt
, const struct menu_item
*items
, int *selected
)
490 enum input_status status
= INPUT_OK
;
494 while (items
[size
].text
)
499 while (status
== INPUT_OK
) {
500 const struct menu_item
*item
= &items
[*selected
];
501 char hotkey
[] = { '[', (char) item
->hotkey
, ']', ' ', 0 };
504 update_status("%s (%d of %d) %s%s", prompt
, *selected
+ 1, size
,
505 item
->hotkey
? hotkey
: "", item
->text
);
507 switch (get_input(COLS
- 1, &key
)) {
516 *selected
= *selected
- 1;
518 *selected
= size
- 1;
523 *selected
= (*selected
+ 1) % size
;
527 status
= INPUT_CANCEL
;
531 for (i
= 0; items
[i
].text
; i
++)
532 if (items
[i
].hotkey
== key
.data
.bytes
[0]) {
542 return status
!= INPUT_CANCEL
;
545 static struct option_info option_toggles
[] = {
546 #define DEFINE_OPTION_TOGGLES(name, type, flags) { #name, STRING_SIZE(#name), #type, &opt_ ## name, flags },
547 OPTION_INFO(DEFINE_OPTION_TOGGLES
)
551 find_arg(const char *argv
[], const char *arg
)
555 for (i
= 0; argv
&& argv
[i
]; i
++)
556 if (!strcmp(argv
[i
], arg
))
561 static enum status_code
562 prompt_toggle_option(struct view
*view
, const char *argv
[], const char *prefix
,
563 struct option_info
*toggle
, enum view_flag
*flags
)
565 char name
[SIZEOF_STR
];
567 if (!enum_name_prefixed(name
, sizeof(name
), prefix
, toggle
->name
))
568 return error("Failed to toggle option %s", toggle
->name
);
570 *flags
= toggle
->flags
;
572 if (!strcmp(toggle
->type
, "bool")) {
573 bool *opt
= toggle
->value
;
576 if (opt
== &opt_mouse
)
578 return success("set %s = %s", name
, *opt
? "yes" : "no");
580 } else if (!strncmp(toggle
->type
, "enum", 4)) {
581 const char *type
= toggle
->type
+ STRING_SIZE("enum ");
582 enum author
*opt
= toggle
->value
;
583 const struct enum_map
*map
= find_enum_map(type
);
585 *opt
= (*opt
+ 1) % map
->size
;
586 return success("set %s = %s", name
, enum_name(map
->entries
[*opt
].name
));
588 } else if (!strcmp(toggle
->type
, "int")) {
589 const char *arg
= argv
[2] ? argv
[2] : "1";
590 int diff
= atoi(arg
);
591 int *opt
= toggle
->value
;
594 diff
= *arg
== '-' ? -1 : 1;
596 if (opt
== &opt_diff_context
&& *opt
< 0)
598 if (opt
== &opt_diff_context
&& diff
< 0) {
600 return error("Diff context cannot be less than zero");
605 if (strstr(name
, "commit-title-overflow")) {
606 *opt
= *opt
? -*opt
: 50;
608 return success("set %s = no", name
);
613 return success("set %s = %d", name
, *opt
);
615 } else if (!strcmp(toggle
->type
, "double")) {
616 const char *arg
= argv
[2] ? argv
[2] : "1.0";
617 double *opt
= toggle
->value
;
626 if (parse_step(&diff
, arg
) != SUCCESS
)
627 diff
= strtod(arg
, NULL
);
630 return success("set %s = %.2f", name
, *opt
);
632 } else if (!strcmp(toggle
->type
, "const char **")) {
633 const char ***opt
= toggle
->value
;
637 if (argv_size(argv
) <= 2) {
643 for (i
= 2; argv
[i
]; i
++) {
644 if (!find_arg(*opt
, argv
[i
])) {
653 for (next
= 0, pos
= 0; (*opt
)[pos
]; pos
++) {
654 const char *arg
= (*opt
)[pos
];
656 if (find_arg(argv
+ 2, arg
)) {
660 (*opt
)[next
++] = arg
;
665 } else if (!argv_copy(opt
, argv
+ 2)) {
666 return ERROR_OUT_OF_MEMORY
;
671 return error("Unsupported `:toggle %s` (%s)", name
, toggle
->type
);
675 static enum status_code
676 prompt_toggle(struct view
*view
, const char *argv
[], enum view_flag
*flags
)
678 const char *option
= argv
[1];
679 size_t optionlen
= option
? strlen(option
) : 0;
680 struct option_info
template;
681 struct option_info
*toggle
;
682 struct view_column
*column
;
683 const char *column_name
;
686 return error("%s", "No option name given to :toggle");
688 if (enum_equals_static("sort-field", option
, optionlen
) ||
689 enum_equals_static("sort-order", option
, optionlen
)) {
690 if (!view_has_flags(view
, VIEW_SORTABLE
)) {
691 return error("Sorting is not yet supported for the %s view", view
->name
);
693 bool sort_field
= enum_equals_static("sort-field", option
, optionlen
);
694 struct sort_state
*sort
= &view
->sort
;
696 sort_view(view
, sort_field
);
697 return success("set %s = %s", option
,
698 sort_field
? view_column_name(get_sort_field(view
))
699 : sort
->reverse
? "descending" : "ascending");
703 toggle
= find_option_info(option_toggles
, ARRAY_SIZE(option_toggles
), "", option
);
705 return prompt_toggle_option(view
, argv
, "", toggle
, flags
);
707 for (column
= view
->columns
; column
; column
= column
->next
) {
708 toggle
= find_column_option_info(column
->type
, &column
->opt
, option
, &template, &column_name
);
710 return prompt_toggle_option(view
, argv
, column_name
, toggle
, flags
);
713 return error("`:toggle %s` not supported", option
);
717 prompt_update_display(enum view_flag flags
)
722 if (flags
& VIEW_RESET_DISPLAY
) {
724 redraw_display(true);
727 foreach_displayed_view(view
, i
) {
728 if (view_has_flags(view
, flags
) && view_can_refresh(view
))
736 run_prompt_command(struct view
*view
, const char *argv
[])
738 enum request request
;
739 const char *cmd
= argv
[0];
740 size_t cmdlen
= cmd
? strlen(cmd
) : 0;
745 if (string_isnumber(cmd
)) {
746 int lineno
= view
->pos
.lineno
+ 1;
748 if (parse_int(&lineno
, cmd
, 0, view
->lines
+ 1) == SUCCESS
) {
751 select_view_line(view
, lineno
- 1);
754 report("Unable to parse '%s' as a line number", cmd
);
756 } else if (iscommit(cmd
)) {
757 goto_id(view
, cmd
, true, true);
760 } else if (cmdlen
> 1 && (cmd
[0] == '/' || cmd
[0] == '?')) {
761 char search
[SIZEOF_STR
];
763 if (!argv_to_string(argv
, search
, sizeof(search
), " ")) {
764 report("Failed to copy search string");
768 if (!strcmp(search
+ 1, view
->env
->search
))
769 return cmd
[0] == '/' ? REQ_FIND_NEXT
: REQ_FIND_PREV
;
771 string_ncopy(view
->env
->search
, search
+ 1, strlen(search
+ 1));
772 return cmd
[0] == '/' ? REQ_FIND_NEXT
: REQ_FIND_PREV
;
774 } else if (cmdlen
> 1 && cmd
[0] == '!') {
775 struct view
*next
= &pager_view
;
778 /* Trim the leading '!'. */
780 copied
= argv_format(view
->env
, &next
->argv
, argv
, false, true);
784 report("Argument formatting failed");
786 /* When running random commands, initially show the
787 * command in the title. However, it maybe later be
788 * overwritten if a commit line is selected. */
789 argv_to_string(next
->argv
, next
->ref
, sizeof(next
->ref
), " ");
792 open_pager_view(view
, OPEN_PREPARED
| OPEN_WITH_STDERR
);
795 } else if (!strcmp(cmd
, "goto")) {
796 if (!argv
[1] || !strlen(argv
[1]))
797 report("goto requires an argument");
799 goto_id(view
, argv
[1], true, true);
802 } else if (!strcmp(cmd
, "save-display")) {
803 const char *path
= argv
[1] ? argv
[1] : "tig-display.txt";
805 if (!save_display(path
))
806 report("Failed to save screen to %s", path
);
808 report("Saved screen to %s", path
);
810 } else if (!strcmp(cmd
, "save-options")) {
811 const char *path
= argv
[1] ? argv
[1] : "tig-options.txt";
812 enum status_code code
= save_options(path
);
815 report("Failed to save options: %s", get_status_message(code
));
817 report("Saved options to %s", path
);
819 } else if (!strcmp(cmd
, "exec")) {
820 // argv may be allocated and mutations below will cause
821 // free() to error out so backup and restore. :(
822 const char *cmd
= argv
[1];
823 struct run_request req
= { view
->keymap
, {0}, argv
+ 1 };
824 enum status_code code
= parse_run_request_flags(&req
.flags
, argv
+ 1);
826 if (code
!= SUCCESS
) {
828 report("Failed to execute command: %s", get_status_message(code
));
830 request
= exec_run_request(view
, &req
);
835 } else if (!strcmp(cmd
, "toggle")) {
836 enum view_flag flags
= VIEW_NO_FLAGS
;
837 enum status_code code
= prompt_toggle(view
, argv
, &flags
);
838 const char *action
= get_status_message(code
);
840 if (code
!= SUCCESS
) {
841 report("%s", action
);
845 prompt_update_display(flags
);
848 report("%s", action
);
850 } else if (!strcmp(cmd
, "script")) {
851 enum status_code code
= open_script(argv
[1]);
854 report("%s", get_status_message(code
));
858 struct key key
= {{0}};
859 enum status_code code
;
860 enum view_flag flags
= VIEW_NO_FLAGS
;
863 key
.modifiers
.multibytes
= 1;
864 string_ncopy(key
.data
.bytes
, cmd
, cmdlen
);
865 request
= get_keybinding(view
->keymap
, &key
, 1, NULL
);
866 if (request
!= REQ_UNKNOWN
)
870 request
= get_request(cmd
);
871 if (request
!= REQ_UNKNOWN
)
874 code
= set_option(argv
[0], argv_size(argv
+ 1), &argv
[1]);
875 if (code
!= SUCCESS
) {
876 report("%s", get_status_message(code
));
880 if (!strcmp(cmd
, "set")) {
881 struct option_info
*toggle
;
883 toggle
= find_option_info(option_toggles
, ARRAY_SIZE(option_toggles
),
887 flags
= toggle
->flags
;
891 prompt_update_display(flags
);
894 request
= view_can_refresh(view
) ? REQ_REFRESH
: REQ_SCREEN_REDRAW
;
895 if (!strcmp(cmd
, "color"))
898 redraw_display(true);
906 exec_run_request(struct view
*view
, struct run_request
*req
)
908 const char **argv
= NULL
;
909 bool confirmed
= false;
910 enum request request
= REQ_NONE
;
911 char cmd
[SIZEOF_STR
];
912 const char *req_argv
[SIZEOF_ARG
];
915 if (!argv_to_string(req
->argv
, cmd
, sizeof(cmd
), " ")
916 || !argv_from_string_no_quotes(req_argv
, &req_argc
, cmd
)
917 || !argv_format(view
->env
, &argv
, req_argv
, false, true)) {
918 report("Failed to format arguments");
922 if (req
->flags
.internal
) {
923 request
= run_prompt_command(view
, argv
);
926 confirmed
= !req
->flags
.confirm
;
928 if (req
->flags
.confirm
) {
929 char cmd
[SIZEOF_STR
], prompt
[SIZEOF_STR
];
930 const char *and_exit
= req
->flags
.exit
? " and exit" : "";
932 if (argv_to_string_quoted(argv
, cmd
, sizeof(cmd
), " ") &&
933 string_format(prompt
, "Run `%s`%s?", cmd
, and_exit
) &&
934 prompt_yesno(prompt
)) {
940 open_external_viewer(argv
, repo
.cdup
, req
->flags
.silent
,
941 !req
->flags
.exit
, false, "");
948 if (request
== REQ_NONE
) {
949 if (req
->flags
.confirm
&& !confirmed
)
952 else if (req
->flags
.exit
)
955 else if (!req
->flags
.internal
&& watch_dirty(&view
->watch
))
956 request
= REQ_REFRESH
;
964 open_prompt(struct view
*view
)
966 char *cmd
= read_prompt(":");
967 const char *argv
[SIZEOF_ARG
] = { NULL
};
970 if (cmd
&& !argv_from_string(argv
, &argc
, cmd
)) {
971 report("Too many arguments");
975 return run_prompt_command(view
, argv
);
978 /* vim: set ts=8 sw=8 noexpandtab: */