From 272ca37582278ba1edbc3101c6855864bcd1b926 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Mon, 3 Aug 2020 14:53:09 -0700 Subject: [PATCH] Remove most dynamic reader configuration Have the reader accept a constant configuration object, which controls whether autosuggestions, etc. are enabled. These things don't change dynamically. --- src/builtin_read.cpp | 31 ++++----- src/reader.cpp | 146 +++++++++++++++++-------------------------- src/reader.h | 61 +++++++++--------- 3 files changed, 107 insertions(+), 131 deletions(-) diff --git a/src/builtin_read.cpp b/src/builtin_read.cpp index 2ebecaff7..b8df208b1 100644 --- a/src/builtin_read.cpp +++ b/src/builtin_read.cpp @@ -201,22 +201,25 @@ static int read_interactive(parser_t &parser, wcstring &buff, int nchars, bool s const wchar_t *commandline) { int exit_res = STATUS_CMD_OK; - // Don't keep history - reader_push(parser, L""); - reader_get_history()->resolve_pending(); + // Construct a configuration. + reader_config_t conf; + conf.complete_ok = shell; + conf.highlight_ok = shell; + conf.syntax_check_ok = shell; - reader_set_left_prompt(prompt); - reader_set_right_prompt(right_prompt); - if (shell) { - reader_set_complete_ok(true); - reader_set_highlight_ok(true); - reader_set_syntax_check_ok(true); - } // No autosuggestions or abbreviations in builtin_read. - reader_set_allow_autosuggesting(false); - reader_set_expand_abbreviations(false); - reader_set_exit_on_interrupt(true); - reader_set_silent_status(silent); + conf.autosuggest_ok = false; + conf.expand_abbrev_ok = false; + + conf.exit_on_interrupt = true; + conf.in_silent_mode = silent; + + conf.left_prompt_cmd = prompt; + conf.right_prompt_cmd = right_prompt; + + // Don't keep history. + reader_push(parser, wcstring{}, std::move(conf)); + reader_get_history()->resolve_pending(); reader_set_buffer(commandline, std::wcslen(commandline)); scoped_push interactive{&parser.libdata().is_interactive, true}; diff --git a/src/reader.cpp b/src/reader.cpp index ebca0151b..07ae7d1d1 100644 --- a/src/reader.cpp +++ b/src/reader.cpp @@ -452,6 +452,8 @@ struct readline_loop_state_t; /// reader_readline() calls are nested. This happens when the 'read' builtin is used. class reader_data_t : public std::enable_shared_from_this { public: + /// Configuration for the reader. + const reader_config_t conf; /// The parser being used. std::shared_ptr parser_ref; /// String containing the whole current commandline. @@ -466,14 +468,8 @@ class reader_data_t : public std::enable_shared_from_this { pager_t pager; /// Current page rendering. page_rendering_t current_page_rendering; - /// Whether autosuggesting is allowed at all. - bool allow_autosuggestion{false}; /// When backspacing, we temporarily suppress autosuggestions. bool suppress_autosuggestion{false}; - /// Whether abbreviations are expanded. - bool expand_abbreviations{false}; - /// Silent mode used for password input on the read command - bool silent{false}; /// The representation of the current screen contents. screen_t screen; /// The source of input events. @@ -490,9 +486,6 @@ class reader_data_t : public std::enable_shared_from_this { size_t sel_start_pos{0}; /// The stop position of the current selection, if one. size_t sel_stop_pos{0}; - /// The prompt commands. - wcstring left_prompt; - wcstring right_prompt; /// The output of the last evaluation of the prompt command. wcstring left_prompt_buff; wcstring mode_prompt_buff; @@ -506,12 +499,6 @@ class reader_data_t : public std::enable_shared_from_this { std::vector colors; /// An array defining the block level at each character. std::vector indents; - /// Whether tab completion is allowed. - bool complete_ok{false}; - /// Whether to perform syntax highlighting. - bool highlight_ok{false}; - /// Whether to perform fish syntax checking. - bool syntax_check_ok{false}; /// If this is true, exit reader even if there are running jobs. This happens if we press e.g. /// ^D twice. bool prev_end_loop{false}; @@ -522,8 +509,6 @@ class reader_data_t : public std::enable_shared_from_this { bool repaint_needed{false}; /// Whether a screen reset is needed after a repaint. bool screen_reset_needed{false}; - /// Whether the reader should exit on ^C. - bool exit_on_interrupt{false}; /// The target character of the last jump command. wchar_t last_jump_target{0}; jump_direction_t last_jump_direction{jump_direction_t::forward}; @@ -563,8 +548,11 @@ class reader_data_t : public std::enable_shared_from_this { /// Access the parser. parser_t &parser() { return *parser_ref; } - reader_data_t(std::shared_ptr parser, history_t *hist) - : parser_ref(std::move(parser)), inputter(*parser_ref), history(hist) {} + reader_data_t(std::shared_ptr parser, history_t *hist, reader_config_t &&conf) + : conf(std::move(conf)), + parser_ref(std::move(parser)), + inputter(*parser_ref), + history(hist) {} void update_buff_pos(editable_line_t *el, maybe_t new_pos = none_t()); void repaint(); @@ -593,7 +581,8 @@ class reader_data_t : public std::enable_shared_from_this { bool can_autosuggest() const; void autosuggest_completed(autosuggestion_result_t result); void update_autosuggestion(); - void accept_autosuggestion(bool full, bool single = false, move_word_style_t style = move_word_style_punctuation); + void accept_autosuggestion(bool full, bool single = false, + move_word_style_t style = move_word_style_punctuation); void super_highlight_me_plenty(int highlight_pos_adjust = 0, bool no_io = false); void highlight_search(); @@ -818,7 +807,7 @@ void reader_data_t::repaint() { indents = parse_util_compute_indents(cmd_line->text()); wcstring full_line; - if (silent) { + if (conf.in_silent_mode) { full_line = wcstring(cmd_line->text().length(), get_obfuscation_read_char()); } else { // Combine the command and autosuggestion into one string. @@ -993,7 +982,7 @@ bool reader_data_t::expand_abbreviation_as_necessary(size_t cursor_backtrack) { bool result = false; editable_line_t *el = active_edit_line(); - if (expand_abbreviations && el == &command_line) { + if (conf.expand_abbrev_ok && el == &command_line) { // Try expanding abbreviations. size_t cursor_pos = el->position() - std::min(el->position(), cursor_backtrack); @@ -1088,35 +1077,35 @@ void reader_data_t::exec_prompt() { // Suppress fish_trace while in the prompt. scoped_push in_prompt(&parser().libdata().suppress_fish_trace, true); - // Do not allow the exit status of the prompts to leak through. - const bool apply_exit_status = false; - // Update the termsize now. // This allows prompts to react to $COLUMNS. update_termsize(); // If we have any prompts, they must be run non-interactively. - if (!left_prompt.empty() || !right_prompt.empty()) { + if (!conf.left_prompt_cmd.empty() || !conf.right_prompt_cmd.empty()) { scoped_push noninteractive{&parser().libdata().is_interactive, false}; exec_mode_prompt(); - if (!left_prompt.empty()) { + if (!conf.left_prompt_cmd.empty()) { + // Status is ignored. wcstring_list_t prompt_list; - // Ignore return status. - exec_subshell(left_prompt, parser(), prompt_list, apply_exit_status); - for (size_t i = 0; i < prompt_list.size(); i++) { - if (i > 0) left_prompt_buff += L'\n'; - left_prompt_buff += prompt_list.at(i); - } + // Historic compatibility hack. + // If the left prompt function is deleted, then use a default prompt instead of + // producing an error. + bool left_prompt_deleted = conf.left_prompt_cmd == LEFT_PROMPT_FUNCTION_NAME && + !function_exists(conf.left_prompt_cmd, parser()); + exec_subshell(left_prompt_deleted ? DEFAULT_PROMPT : conf.left_prompt_cmd, parser(), + prompt_list, false); + left_prompt_buff = join_strings(prompt_list, L'\n'); } - if (!right_prompt.empty()) { - wcstring_list_t prompt_list; + if (!conf.right_prompt_cmd.empty()) { // Status is ignored. - exec_subshell(right_prompt, parser(), prompt_list, apply_exit_status); + wcstring_list_t prompt_list; + exec_subshell(conf.right_prompt_cmd, parser(), prompt_list, false); + // Right prompt does not support multiple lines, so just concatenate all of them. for (const auto &i : prompt_list) { - // Right prompt does not support multiple lines, so just concatenate all of them. right_prompt_buff += i; } } @@ -1533,7 +1522,7 @@ bool reader_data_t::can_autosuggest() const { // and our command line contains a non-whitespace character. const editable_line_t *el = active_edit_line(); const wchar_t *whitespace = L" \t\r\n\v"; - return allow_autosuggestion && !suppress_autosuggestion && history_search.is_at_end() && + return conf.autosuggest_ok && !suppress_autosuggestion && history_search.is_at_end() && el == &command_line && el->text().find_first_not_of(whitespace) != wcstring::npos; } @@ -2293,7 +2282,7 @@ static std::function get_highlight_performer(parser_t /// \param no_io if true, do a highlight that does not perform I/O, synchronously. If false, perform /// an asynchronous highlight in the background, which may perform disk I/O. void reader_data_t::super_highlight_me_plenty(int match_highlight_pos_adjust, bool no_io) { - if (!highlight_ok) return; + if (!conf.highlight_ok) return; const editable_line_t *el = &command_line; assert(el != nullptr); @@ -2353,9 +2342,10 @@ void reader_change_history(const wcstring &name) { } } -void reader_push(parser_t &parser, const wcstring &name) { - history_t *hist = &history_t::history_with_name(name); - reader_data_stack.push_back(std::make_shared(parser.shared(), hist)); +void reader_push(parser_t &parser, const wcstring &history_name, reader_config_t &&conf) { + history_t *hist = &history_t::history_with_name(history_name); + reader_data_stack.push_back( + std::make_shared(parser.shared(), hist, std::move(conf))); reader_data_t *data = current_data(); data->command_line_changed(&data->command_line); if (reader_data_stack.size() == 1) { @@ -2377,28 +2367,6 @@ void reader_pop() { } } -void reader_set_left_prompt(const wcstring &new_prompt) { - current_data()->left_prompt = new_prompt; -} - -void reader_set_right_prompt(const wcstring &new_prompt) { - current_data()->right_prompt = new_prompt; -} - -void reader_set_allow_autosuggesting(bool flag) { current_data()->allow_autosuggestion = flag; } - -void reader_set_expand_abbreviations(bool flag) { current_data()->expand_abbreviations = flag; } - -void reader_set_complete_ok(bool flag) { current_data()->complete_ok = flag; } - -void reader_set_highlight_ok(bool flag) { current_data()->highlight_ok = flag; } - -void reader_set_syntax_check_ok(bool flag) { current_data()->syntax_check_ok = flag; } - -void reader_set_exit_on_interrupt(bool i) { current_data()->exit_on_interrupt = i; } - -void reader_set_silent_status(bool flag) { current_data()->silent = flag; } - void reader_import_history_if_necessary() { // Import history from older location (config path) if our current history is empty. reader_data_t *data = current_data(); @@ -2482,12 +2450,26 @@ uint64_t reader_run_count() { return run_count; } /// Read interactively. Read input from stdin while providing editing facilities. static int read_i(parser_t &parser) { - reader_push(parser, history_session_id(parser.vars())); - reader_set_complete_ok(true); - reader_set_highlight_ok(true); - reader_set_syntax_check_ok(true); - reader_set_allow_autosuggesting(true); - reader_set_expand_abbreviations(true); + reader_config_t conf; + conf.complete_ok = true; + conf.highlight_ok = true; + conf.syntax_check_ok = true; + conf.autosuggest_ok = true; + conf.expand_abbrev_ok = true; + + if (parser.libdata().is_breakpoint && function_exists(DEBUG_PROMPT_FUNCTION_NAME, parser)) { + conf.left_prompt_cmd = DEBUG_PROMPT_FUNCTION_NAME; + } else { + conf.left_prompt_cmd = LEFT_PROMPT_FUNCTION_NAME; + } + + if (function_exists(RIGHT_PROMPT_FUNCTION_NAME, parser)) { + conf.right_prompt_cmd = RIGHT_PROMPT_FUNCTION_NAME; + } else { + conf.right_prompt_cmd = wcstring{}; + } + + reader_push(parser, history_session_id(parser.vars()), std::move(conf)); reader_import_history_if_necessary(); reader_data_t *data = current_data(); @@ -2496,20 +2478,6 @@ static int read_i(parser_t &parser) { while (!shell_is_exiting()) { run_count++; - if (parser.libdata().is_breakpoint && function_exists(DEBUG_PROMPT_FUNCTION_NAME, parser)) { - reader_set_left_prompt(DEBUG_PROMPT_FUNCTION_NAME); - } else if (function_exists(LEFT_PROMPT_FUNCTION_NAME, parser)) { - reader_set_left_prompt(LEFT_PROMPT_FUNCTION_NAME); - } else { - reader_set_left_prompt(DEFAULT_PROMPT); - } - - if (function_exists(RIGHT_PROMPT_FUNCTION_NAME, parser)) { - reader_set_right_prompt(RIGHT_PROMPT_FUNCTION_NAME); - } else { - reader_set_right_prompt(L""); - } - // Put buff in temporary string and clear buff, so that we can handle a call to // reader_set_buffer during evaluation. maybe_t tmp = reader_readline(0); @@ -2756,7 +2724,7 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat } case rl::complete: case rl::complete_and_search: { - if (!complete_ok) break; + if (!conf.complete_ok) break; // Use the command line only; it doesn't make sense to complete in any other line. editable_line_t *el = &command_line; @@ -2996,7 +2964,7 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat // See if this command is valid. parser_test_error_bits_t command_test_result = 0; - if (syntax_check_ok) { + if (conf.syntax_check_ok) { command_test_result = reader_shell_test(parser(), el->text()); } if (command_test_result == 0 || command_test_result == PARSER_TEST_INCOMPLETE) { @@ -3006,7 +2974,7 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat if (abbreviation_expanded) { // It's our reponsibility to rehighlight and repaint. But everything we do // below triggers a repaint. - if (syntax_check_ok) { + if (conf.syntax_check_ok) { command_test_result = reader_shell_test(parser(), el->text()); } @@ -3022,7 +2990,7 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat // Finished command, execute it. Don't add items that start with a leading // space, or if in silent mode (#7230). const editable_line_t *el = &command_line; - if (history != nullptr && !silent && may_add_to_history(el->text())) { + if (history != nullptr && !conf.in_silent_mode && may_add_to_history(el->text())) { history->add_pending_with_file_detection(el->text(), vars.get_pwd_slash()); } rls.finished = true; @@ -3779,7 +3747,7 @@ bool reader_has_pager_contents() { int reader_reading_interrupted() { int res = reader_test_and_clear_interrupted(); reader_data_t *data = current_data_or_null(); - if (res && data && data->exit_on_interrupt) { + if (res && data && data->conf.exit_on_interrupt) { reader_set_end_loop(true); // We handled the interrupt ourselves, our caller doesn't need to handle it. return 0; diff --git a/src/reader.h b/src/reader.h index dba1061e0..be468084a 100644 --- a/src/reader.h +++ b/src/reader.h @@ -207,38 +207,43 @@ int reader_reading_interrupted(); /// commandline. maybe_t reader_readline(int nchars); -/// Push a new reader environment. -void reader_push(parser_t &parser, const wcstring &name); +/// Configuration that we provide to a reader. +struct reader_config_t { + /// Left prompt command, typically fish_prompt. + wcstring left_prompt_cmd{}; + + /// Right prompt command, typically fish_right_prompt. + wcstring right_prompt_cmd{}; + + /// Whether tab completion is OK. + bool complete_ok{false}; + + /// Whether to perform syntax highlighting. + bool highlight_ok{false}; + + /// Whether to perform syntax checking before returning. + bool syntax_check_ok{false}; + + /// Whether to allow autosuggestions. + bool autosuggest_ok{false}; + + /// Whether to expand abbreviations. + bool expand_abbrev_ok{false}; + + /// Whether to exit on interrupt (^C). + bool exit_on_interrupt{false}; + + /// If set, do not show what is typed. + bool in_silent_mode{false}; +}; + +/// Push a new reader environment controlled by \p conf. +/// If \p history_name is not empty, then use the history with that name. +void reader_push(parser_t &parser, const wcstring &history_name, reader_config_t &&conf); /// Return to previous reader environment. void reader_pop(); -/// Mark whether tab completion is enabled. -void reader_set_complete_ok(bool flag); - -/// Mark whether syntax highlighting is enabled. -void reader_set_highlight_ok(bool flag); - -/// Mark whether to check syntax. -void reader_set_syntax_check_ok(bool flag); - -/// Specify string of shell commands to be run in order to generate the prompt. -void reader_set_left_prompt(const wcstring &prompt); - -/// Specify string of shell commands to be run in order to generate the right prompt. -void reader_set_right_prompt(const wcstring &prompt); - -/// Sets whether autosuggesting is allowed. -void reader_set_allow_autosuggesting(bool flag); - -/// Sets whether abbreviation expansion is performed. -void reader_set_expand_abbreviations(bool flag); - -/// Sets whether the reader should exit on ^C. -void reader_set_exit_on_interrupt(bool i); - -void reader_set_silent_status(bool f); - /// Returns true if the shell is exiting, 0 otherwise. bool shell_is_exiting();