Allow scripts to go through the readline backend
[tig.git] / src / prompt.c
blob7f642f765dce8b2f48e139ce178e4af6e600c7c4
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 #include "tig/tig.h"
15 #include "tig/view.h"
16 #include "tig/draw.h"
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"
23 #ifdef HAVE_READLINE
24 #include <readline/readline.h>
25 #include <readline/history.h>
26 #endif /* HAVE_READLINE */
28 static char *
29 prompt_input(const char *prompt, struct input *input)
31 enum input_status status = INPUT_OK;
32 unsigned char chars_length[SIZEOF_STR];
33 struct key key;
34 size_t promptlen = strlen(prompt);
35 int pos = 0, chars = 0;
37 input->buf[pos] = 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");
48 return NULL;
51 string_ncopy_do(input->buf + pos, sizeof(input->buf) - pos, key.data.bytes, len);
52 pos += len;
53 chars_length[chars++] = len;
54 status = input->handler(input, &key);
55 if (status != INPUT_OK) {
56 pos -= len;
57 chars--;
58 } else {
59 int changed_pos = strlen(input->buf);
61 if (changed_pos != pos) {
62 pos = changed_pos;
63 chars_length[chars - 1] = changed_pos - (pos - len);
66 } else {
67 status = input->handler(input, &key);
68 if (status == INPUT_DELETE) {
69 int len = chars_length[--chars];
71 pos -= len;
72 status = INPUT_OK;
73 } else {
74 int changed_pos = strlen(input->buf);
76 if (changed_pos != pos) {
77 pos = changed_pos;
78 chars_length[chars++] = changed_pos - pos;
82 input->buf[pos] = 0;
85 report_clear();
87 if (status == INPUT_CANCEL)
88 return NULL;
90 input->buf[pos++] = 0;
92 return input->buf;
95 static enum input_status
96 prompt_default_handler(struct input *input, struct key *key)
98 if (key->modifiers.multibytes)
99 return INPUT_SKIP;
101 switch (key->data.value) {
102 case KEY_RETURN:
103 case KEY_ENTER:
104 case '\n':
105 return *input->buf ? INPUT_STOP : INPUT_CANCEL;
107 case KEY_BACKSPACE:
108 return *input->buf ? INPUT_DELETE : INPUT_CANCEL;
110 case KEY_ESC:
111 return INPUT_CANCEL;
113 default:
114 return INPUT_SKIP;
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')
124 return INPUT_STOP;
125 if (c == 'n' || c == 'N')
126 return INPUT_CANCEL;
127 return prompt_default_handler(input, key);
130 bool
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))
137 return FALSE;
139 return !!prompt_input(prompt2, &input);
142 struct incremental_input {
143 struct input input;
144 input_handler handler;
145 bool edit_mode;
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))
157 return INPUT_SKIP;
159 if (!incremental->handler)
160 return INPUT_OK;
162 return incremental->handler(input, key);
165 char *
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);
177 #ifdef HAVE_READLINE
178 static void
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);
189 static char *
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),
195 #undef FORMAT_VAR
196 NULL
199 static int index, len;
200 const char *name;
201 char *variable = NULL; /* No match */
203 /* If it is a new word to complete, initialize */
204 if (!state) {
205 index = 0;
206 len = strlen(text);
209 /* Return the next name which partially matches */
210 while ((name = vars[index])) {
211 index++;
213 /* Complete or format a variable */
214 if (strncmp(name, text, len) == 0) {
215 if (strlen(name) > len)
216 variable = strdup(name);
217 else
218 variable = argv_format_arg(&argv_env, text);
219 break;
223 return variable;
226 static char *
227 readline_action_generator(const char *text, int state)
229 static const char *actions[] = {
230 "!",
231 "source",
232 "color",
233 "bind",
234 "set",
235 "toggle",
236 "save-display",
237 #define REQ_GROUP(help)
238 #define REQ_(req, help) #req
239 REQ_INFO,
240 #undef REQ_GROUP
241 #undef REQ_
242 NULL
245 static int index, len;
246 const char *name;
247 char *match = NULL; /* No match */
249 /* If it is a new word to complete, initialize */
250 if (!state) {
251 index = 0;
252 len = strlen(text);
255 /* Return the next name which partially matches */
256 while ((name = actions[index])) {
257 name = enum_name(name);
258 index++;
260 if (strncmp(name, text, len) == 0) {
261 /* Ignore exact completion */
262 if (strlen(name) > len)
263 match = strdup(name);
264 break;
268 return match;
271 static char *
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
278 NULL
281 static int index, len;
282 const char *name;
283 char *match = NULL; /* No match */
285 /* If it is a new word to complete, initialize */
286 if (!state) {
287 index = 0;
288 len = strlen(text);
291 /* Return the next name which partially matches */
292 while ((name = words[index])) {
293 name = enum_name(name);
294 index++;
296 if (strncmp(name, text, len) == 0) {
297 /* Ignore exact completion */
298 if (strlen(name) > len)
299 match = strdup(name);
300 break;
304 return match;
307 static char *
308 readline_toggle_generator(const char *text, int state)
310 static const char **words;
311 static int index, len;
312 const char *name;
313 char *match = NULL; /* No match */
315 if (!words) {
316 /* TODO: Only complete column options that are defined
317 * for the view. */
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) \
325 }; \
326 char buf[SIZEOF_STR]; \
327 int i; \
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 */
339 if (!state) {
340 index = 0;
341 len = strlen(text);
344 /* Return the next name which partially matches */
345 while ((name = words[index])) {
346 name = enum_name(name);
347 index++;
349 if (strncmp(name, text, len) == 0) {
350 /* Ignore exact completion */
351 if (strlen(name) > len)
352 match = strdup(name);
353 break;
357 return match;
360 static int
361 readline_getc(FILE *stream)
363 return get_input_char();
366 static 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.
376 if (start == 0)
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.
402 return NULL;
405 static void
406 readline_display_matches(char **matches, int num_matches, int max_length)
408 unsigned int i;
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, ' ');
419 wgetch(status_win);
420 wrefresh(status_win);
423 static void
424 readline_init(void)
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;
442 char *
443 read_prompt(const char *prompt)
445 static char *line = NULL;
447 if (line) {
448 free(line);
449 line = NULL;
452 line = readline(prompt);
453 if (line && *line)
454 add_history(line);
456 return line;
459 void
460 prompt_init(void)
462 readline_init();
464 #else
465 char *
466 read_prompt(const char *prompt)
468 return read_prompt_incremental(prompt, TRUE, NULL, NULL);
471 void
472 prompt_init(void)
475 #endif /* HAVE_READLINE */
477 bool
478 prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
480 enum input_status status = INPUT_OK;
481 struct key key;
482 int size = 0;
484 while (items[size].text)
485 size++;
487 assert(size > 0);
489 while (status == INPUT_OK) {
490 const struct menu_item *item = &items[*selected];
491 char hotkey[] = { '[', (char) item->hotkey, ']', ' ', 0 };
492 int i;
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)) {
498 case KEY_RETURN:
499 case KEY_ENTER:
500 case '\n':
501 status = INPUT_STOP;
502 break;
504 case KEY_LEFT:
505 case KEY_UP:
506 *selected = *selected - 1;
507 if (*selected < 0)
508 *selected = size - 1;
509 break;
511 case KEY_RIGHT:
512 case KEY_DOWN:
513 *selected = (*selected + 1) % size;
514 break;
516 case KEY_ESC:
517 status = INPUT_CANCEL;
518 break;
520 default:
521 for (i = 0; items[i].text; i++)
522 if (items[i].hotkey == key.data.bytes[0]) {
523 *selected = i;
524 status = INPUT_STOP;
525 break;
530 report_clear();
532 return status != INPUT_CANCEL;
535 struct prompt_toggle {
536 const char *name;
537 const char *type;
538 enum view_flag flags;
539 void *opt;
542 static bool
543 find_arg(const char *argv[], const char *arg)
545 int i;
547 for (i = 0; argv[i]; i++)
548 if (!strcmp(argv[i], arg))
549 return TRUE;
550 return FALSE;
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;
567 *opt = !*opt;
568 if (opt == &opt_mouse)
569 enable_mouse(*opt);
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;
585 if (!diff)
586 diff = *arg == '-' ? -1 : 1;
588 if (opt == &opt_diff_context && *opt < 0)
589 *opt = -*opt;
590 if (opt == &opt_diff_context && diff < 0) {
591 if (!*opt)
592 return error("Diff context cannot be less than zero");
593 if (*opt < -diff)
594 diff = -*opt;
597 if (strstr(name, "commit-title-overflow")) {
598 *opt = *opt ? -*opt : 50;
599 if (*opt < 0)
600 return success("set %s = no", name);
601 diff = 0;
604 *opt += diff;
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;
610 int sign = 1;
611 double diff;
613 if (*arg == '-') {
614 sign = -1;
615 arg++;
618 if (parse_step(&diff, arg) != SUCCESS)
619 diff = strtod(arg, NULL);
621 *opt += sign * diff;
622 return success("set %s = %.2f", name, *opt);
624 } else if (!strcmp(toggle->type, "const char **")) {
625 const char ***opt = toggle->opt;
626 bool found = TRUE;
627 int i;
629 for (i = 2; argv[i]; i++) {
630 if (!find_arg(*opt, argv[i])) {
631 found = FALSE;
632 break;
636 if (found) {
637 int next, pos;
639 for (next = 0, pos = 0; (*opt)[pos]; pos++) {
640 const char *arg = (*opt)[pos];
642 if (find_arg(argv + 2, arg)) {
643 free((void *) arg);
644 continue;
646 (*opt)[next++] = arg;
649 (*opt)[next] = NULL;
651 } else if (!argv_copy(opt, argv + 2)) {
652 return ERROR_OUT_OF_MEMORY;
654 return SUCCESS;
656 } else {
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];
666 int i;
668 if (*prefix && namelen == strlen(prefix) &&
669 !string_enum_compare(prefix, name, namelen)) {
670 name = "display";
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))
679 return toggle;
681 if (enum_name_prefixed(prefixed, sizeof(prefixed), prefix, toggle->name) &&
682 namelen == strlen(prefixed) &&
683 !string_enum_compare(prefixed, name, namelen))
684 return toggle;
687 return NULL;
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;
702 if (!option)
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);
709 } else {
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);
722 if (toggle)
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) \
733 }; \
734 toggle = find_prompt_toggle(toggles, ARRAY_SIZE(toggles), #name, option, optionlen); \
735 if (toggle) \
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);
746 enum request
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;
753 if (!cmd)
754 return REQ_NONE;
756 if (string_isnumber(cmd)) {
757 int lineno = view->pos.lineno + 1;
759 if (parse_int(&lineno, cmd, 0, view->lines + 1) == SUCCESS) {
760 if (!lineno)
761 lineno = 1;
762 select_view_line(view, lineno - 1);
763 report_clear();
764 } else {
765 report("Unable to parse '%s' as a line number", cmd);
767 } else if (iscommit(cmd)) {
768 int lineno;
770 if (!(view->ops->column_bits & view_column_bit(ID))) {
771 report("Jumping to commits is not supported by the %s view", view->name);
772 return REQ_NONE;
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) &&
780 column_data.id &&
781 !strncasecmp(column_data.id, cmd, cmdlen)) {
782 string_ncopy(view->env->search, cmd, cmdlen);
783 select_view_line(view, lineno);
784 report_clear();
785 return REQ_NONE;
789 report("Unable to find commit '%s'", view->env->search);
790 return REQ_NONE;
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");
797 return REQ_NONE;
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;
808 bool copied;
810 /* Trim the leading '!'. */
811 argv[0] = cmd + 1;
812 copied = argv_format(view->env, &next->argv, argv, FALSE, TRUE);
813 argv[0] = cmd;
815 if (!copied) {
816 report("Argument formatting failed");
817 } else {
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), " ");
823 next->dir = NULL;
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);
832 else
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);
839 int i;
841 if (code != SUCCESS) {
842 report("%s", action);
843 return REQ_NONE;
846 if (flags & VIEW_RESET_DISPLAY) {
847 resize_display();
848 redraw_display(TRUE);
851 foreach_displayed_view(view, i) {
852 if (view_has_flags(view, flags) && view_can_refresh(view))
853 reload_view(view);
854 else
855 redraw_view(view);
858 if (*action)
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]);
868 } else {
869 struct key key = {};
871 /* Try :<key> */
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)
876 return request;
878 /* Try :<command> */
879 request = get_request(cmd);
880 if (request != REQ_UNKNOWN)
881 return request;
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"))
886 init_colors();
887 resize_display();
888 redraw_display(TRUE);
890 return request;
892 return REQ_NONE;
895 enum request
896 open_prompt(struct view *view)
898 char *cmd = read_prompt(":");
899 const char *argv[SIZEOF_ARG] = { NULL };
900 int argc = 0;
902 if (cmd && !argv_from_string(argv, &argc, cmd)) {
903 report("Too many arguments");
904 return REQ_NONE;
907 return run_prompt_command(view, argv);
910 /* vim: set ts=8 sw=8 noexpandtab: */