/** \file input.c Functions for reading a character of input from stdin. */ #include "config.h" #include #include #include #include #include #include #include #include #ifdef HAVE_SYS_IOCTL_H #include #endif #include #include #if HAVE_NCURSES_H #include #else #include #endif #if HAVE_TERM_H #include #elif HAVE_NCURSES_TERM_H #include #endif #include #include #include #include "fallback.h" #include "util.h" #include "wutil.h" #include "reader.h" #include "proc.h" #include "common.h" #include "sanity.h" #include "input_common.h" #include "input.h" #include "parser.h" #include "env.h" #include "expand.h" #include "event.h" #include "signal.h" #include "output.h" #include "intern.h" #include #define DEFAULT_TERM L"ansi" #define FISH_BIND_MODE_VAR L"fish_bind_mode" /** Struct representing a keybinding. Returned by input_get_mappings. */ struct input_mapping_t { wcstring seq; /**< Character sequence which generates this event */ std::vector commands; /**< commands that should be evaluated by this mapping */ wcstring mode; /**< mode in which this command should be evaluated */ wcstring sets_mode; /** new mode that should be switched to after command evaluation */ input_mapping_t(const wcstring &s, const std::vector &c, const wcstring &m = DEFAULT_BIND_MODE, const wcstring &sm = DEFAULT_BIND_MODE) : seq(s), commands(c), mode(m), sets_mode(sm) {} }; /** A struct representing the mapping from a terminfo key name to a terminfo character sequence */ struct terminfo_mapping_t { const wchar_t *name; /**< Name of key */ const char *seq; /**< Character sequence generated on keypress. Constant string. */ }; /** Names of all the input functions supported */ static const wchar_t * const name_arr[] = { L"beginning-of-line", L"end-of-line", L"forward-char", L"backward-char", L"forward-word", L"backward-word", L"history-search-backward", L"history-search-forward", L"delete-char", L"backward-delete-char", L"kill-line", L"yank", L"yank-pop", L"complete", L"beginning-of-history", L"end-of-history", L"backward-kill-line", L"kill-whole-line", L"kill-word", L"backward-kill-word", L"backward-kill-path-component", L"dump-functions", L"history-token-search-backward", L"history-token-search-forward", L"self-insert", L"transpose-chars", L"transpose-words", L"upcase-word", L"downcase-word", L"capitalize-word", L"null", L"eof", L"vi-arg-digit", L"execute", L"beginning-of-buffer", L"end-of-buffer", L"repaint", L"force-repaint", L"up-line", L"down-line", L"suppress-autosuggestion", L"accept-autosuggestion", L"begin-selection", L"end-selection", } ; /** Description of each supported input function */ /* static const wchar_t *desc_arr[] = { L"Move to beginning of line", L"Move to end of line", L"Move forward one character", L"Move backward one character", L"Move forward one word", L"Move backward one word", L"Search backward through list of previous commands", L"Search forward through list of previous commands", L"Delete one character forward", L"Delete one character backward", L"Move contents from cursor to end of line to killring", L"Paste contents of killring", L"Rotate to previous killring entry", L"Guess the rest of the next input token", L"Move to first item of history", L"Move to last item of history", L"Clear current line", L"Move contents from beginning of line to cursor to killring", L"Move entire line to killring", L"Move next word to killring", L"Move previous word to killring", L"Write out key bindings", L"Clear entire screen", L"Quit the running program", L"Search backward through list of previous commands for matching token", L"Search forward through list of previous commands for matching token", L"Insert the pressed key", L"Do nothing", L"End of file", L"Repeat command" } ; */ /** Internal code for each supported input function */ static const wchar_t code_arr[] = { R_BEGINNING_OF_LINE, R_END_OF_LINE, R_FORWARD_CHAR, R_BACKWARD_CHAR, R_FORWARD_WORD, R_BACKWARD_WORD, R_HISTORY_SEARCH_BACKWARD, R_HISTORY_SEARCH_FORWARD, R_DELETE_CHAR, R_BACKWARD_DELETE_CHAR, R_KILL_LINE, R_YANK, R_YANK_POP, R_COMPLETE, R_BEGINNING_OF_HISTORY, R_END_OF_HISTORY, R_BACKWARD_KILL_LINE, R_KILL_WHOLE_LINE, R_KILL_WORD, R_BACKWARD_KILL_WORD, R_BACKWARD_KILL_PATH_COMPONENT, R_DUMP_FUNCTIONS, R_HISTORY_TOKEN_SEARCH_BACKWARD, R_HISTORY_TOKEN_SEARCH_FORWARD, R_SELF_INSERT, R_TRANSPOSE_CHARS, R_TRANSPOSE_WORDS, R_UPCASE_WORD, R_DOWNCASE_WORD, R_CAPITALIZE_WORD, R_NULL, R_EOF, R_VI_ARG_DIGIT, R_EXECUTE, R_BEGINNING_OF_BUFFER, R_END_OF_BUFFER, R_REPAINT, R_FORCE_REPAINT, R_UP_LINE, R_DOWN_LINE, R_SUPPRESS_AUTOSUGGESTION, R_ACCEPT_AUTOSUGGESTION, R_BEGIN_SELECTION, R_END_SELECTION } ; /** Mappings for the current input mode */ static std::vector mapping_list; /* Terminfo map list */ static std::vector terminfo_mappings; #define TERMINFO_ADD(key) { (L ## #key) + 4, key } /** List of all terminfo mappings */ static std::vector mappings; /** Set to one when the input subsytem has been initialized. */ static bool is_init = false; /** Initialize terminfo. */ static void input_terminfo_init(); /** Return the current bind mode */ const wchar_t *input_get_bind_mode() { const wchar_t *bind_mode = DEFAULT_BIND_MODE; const env_var_t bind_mode_var = env_get_string(FISH_BIND_MODE_VAR); if(!bind_mode_var.missing()) { bind_mode = bind_mode_var.c_str(); } return bind_mode; } /** Set the current bind mode */ bool input_set_bind_mode(const wchar_t *bm) { env_set(FISH_BIND_MODE_VAR, bm, ENV_GLOBAL); return true; } /** Returns the function description for the given function code. */ void input_mapping_add(const wchar_t *sequence, const wchar_t **commands, size_t commands_len, const wchar_t *mode, const wchar_t *sets_mode) { CHECK(sequence,); CHECK(commands,); CHECK(mode,); CHECK(sets_mode,); // debug( 0, L"Add mapping from %ls to %ls in mode %ls", escape(sequence, 1), escape(command, 1 ), mode); std::vector commands_vector(commands, commands + commands_len); for (size_t i=0; i(fish_term256); } else { env_var_t term = env_get_string(L"TERM"); if (term.missing()) { support_term256 = false; } else if (term.find(L"256color") != wcstring::npos) { /* Explicitly supported */ support_term256 = true; } else if (term.find(L"xterm") != wcstring::npos) { // assume that all xterms are 256, except for OS X SnowLeopard env_var_t prog = env_get_string(L"TERM_PROGRAM"); support_term256 = (prog != L"Apple_Terminal"); } else { // Don't know, default to false support_term256 = false; } } output_set_supports_term256(support_term256); } int input_init() { if (is_init) return 1; is_init = true; input_common_init(&interrupt_handler); const env_var_t term = env_get_string(L"TERM"); int errret; if (setupterm(0, STDOUT_FILENO, &errret) == ERR) { debug(0, _(L"Could not set up terminal")); if (errret == 0) { debug(0, _(L"Check that your terminal type, '%ls', is supported on this system"), term.c_str()); debug(0, _(L"Attempting to use '%ls' instead"), DEFAULT_TERM); env_set(L"TERM", DEFAULT_TERM, ENV_GLOBAL | ENV_EXPORT); const std::string default_term = wcs2string(DEFAULT_TERM); if (setupterm(const_cast(default_term.c_str()), STDOUT_FILENO, &errret) == ERR) { debug(0, _(L"Could not set up terminal")); exit_without_destructors(1); } } else { exit_without_destructors(1); } } assert(! term.missing()); output_set_term(term); input_terminfo_init(); update_fish_term256(); /* If we have no keybindings, add a few simple defaults */ if (mapping_list.empty()) { input_mapping_add(L"", L"self-insert"); input_mapping_add(L"\n", L"execute"); input_mapping_add(L"\t", L"complete"); input_mapping_add(L"\x3", L"commandline \"\""); input_mapping_add(L"\x4", L"exit"); input_mapping_add(L"\x5", L"bind"); } return 1; } void input_destroy() { if (!is_init) return; is_init = false; input_common_destroy(); if (fish_del_curterm(cur_term) == ERR) { debug(0, _(L"Error while closing terminfo")); } } /** Perform the action of the specified binding */ static void input_mapping_execute(const input_mapping_t &m) { for(int i = m.commands.size() - 1; i >= 0; i--) { wcstring command = m.commands.at(i); wchar_t code = input_function_get_code(command); if (code != (wchar_t)-1) { input_unreadch(code); } else { /* This key sequence is bound to a command, which is sent to the parser for evaluation. */ int last_status = proc_get_last_status(); parser_t::principal_parser().eval(command.c_str(), io_chain_t(), TOP); proc_set_last_status(last_status); input_unreadch(R_NULL); } } input_set_bind_mode(m.sets_mode.c_str()); } /** Try reading the specified function mapping */ static bool input_mapping_is_match(const input_mapping_t &m) { wint_t c = 0; int j; //debug(0, L"trying mapping %ls\n", escape(m.seq.c_str(), 1)); const wchar_t *str = m.seq.c_str(); for (j=0; str[j] != L'\0'; j++) { bool timed = (j > 0 && iswcntrl(str[0])); c = input_common_readch(timed); if (str[j] != c) { break; } } if (str[j] == L'\0') { //debug(0, L"matched mapping %ls (%ls)\n", escape(m.seq.c_str(), 1), m.command.c_str()); /* We matched the entire sequence */ return true; } else { int k; /* Return the read characters */ input_unreadch(c); for (k=j-1; k>=0; k--) { input_unreadch(m.seq[k]); } } return false; } void input_unreadch(wint_t ch) { input_common_unreadch(ch); } static void input_mapping_execute_matching_or_generic() { const input_mapping_t *generic = NULL; const wchar_t *bind_mode = input_get_bind_mode(); for (int i = 0; i < mapping_list.size(); i++) { const input_mapping_t &m = mapping_list.at(i); //debug(0, L"trying mapping (%ls,%ls,%ls)\n", escape(m.seq.c_str(), 1), // m.mode.c_str(), m.sets_mode.c_str()); if(wcscmp(m.mode.c_str(), bind_mode)) { //debug(0, L"skipping mapping because mode %ls != %ls\n", m.mode.c_str(), input_get_bind_mode()); continue; } if (m.seq.length() == 0) { generic = &m; } else if(input_mapping_is_match(m)) { input_mapping_execute(m); return; } } if (generic) { input_mapping_execute(*generic); } else { //debug(0, L"no generic found, ignoring..."); wchar_t c = input_common_readch(0); if (c == R_EOF) input_common_unreadch(c); } } wint_t input_readch() { CHECK_BLOCK(R_NULL); /* Clear the interrupted flag */ reader_reset_interrupted(); /* Search for sequence in mapping tables */ while (1) { wchar_t c = input_common_readch(0); if (c >= R_MIN && c <= R_MAX) { switch (c) { case R_EOF: /* If it's closed, then just return */ { return WEOF; } case R_SELF_INSERT: { return input_common_readch(0); } default: { return c; } } } else { input_unreadch(c); input_mapping_execute_matching_or_generic(); } } } void input_mapping_get_names(wcstring_list_t &lst) { size_t i; for (i=0; i &cmds, wcstring &mode, wcstring &sets_mode) { size_t i, sz = mapping_list.size(); for (i=0; i