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.
17 #include "tig/display.h"
18 #include "tig/options.h"
19 #include "tig/prompt.h"
20 #include "tig/pager.h"
21 #include "tig/types.h"
24 #include <readline/readline.h>
25 #include <readline/history.h>
26 #endif /* HAVE_READLINE */
29 prompt_input(const char *prompt
, struct input
*input
)
31 enum input_status status
= INPUT_OK
;
32 unsigned char chars_length
[SIZEOF_STR
];
34 size_t promptlen
= strlen(prompt
);
35 int pos
= 0, chars
= 0;
39 while (status
== INPUT_OK
|| status
== INPUT_SKIP
) {
41 update_status("%s%.*s", prompt
, pos
, input
->buf
);
43 if (get_input(pos
+ promptlen
, &key
, FALSE
) == OK
) {
44 int len
= strlen(key
.data
.bytes
);
46 if (pos
+ len
>= sizeof(input
->buf
)) {
47 report("Input string too long");
51 string_ncopy_do(input
->buf
+ pos
, sizeof(input
->buf
) - pos
, key
.data
.bytes
, len
);
53 chars_length
[chars
++] = len
;
54 status
= input
->handler(input
, &key
);
55 if (status
!= INPUT_OK
) {
59 int changed_pos
= strlen(input
->buf
);
61 if (changed_pos
!= pos
) {
63 chars_length
[chars
- 1] = changed_pos
- (pos
- len
);
67 status
= input
->handler(input
, &key
);
68 if (status
== INPUT_DELETE
) {
69 int len
= chars_length
[--chars
];
74 int changed_pos
= strlen(input
->buf
);
76 if (changed_pos
!= pos
) {
78 chars_length
[chars
++] = changed_pos
- pos
;
87 if (status
== INPUT_CANCEL
)
90 input
->buf
[pos
++] = 0;
95 static enum input_status
96 prompt_default_handler(struct input
*input
, struct key
*key
)
98 if (key
->modifiers
.multibytes
)
101 switch (key
->data
.value
) {
105 return *input
->buf
? INPUT_STOP
: INPUT_CANCEL
;
108 return *input
->buf
? INPUT_DELETE
: INPUT_CANCEL
;
118 static enum input_status
119 prompt_yesno_handler(struct input
*input
, struct key
*key
)
121 unsigned long c
= key_to_unicode(key
);
123 if (c
== 'y' || c
== 'Y')
125 if (c
== 'n' || c
== 'N')
127 return prompt_default_handler(input
, key
);
131 prompt_yesno(const char *prompt
)
133 char prompt2
[SIZEOF_STR
];
134 struct input input
= { prompt_yesno_handler
, NULL
};
136 if (!string_format(prompt2
, "%s [Yy/Nn]", prompt
))
139 return !!prompt_input(prompt2
, &input
);
142 struct incremental_input
{
144 input_handler handler
;
148 static enum input_status
149 read_prompt_handler(struct input
*input
, struct key
*key
)
151 struct incremental_input
*incremental
= (struct incremental_input
*) input
;
153 if (incremental
->edit_mode
&& !key
->modifiers
.multibytes
)
154 return prompt_default_handler(input
, key
);
156 if (!unicode_width(key_to_unicode(key
), 8))
159 if (!incremental
->handler
)
162 return incremental
->handler(input
, key
);
166 read_prompt_incremental(const char *prompt
, bool edit_mode
, input_handler handler
, void *data
)
168 static struct incremental_input incremental
= { { read_prompt_handler
} };
170 incremental
.input
.data
= data
;
171 incremental
.handler
= handler
;
172 incremental
.edit_mode
= edit_mode
;
174 return prompt_input(prompt
, (struct input
*) &incremental
);
179 readline_display(void)
181 wmove(status_win
, 0, 0);
182 waddstr(status_win
, rl_display_prompt
);
183 waddstr(status_win
, rl_line_buffer
);
184 wclrtoeol(status_win
);
185 wmove(status_win
, 0, strlen(rl_display_prompt
) + rl_point
);
186 wrefresh(status_win
);
190 readline_variable_generator(const char *text
, int state
)
192 static const char *vars
[] = {
193 #define FORMAT_VAR(name, ifempty, initval) "%(" #name ")"
194 ARGV_ENV_INFO(FORMAT_VAR
),
199 static int index
, len
;
201 char *variable
= NULL
; /* No match */
203 /* If it is a new word to complete, initialize */
209 /* Return the next name which partially matches */
210 while ((name
= vars
[index
])) {
213 /* Complete or format a variable */
214 if (strncmp(name
, text
, len
) == 0) {
215 if (strlen(name
) > len
)
216 variable
= strdup(name
);
218 variable
= argv_format_arg(&argv_env
, text
);
227 readline_action_generator(const char *text
, int state
)
229 static const char *actions
[] = {
237 #define REQ_GROUP(help)
238 #define REQ_(req, help) #req
245 static int index
, len
;
247 char *match
= NULL
; /* No match */
249 /* If it is a new word to complete, initialize */
255 /* Return the next name which partially matches */
256 while ((name
= actions
[index
])) {
257 name
= enum_name(name
);
260 if (strncmp(name
, text
, len
) == 0) {
261 /* Ignore exact completion */
262 if (strlen(name
) > len
)
263 match
= strdup(name
);
272 readline_set_generator(const char *text
, int state
)
274 static const char *words
[] = {
275 #define DEFINE_OPTION_NAME(name, type, flags) #name " = ",
276 OPTION_INFO(DEFINE_OPTION_NAME
)
277 #undef DEFINE_OPTION_NAME
281 static int index
, len
;
283 char *match
= NULL
; /* No match */
285 /* If it is a new word to complete, initialize */
291 /* Return the next name which partially matches */
292 while ((name
= words
[index
])) {
293 name
= enum_name(name
);
296 if (strncmp(name
, text
, len
) == 0) {
297 /* Ignore exact completion */
298 if (strlen(name
) > len
)
299 match
= strdup(name
);
308 readline_toggle_generator(const char *text
, int state
)
310 static const char **words
;
311 static int index
, len
;
313 char *match
= NULL
; /* No match */
316 /* TODO: Only complete column options that are defined
319 #define DEFINE_OPTION_WORD(name, type, flags) argv_append(&words, #name);
320 #define DEFINE_COLUMN_OPTIONS_WORD(name, type, flags) #name,
321 #define DEFINE_COLUMN_OPTIONS_WORDS(name, id, options) \
322 if (VIEW_COLUMN_##id != VIEW_COLUMN_SECTION) { \
323 const char *vars[] = { \
324 options(DEFINE_COLUMN_OPTIONS_WORD) \
326 char buf[SIZEOF_STR]; \
328 for (i = 0; i < ARRAY_SIZE(vars); i++) { \
329 if (enum_name_prefixed(buf, sizeof(buf), #name, vars[i])) \
330 argv_append(&words, buf); \
334 OPTION_INFO(DEFINE_OPTION_WORD
)
335 COLUMN_OPTIONS(DEFINE_COLUMN_OPTIONS_WORDS
);
338 /* If it is a new word to complete, initialize */
344 /* Return the next name which partially matches */
345 while ((name
= words
[index
])) {
346 name
= enum_name(name
);
349 if (strncmp(name
, text
, len
) == 0) {
350 /* Ignore exact completion */
351 if (strlen(name
) > len
)
352 match
= strdup(name
);
361 readline_getc(FILE *stream
)
363 return get_input_char();
367 readline_completion(const char *text
, int start
, int end
)
369 /* Do not append a space after a completion */
370 rl_completion_suppress_append
= 1;
373 * If the word is at the start of the line,
374 * then it is a tig action to complete.
377 return rl_completion_matches(text
, readline_action_generator
);
380 * If the line begins with "toggle", then we complete toggle options.
382 if (start
>= 7 && strncmp(rl_line_buffer
, "toggle ", 7) == 0)
383 return rl_completion_matches(text
, readline_toggle_generator
);
386 * If the line begins with "set", then we complete set options.
387 * (unless it is already completed)
389 if (start
>= 4 && strncmp(rl_line_buffer
, "set ", 4) == 0 &&
390 !strchr(rl_line_buffer
, '='))
391 return rl_completion_matches(text
, readline_set_generator
);
394 * Otherwise it might be a variable name...
396 if (strncmp(text
, "%(", 2) == 0)
397 return rl_completion_matches(text
, readline_variable_generator
);
400 * ... or finally fall back to filename completion.
406 readline_display_matches(char **matches
, int num_matches
, int max_length
)
410 wmove(status_win
, 0, 0);
411 waddstr(status_win
, "matches: ");
413 /* matches[0] is the incomplete word */
414 for (i
= 1; i
< num_matches
+ 1; ++i
) {
415 waddstr(status_win
, matches
[i
]);
416 waddch(status_win
, ' ');
420 wrefresh(status_win
);
426 /* Allow conditional parsing of the ~/.inputrc file. */
427 rl_readline_name
= "tig";
429 /* Word break caracters (we removed '(' to match variables) */
430 rl_basic_word_break_characters
= " \t\n\"\\'`@$><=;|&{";
432 /* Custom display function */
433 rl_redisplay_function
= readline_display
;
434 rl_getc_function
= readline_getc
;
436 /* Completion support */
437 rl_attempted_completion_function
= readline_completion
;
439 rl_completion_display_matches_hook
= readline_display_matches
;
443 read_prompt(const char *prompt
)
445 static char *line
= NULL
;
452 line
= readline(prompt
);
466 read_prompt(const char *prompt
)
468 return read_prompt_incremental(prompt
, TRUE
, NULL
, NULL
);
475 #endif /* HAVE_READLINE */
478 prompt_menu(const char *prompt
, const struct menu_item
*items
, int *selected
)
480 enum input_status status
= INPUT_OK
;
484 while (items
[size
].text
)
489 while (status
== INPUT_OK
) {
490 const struct menu_item
*item
= &items
[*selected
];
491 char hotkey
[] = { '[', (char) item
->hotkey
, ']', ' ', 0 };
494 update_status("%s (%d of %d) %s%s", prompt
, *selected
+ 1, size
,
495 item
->hotkey
? hotkey
: "", item
->text
);
497 switch (get_input(COLS
- 1, &key
, FALSE
)) {
506 *selected
= *selected
- 1;
508 *selected
= size
- 1;
513 *selected
= (*selected
+ 1) % size
;
517 status
= INPUT_CANCEL
;
521 for (i
= 0; items
[i
].text
; i
++)
522 if (items
[i
].hotkey
== key
.data
.bytes
[0]) {
532 return status
!= INPUT_CANCEL
;
535 struct prompt_toggle
{
538 enum view_flag flags
;
543 find_arg(const char *argv
[], const char *arg
)
547 for (i
= 0; argv
[i
]; i
++)
548 if (!strcmp(argv
[i
], arg
))
553 static enum status_code
554 prompt_toggle_option(struct view
*view
, const char *argv
[], const char *prefix
,
555 struct prompt_toggle
*toggle
, enum view_flag
*flags
)
557 char name
[SIZEOF_STR
];
559 if (!enum_name_prefixed(name
, sizeof(name
), prefix
, toggle
->name
))
560 return error("Failed to toggle option %s", toggle
->name
);
562 *flags
= toggle
->flags
;
564 if (!strcmp(toggle
->type
, "bool")) {
565 bool *opt
= toggle
->opt
;
568 if (opt
== &opt_mouse
)
570 return success("set %s = %s", name
, *opt
? "yes" : "no");
572 } else if (!strncmp(toggle
->type
, "enum", 4)) {
573 const char *type
= toggle
->type
+ STRING_SIZE("enum ");
574 enum author
*opt
= toggle
->opt
;
575 const struct enum_map
*map
= find_enum_map(type
);
577 *opt
= (*opt
+ 1) % map
->size
;
578 return success("set %s = %s", name
, enum_name(map
->entries
[*opt
].name
));
580 } else if (!strcmp(toggle
->type
, "int")) {
581 const char *arg
= argv
[2] ? argv
[2] : "1";
582 int diff
= atoi(arg
);
583 int *opt
= toggle
->opt
;
586 diff
= *arg
== '-' ? -1 : 1;
588 if (opt
== &opt_diff_context
&& *opt
< 0)
590 if (opt
== &opt_diff_context
&& diff
< 0) {
592 return error("Diff context cannot be less than zero");
597 if (strstr(name
, "commit-title-overflow")) {
598 *opt
= *opt
? -*opt
: 50;
600 return success("set %s = no", name
);
605 return success("set %s = %d", name
, *opt
);
607 } else if (!strcmp(toggle
->type
, "double")) {
608 const char *arg
= argv
[2] ? argv
[2] : "1.0";
609 double *opt
= toggle
->opt
;
618 if (parse_step(&diff
, arg
) != SUCCESS
)
619 diff
= strtod(arg
, NULL
);
622 return success("set %s = %.2f", name
, *opt
);
624 } else if (!strcmp(toggle
->type
, "const char **")) {
625 const char ***opt
= toggle
->opt
;
629 for (i
= 2; argv
[i
]; i
++) {
630 if (!find_arg(*opt
, argv
[i
])) {
639 for (next
= 0, pos
= 0; (*opt
)[pos
]; pos
++) {
640 const char *arg
= (*opt
)[pos
];
642 if (find_arg(argv
+ 2, arg
)) {
646 (*opt
)[next
++] = arg
;
651 } else if (!argv_copy(opt
, argv
+ 2)) {
652 return ERROR_OUT_OF_MEMORY
;
657 return error("Unsupported `:toggle %s` (%s)", name
, toggle
->type
);
661 static struct prompt_toggle
*
662 find_prompt_toggle(struct prompt_toggle toggles
[], size_t toggles_size
,
663 const char *prefix
, const char *name
, size_t namelen
)
665 char prefixed
[SIZEOF_STR
];
668 if (*prefix
&& namelen
== strlen(prefix
) &&
669 !string_enum_compare(prefix
, name
, namelen
)) {
671 namelen
= strlen(name
);
674 for (i
= 0; i
< toggles_size
; i
++) {
675 struct prompt_toggle
*toggle
= &toggles
[i
];
677 if (namelen
== strlen(toggle
->name
) &&
678 !string_enum_compare(toggle
->name
, name
, namelen
))
681 if (enum_name_prefixed(prefixed
, sizeof(prefixed
), prefix
, toggle
->name
) &&
682 namelen
== strlen(prefixed
) &&
683 !string_enum_compare(prefixed
, name
, namelen
))
690 static enum status_code
691 prompt_toggle(struct view
*view
, const char *argv
[], enum view_flag
*flags
)
693 struct prompt_toggle option_toggles
[] = {
694 #define DEFINE_OPTION_TOGGLES(name, type, flags) { #name, #type, flags, &opt_ ## name },
695 OPTION_INFO(DEFINE_OPTION_TOGGLES
)
697 const char *option
= argv
[1];
698 size_t optionlen
= option
? strlen(option
) : 0;
699 struct prompt_toggle
*toggle
;
700 struct view_column
*column
;
703 return error("%s", "No option name given to :toggle");
705 if (enum_equals_static("sort-field", option
, optionlen
) ||
706 enum_equals_static("sort-order", option
, optionlen
)) {
707 if (!view_has_flags(view
, VIEW_SORTABLE
)) {
708 return error("Sorting is not yet supported for the %s view", view
->name
);
710 bool sort_field
= enum_equals_static("sort-field", option
, optionlen
);
711 struct sort_state
*sort
= &view
->sort
;
713 sort_view(view
, sort_field
);
714 return success("set %s = %s", option
,
715 sort_field
? view_column_name(get_sort_field(view
))
716 : sort
->reverse
? "descending" : "ascending");
720 toggle
= find_prompt_toggle(option_toggles
, ARRAY_SIZE(option_toggles
),
721 "", option
, optionlen
);
723 return prompt_toggle_option(view
, argv
, "", toggle
, flags
);
725 #define DEFINE_COLUMN_OPTIONS_TOGGLE(name, type, flags) \
726 { #name, #type, flags, &opt->name },
728 #define DEFINE_COLUMN_OPTIONS_CHECK(name, id, options) \
729 if (column->type == VIEW_COLUMN_##id) { \
730 struct name##_options *opt = &column->opt.name; \
731 struct prompt_toggle toggles[] = { \
732 options(DEFINE_COLUMN_OPTIONS_TOGGLE) \
734 toggle = find_prompt_toggle(toggles, ARRAY_SIZE(toggles), #name, option, optionlen); \
736 return prompt_toggle_option(view, argv, #name, toggle, flags); \
739 for (column
= view
->columns
; column
; column
= column
->next
) {
740 COLUMN_OPTIONS(DEFINE_COLUMN_OPTIONS_CHECK
);
743 return error("`:toggle %s` not supported", option
);
747 run_prompt_command(struct view
*view
, const char *argv
[])
749 enum request request
;
750 const char *cmd
= argv
[0];
751 size_t cmdlen
= cmd
? strlen(cmd
) : 0;
756 if (string_isnumber(cmd
)) {
757 int lineno
= view
->pos
.lineno
+ 1;
759 if (parse_int(&lineno
, cmd
, 0, view
->lines
+ 1) == SUCCESS
) {
762 select_view_line(view
, lineno
- 1);
765 report("Unable to parse '%s' as a line number", cmd
);
767 } else if (iscommit(cmd
)) {
770 if (!(view
->ops
->column_bits
& view_column_bit(ID
))) {
771 report("Jumping to commits is not supported by the %s view", view
->name
);
775 for (lineno
= 0; lineno
< view
->lines
; lineno
++) {
776 struct view_column_data column_data
= {};
777 struct line
*line
= &view
->line
[lineno
];
779 if (view
->ops
->get_column_data(view
, line
, &column_data
) &&
781 !strncasecmp(column_data
.id
, cmd
, cmdlen
)) {
782 string_ncopy(view
->env
->search
, cmd
, cmdlen
);
783 select_view_line(view
, lineno
);
789 report("Unable to find commit '%s'", view
->env
->search
);
792 } else if (cmdlen
> 1 && (cmd
[0] == '/' || cmd
[0] == '?')) {
793 char search
[SIZEOF_STR
];
795 if (!argv_to_string(argv
, search
, sizeof(search
), " ")) {
796 report("Failed to copy search string");
800 if (!strcmp(search
+ 1, view
->env
->search
))
801 return cmd
[0] == '/' ? REQ_FIND_NEXT
: REQ_FIND_PREV
;
803 string_ncopy(view
->env
->search
, search
+ 1, strlen(search
+ 1));
804 return cmd
[0] == '/' ? REQ_SEARCH
: REQ_SEARCH_BACK
;
806 } else if (cmdlen
> 1 && cmd
[0] == '!') {
807 struct view
*next
= &pager_view
;
810 /* Trim the leading '!'. */
812 copied
= argv_format(view
->env
, &next
->argv
, argv
, FALSE
, TRUE
);
816 report("Argument formatting failed");
818 /* When running random commands, initially show the
819 * command in the title. However, it maybe later be
820 * overwritten if a commit line is selected. */
821 argv_to_string(next
->argv
, next
->ref
, sizeof(next
->ref
), " ");
824 open_pager_view(view
, OPEN_PREPARED
| OPEN_WITH_STDERR
);
827 } else if (!strcmp(cmd
, "save-display")) {
828 const char *path
= argv
[1] ? argv
[1] : "tig-display.txt";
830 if (!save_display(path
))
831 report("Failed to save screen to %s", path
);
833 report("Saved screen to %s", path
);
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
);
841 if (code
!= SUCCESS
) {
842 report("%s", action
);
846 if (flags
& VIEW_RESET_DISPLAY
) {
848 redraw_display(TRUE
);
851 foreach_displayed_view(view
, i
) {
852 if (view_has_flags(view
, flags
) && view_can_refresh(view
))
859 report("%s", action
);
861 } else if (!strcmp(cmd
, "script")) {
862 if (is_script_executing()) {
863 report("Scripts cannot be run from scripts");
864 } else if (!open_script(argv
[1])) {
865 report("Failed to open %s", argv
[1]);
872 key
.modifiers
.multibytes
= 1;
873 string_ncopy(key
.data
.bytes
, cmd
, cmdlen
);
874 request
= get_keybinding(view
->keymap
, &key
, 1);
875 if (request
!= REQ_NONE
)
879 request
= get_request(cmd
);
880 if (request
!= REQ_UNKNOWN
)
883 if (set_option(argv
[0], argv_size(argv
+ 1), &argv
[1]) == SUCCESS
) {
884 request
= view_can_refresh(view
) ? REQ_REFRESH
: REQ_SCREEN_REDRAW
;
885 if (!strcmp(cmd
, "color"))
888 redraw_display(TRUE
);
896 open_prompt(struct view
*view
)
898 char *cmd
= read_prompt(":");
899 const char *argv
[SIZEOF_ARG
] = { NULL
};
902 if (cmd
&& !argv_from_string(argv
, &argc
, cmd
)) {
903 report("Too many arguments");
907 return run_prompt_command(view
, argv
);
910 /* vim: set ts=8 sw=8 noexpandtab: */