#include <stic.h> #include <test-utils.h> #include "../../src/cfg/config.h" #include "../../src/compat/curses.h" #include "../../src/compat/os.h" #include "../../src/engine/keys.h" #include "../../src/engine/mode.h" #include "../../src/modes/cmdline.h" #include "../../src/modes/modes.h" #include "../../src/modes/wk.h" #include "../../src/ui/column_view.h" #include "../../src/ui/statusbar.h" #include "../../src/ui/ui.h" #include "../../src/utils/matcher.h" #include "../../src/utils/path.h" #include "../../src/utils/str.h" #include "../../src/builtin_functions.h" #include "../../src/cmd_core.h" #include "../../src/event_loop.h" #include "../../src/filelist.h" #include "../../src/flist_hist.h" #include "../../src/status.h" static void prompt_callback(const char response[], void *arg); static void column_line_print(const char buf[], int offset, AlignType align, const char full_column[], const format_info_t *info); static line_stats_t *stats; static char *prompt_response; static int prompt_invocation_count; SETUP_ONCE() { stats = get_line_stats(); try_enable_utf8_locale(); init_builtin_functions(); } SETUP() { modes_init(); curr_view = &lwin; other_view = &rwin; view_setup(&lwin); view_setup(&rwin); } TEARDOWN() { view_teardown(&lwin); view_teardown(&rwin); (void)vle_keys_exec_timed_out(WK_C_c); vle_keys_reset(); cfg.inc_search = 0; } TEST(expr_reg_completion) { (void)vle_keys_exec_timed_out(L":" WK_C_r WK_EQUALS); (void)vle_keys_exec_timed_out(L"ex" WK_C_i); assert_wstring_equal(L"executable(", stats->line); (void)vle_keys_exec_timed_out(WK_C_c); } TEST(expr_reg_completion_ignores_pipe) { (void)vle_keys_exec_timed_out(L":" WK_C_r WK_EQUALS); (void)vle_keys_exec_timed_out(L"ab|ex" WK_C_i); assert_wstring_equal(L"ab|ex", stats->line); (void)vle_keys_exec_timed_out(WK_C_c); } TEST(prompt_cb_is_called_on_success) { update_string(&prompt_response, NULL); prompt_invocation_count = 0; modcline_prompt("(prompt)", "initial", &prompt_callback, /*cb_arg=*/NULL, /*complete=*/NULL); assert_true(vle_mode_is(CMDLINE_MODE)); assert_int_equal(CLS_PROMPT, stats->sub_mode); (void)vle_keys_exec_timed_out(WK_CR); assert_string_equal("initial", prompt_response); assert_int_equal(1, prompt_invocation_count); } TEST(prompt_cb_is_called_on_cancellation) { update_string(&prompt_response, NULL); prompt_invocation_count = 0; modcline_prompt("(prompt)", "initial", &prompt_callback, /*cb_arg=*/NULL, /*complete=*/NULL); assert_true(vle_mode_is(CMDLINE_MODE)); assert_int_equal(CLS_PROMPT, stats->sub_mode); (void)vle_keys_exec_timed_out(WK_C_c); assert_string_equal(NULL, prompt_response); assert_int_equal(1, prompt_invocation_count); } TEST(user_prompt_accepts_input) { cfg.timeout_len = 1; ui_sb_msg(""); /* Preparing input beforehand, because input() runs nested event loop. */ feed_keys(L"suffix" WK_CR); (void)vle_keys_exec_timed_out(L":echo input('prompt', 'input')" WK_CR); assert_string_equal("inputsuffix", ui_sb_last()); } TEST(user_prompt_handles_cancellation) { cfg.timeout_len = 1; ui_sb_msg("old"); /* Preparing input beforehand, because input() runs nested event loop. */ feed_keys(L"suffix" WK_C_c); (void)vle_keys_exec_timed_out(L":echo input('prompt', 'input')" WK_CR); assert_string_equal("", ui_sb_last()); } TEST(user_prompt_nests) { cfg.timeout_len = 1; ui_sb_msg(""); /* Preparing input beforehand, because input() runs nested event loop. */ feed_keys(L"-" WK_CR L"*" WK_CR); (void)vle_keys_exec_timed_out( L":echo input('p2', input('p1', '1').'2')" WK_CR); assert_string_equal("1-2*", ui_sb_last()); } TEST(user_prompt_and_expr_reg) { cfg.timeout_len = 1; ui_sb_msg(""); /* Preparing input beforehand, because input() runs nested event loop. */ feed_keys(WK_C_r WK_EQUALS "input('n')" WK_CR L"nested" WK_CR L"extra" WK_CR); (void)vle_keys_exec_timed_out(L":echo input('p').'out'" WK_CR); assert_string_equal("nestedextraout", ui_sb_last()); } TEST(user_prompt_completion) { cfg.timeout_len = 1; ui_sb_msg(""); make_abs_path(curr_view->curr_dir, sizeof(curr_view->curr_dir), TEST_DATA_PATH, "", NULL); /* Preparing input beforehand, because input() runs nested event loop. */ feed_keys(WK_C_i WK_CR); (void)vle_keys_exec_timed_out(L":echo input('p', 'read/dos', 'dir')" WK_CR); assert_string_equal("read/dos", ui_sb_last()); /* Preparing input beforehand, because input() runs nested event loop. */ feed_keys(WK_C_i WK_CR); (void)vle_keys_exec_timed_out(L":echo input('p', 'read/dos', 'file')" WK_CR); assert_string_equal("read/dos-eof", ui_sb_last()); } TEST(each_filtering_prompt_gets_clean_state) { conf_setup(); cfg.inc_search = 1; /* In order to handle input change, old input must be stored somewhere and * compared against the new state. Make sure the storage is cleared on every * prompt. */ (void)vle_keys_exec_timed_out(L"=a"); assert_string_equal("a", curr_view->local_filter.filter.raw); (void)vle_keys_exec_timed_out(WK_ESC); assert_string_equal("", curr_view->local_filter.filter.raw); (void)vle_keys_exec_timed_out(L"=a"); assert_string_equal("a", curr_view->local_filter.filter.raw); (void)vle_keys_exec_timed_out(WK_ESC); conf_teardown(); } TEST(cmdline_navigation) { /* This doesn't work outside of search and local filter submodes. */ make_abs_path(curr_view->curr_dir, sizeof(curr_view->curr_dir), TEST_DATA_PATH, "tree", NULL); (void)vle_keys_exec_timed_out(L":"); (void)vle_keys_exec_timed_out(WK_C_y); assert_false(stats->navigating); (void)vle_keys_exec_timed_out(WK_C_o); assert_string_equal("tree", get_last_path_component(curr_view->curr_dir)); } TEST(navigation_requires_interactivity) { (void)vle_keys_exec_timed_out(L"/"); cfg.inc_search = 0; (void)vle_keys_exec_timed_out(WK_C_y); assert_false(stats->navigating); cfg.inc_search = 1; (void)vle_keys_exec_timed_out(WK_C_y); assert_true(stats->navigating); (void)vle_keys_exec_timed_out(WK_C_y); assert_false(stats->navigating); } TEST(navigation_movement) { conf_setup(); cfg.inc_search = 1; make_abs_path(curr_view->curr_dir, sizeof(curr_view->curr_dir), TEST_DATA_PATH, "read", NULL); populate_dir_list(curr_view, /*reload=*/0); (void)vle_keys_exec_timed_out(L"/"); (void)vle_keys_exec_timed_out(WK_C_y); assert_string_equal("binary-data", curr_view->dir_entry[curr_view->list_pos].name); (void)vle_keys_exec_timed_out(WK_C_n); assert_string_equal("dos-eof", curr_view->dir_entry[curr_view->list_pos].name); (void)vle_keys_exec_timed_out(WK_C_n); assert_string_equal("dos-line-endings", curr_view->dir_entry[curr_view->list_pos].name); (void)vle_keys_exec_timed_out(WK_C_p); assert_string_equal("dos-eof", curr_view->dir_entry[curr_view->list_pos].name); #ifdef ENABLE_EXTENDED_KEYS wchar_t keys[2] = { }; keys[0] = K(KEY_UP); (void)vle_keys_exec_timed_out(keys); assert_string_equal("binary-data", curr_view->dir_entry[curr_view->list_pos].name); keys[0] = K(KEY_DOWN); (void)vle_keys_exec_timed_out(keys); assert_string_equal("dos-eof", curr_view->dir_entry[curr_view->list_pos].name); keys[0] = K(KEY_HOME); (void)vle_keys_exec_timed_out(keys); assert_string_equal("binary-data", curr_view->dir_entry[curr_view->list_pos].name); keys[0] = K(KEY_END); (void)vle_keys_exec_timed_out(keys); assert_string_equal("very-long-line", curr_view->dir_entry[curr_view->list_pos].name); keys[0] = K(KEY_LEFT); (void)vle_keys_exec_timed_out(keys); assert_string_equal("test-data", get_last_path_component(curr_view->curr_dir)); keys[0] = K(KEY_RIGHT); (void)vle_keys_exec_timed_out(keys); assert_string_equal("read", get_last_path_component(curr_view->curr_dir)); /* Setup for scrolling. */ curr_view->window_rows = 5; setup_grid(curr_view, /*column_count=*/1, curr_view->list_rows, /*init=*/0); curr_view->top_line = 1; curr_view->list_pos = curr_view->list_rows - 1; keys[0] = K(KEY_PPAGE); (void)vle_keys_exec_timed_out(keys); assert_string_equal("dos-line-endings", curr_view->dir_entry[curr_view->list_pos].name); keys[0] = K(KEY_NPAGE); (void)vle_keys_exec_timed_out(keys); assert_string_equal("two-lines", curr_view->dir_entry[curr_view->list_pos].name); #endif conf_teardown(); } TEST(search_navigation) { conf_setup(); histories_init(5); cfg.inc_search = 1; cfg.wrap_scan = 1; make_abs_path(curr_view->curr_dir, sizeof(curr_view->curr_dir), TEST_DATA_PATH, "tree", NULL); populate_dir_list(curr_view, /*reload=*/0); (void)vle_keys_exec_timed_out(L"/" WK_C_y); /* Can enter and leave directories. */ (void)vle_keys_exec_timed_out(L"5" WK_C_m); assert_string_equal("dir5", get_last_path_component(curr_view->curr_dir)); (void)vle_keys_exec_timed_out(WK_C_o); assert_string_equal("tree", get_last_path_component(curr_view->curr_dir)); (void)vle_keys_exec_timed_out(L"1" WK_C_m); assert_string_equal("dir1", get_last_path_component(curr_view->curr_dir)); (void)vle_keys_exec_timed_out(L"2" WK_C_m); assert_string_equal("dir2", get_last_path_component(curr_view->curr_dir)); assert_true(hist_is_empty(&curr_stats.search_hist)); cfg.wrap_scan = 0; histories_init(0); conf_teardown(); } /* Same as search_navigation test above. Duplicating because filtering is more * complicated and it's a good idea to verify it also works fine. */ TEST(filter_navigation) { conf_setup(); histories_init(5); cfg.inc_search = 1; cfg.wrap_scan = 1; make_abs_path(curr_view->curr_dir, sizeof(curr_view->curr_dir), TEST_DATA_PATH, "tree", NULL); populate_dir_list(curr_view, /*reload=*/0); (void)vle_keys_exec_timed_out(L"=" WK_C_y); /* Can enter and leave directories. */ (void)vle_keys_exec_timed_out(L"5" WK_C_m); assert_string_equal("dir5", get_last_path_component(curr_view->curr_dir)); (void)vle_keys_exec_timed_out(WK_C_o); assert_string_equal("tree", get_last_path_component(curr_view->curr_dir)); (void)vle_keys_exec_timed_out(L"1" WK_C_m); assert_string_equal("dir1", get_last_path_component(curr_view->curr_dir)); (void)vle_keys_exec_timed_out(L"2" WK_C_m); assert_string_equal("dir2", get_last_path_component(curr_view->curr_dir)); assert_true(hist_is_empty(&curr_stats.filter_hist)); cfg.wrap_scan = 0; histories_init(0); conf_teardown(); } TEST(can_leave_navigation_without_running) { conf_setup(); cfg.inc_search = 1; cfg.wrap_scan = 1; make_abs_path(curr_view->curr_dir, sizeof(curr_view->curr_dir), TEST_DATA_PATH, "tree", NULL); populate_dir_list(curr_view, /*reload=*/0); (void)vle_keys_exec_timed_out(L"=" WK_C_y L"h" WK_C_j); assert_int_equal(1, curr_view->list_rows); cfg.wrap_scan = 0; conf_teardown(); } TEST(normal_in_autocmd_does_not_break_filter_navigation) { conf_setup(); assert_success(stats_init(&cfg)); cfg.inc_search = 1; assert_success(cmds_dispatch1("autocmd DirEnter * normal ga", curr_view, CIT_COMMAND)); make_abs_path(curr_view->curr_dir, sizeof(curr_view->curr_dir), TEST_DATA_PATH, "tree", NULL); populate_dir_list(curr_view, /*reload=*/0); (void)vle_keys_exec_timed_out(L"=" WK_C_y WK_C_m); assert_string_equal("", curr_view->local_filter.filter.raw); assert_success(cmds_dispatch1("autocmd!", curr_view, CIT_COMMAND)); wait_for_bg(); conf_teardown(); } TEST(filter_in_autocmd_does_not_break_filter_navigation) { opt_handlers_setup(); histories_init(5); cfg.inc_search = 1; cfg.auto_ch_pos = 1; cfg.ch_pos_on = CHPOS_ENTER; columns_setup_column(SK_BY_NAME); columns_setup_column(SK_BY_SIZE); columns_set_line_print_func(&column_line_print); curr_view->columns = columns_create(); assert_success(cmds_dispatch1("autocmd DirEnter tree/ filter! dir", curr_view, CIT_COMMAND)); make_abs_path(curr_view->curr_dir, sizeof(curr_view->curr_dir), TEST_DATA_PATH, "tree", NULL); populate_dir_list(curr_view, /*reload=*/0); assert_int_equal(3, curr_view->list_rows); curr_view->list_pos = 2; flist_hist_save(curr_view); curr_stats.load_stage = 2; (void)vle_keys_exec_timed_out(L"h/" WK_C_y WK_C_m); /* This shouldn't lead to assertion/segfault caused by cursor position being * outside of file list. */ (void)vle_keys_exec_timed_out(L"d"); curr_stats.load_stage = 0; assert_int_equal(1, curr_view->list_rows); /* Position of the filtered list is the one being stored. */ assert_int_equal(0, stats->old_top); assert_int_equal(0, stats->old_pos); (void)vle_keys_exec_timed_out(WK_C_c); assert_success(cmds_dispatch1("autocmd!", curr_view, CIT_COMMAND)); opt_handlers_teardown(); columns_teardown(); histories_init(0); cfg.auto_ch_pos = 0; cfg.ch_pos_on = 0; } TEST(filtering_does_not_result_in_invalid_position) { conf_setup(); histories_init(5); cfg.inc_search = 1; make_abs_path(curr_view->curr_dir, sizeof(curr_view->curr_dir), TEST_DATA_PATH, "tree", NULL); populate_dir_list(curr_view, /*reload=*/0); assert_int_equal(3, curr_view->list_rows); flist_hist_save(curr_view); (void)vle_keys_exec_timed_out(L"=5" WK_C_m); assert_int_equal(1, curr_view->list_rows); (void)vle_keys_exec_timed_out(L"=" WK_C_u WK_C_y); assert_int_equal(3, curr_view->list_rows); /* This shouldn't cause a crash because of incorrect cursor position (outside * of file list). */ (void)vle_keys_exec_timed_out(WK_C_o); (void)vle_keys_exec_timed_out(WK_C_c); opt_handlers_teardown(); columns_teardown(); histories_init(0); conf_teardown(); } TEST(leaving_navigation_does_not_move_cursor) { conf_setup(); cfg.inc_search = 1; make_abs_path(curr_view->curr_dir, sizeof(curr_view->curr_dir), TEST_DATA_PATH, "existing-files", NULL); populate_dir_list(curr_view, /*reload=*/0); /* Search. */ curr_view->list_pos = 0; (void)vle_keys_exec_timed_out(L"/"); /* Empty input. */ (void)vle_keys_exec_timed_out(WK_C_y); assert_int_equal(0, curr_view->list_pos); (void)vle_keys_exec_timed_out(WK_C_n); assert_int_equal(1, curr_view->list_pos); (void)vle_keys_exec_timed_out(WK_C_y); assert_int_equal(1, curr_view->list_pos); /* This triggers view cursor update. */ (void)vle_keys_exec_timed_out(L"a" WK_C_h); assert_int_equal(1, curr_view->list_pos); /* Non-empty input. */ (void)vle_keys_exec_timed_out(WK_C_y L"a"); assert_int_equal(1, curr_view->list_pos); (void)vle_keys_exec_timed_out(WK_C_n); assert_int_equal(2, curr_view->list_pos); (void)vle_keys_exec_timed_out(WK_C_y); assert_int_equal(2, curr_view->list_pos); /* This triggers view cursor update. */ (void)vle_keys_exec_timed_out(L"a" WK_C_h); assert_int_equal(2, curr_view->list_pos); (void)vle_keys_exec_timed_out(WK_C_c); /* Filter. */ curr_view->list_pos = 0; (void)vle_keys_exec_timed_out(L"="); /* Empty input. */ (void)vle_keys_exec_timed_out(WK_C_y); assert_int_equal(0, curr_view->list_pos); (void)vle_keys_exec_timed_out(WK_C_n); assert_int_equal(1, curr_view->list_pos); (void)vle_keys_exec_timed_out(WK_C_y); assert_int_equal(1, curr_view->list_pos); /* This triggers view cursor update. */ (void)vle_keys_exec_timed_out(L"a" WK_C_h); assert_int_equal(1, curr_view->list_pos); /* Non-empty input. */ (void)vle_keys_exec_timed_out(WK_C_y L"."); assert_int_equal(1, curr_view->list_pos); (void)vle_keys_exec_timed_out(WK_C_n); assert_int_equal(2, curr_view->list_pos); (void)vle_keys_exec_timed_out(WK_C_y); assert_int_equal(2, curr_view->list_pos); /* This triggers view cursor update. */ (void)vle_keys_exec_timed_out(L"a" WK_C_h); assert_int_equal(2, curr_view->list_pos); (void)vle_keys_exec_timed_out(WK_C_c); conf_teardown(); } TEST(navigation_preserves_input_on_enter_failure, IF(regular_unix_user)) { conf_setup(); create_dir(SANDBOX_PATH "/dir"); assert_success(os_chmod(SANDBOX_PATH "/dir", 0000)); cfg.inc_search = 1; make_abs_path(curr_view->curr_dir, sizeof(curr_view->curr_dir), SANDBOX_PATH, "", NULL); populate_dir_list(curr_view, /*reload=*/0); (void)vle_keys_exec_timed_out(L"/" WK_C_y L"di" WK_C_m); assert_wstring_equal(L"di", stats->line); (void)vle_keys_exec_timed_out(WK_C_c); remove_dir(SANDBOX_PATH "/dir"); conf_teardown(); } TEST(navigation_opens_files, IF(not_windows)) { conf_setup(); cfg.inc_search = 1; cfg.nav_open_files = 1; stats_init(&cfg); create_executable(SANDBOX_PATH "/script"); make_file(SANDBOX_PATH "/script", "#!/bin/sh\n" "touch " SANDBOX_PATH "/out"); create_file(SANDBOX_PATH "/in"); char vi_cmd[PATH_MAX + 1]; make_abs_path(vi_cmd, sizeof(vi_cmd), SANDBOX_PATH, "script", NULL); update_string(&cfg.vi_command, vi_cmd); make_abs_path(curr_view->curr_dir, sizeof(curr_view->curr_dir), SANDBOX_PATH, "", NULL); populate_dir_list(curr_view, /*reload=*/0); /* This should create "out" file. */ (void)vle_keys_exec_timed_out(L"/" WK_C_y WK_C_m); update_string(&cfg.vi_command, NULL); remove_file(SANDBOX_PATH "/in"); remove_file(SANDBOX_PATH "/out"); remove_file(SANDBOX_PATH "/script"); conf_teardown(); cfg.nav_open_files = 0; } TEST(search_match_navigation) { conf_setup(); make_abs_path(curr_view->curr_dir, sizeof(curr_view->curr_dir), TEST_DATA_PATH, "read", NULL); populate_dir_list(curr_view, /*reload=*/0); (void)vle_keys_exec_timed_out(L"/"); assert_int_equal(0, curr_view->list_pos); /* Regular search mode, non-incremental search. */ cfg.inc_search = 0; (void)vle_keys_exec_timed_out(L"li"); assert_int_equal(0, curr_view->list_pos); (void)vle_keys_exec_timed_out(WK_C_i); assert_int_equal(0, curr_view->list_pos); (void)vle_keys_exec_timed_out(WK_C_u); /* Regular search mode, incremental search. */ cfg.inc_search = 1; /* No matches, no cursor movement. */ (void)vle_keys_exec_timed_out(WK_C_i); assert_int_equal(0, curr_view->list_pos); (void)vle_keys_exec_timed_out(L"li"); assert_int_equal(2, curr_view->list_pos); (void)vle_keys_exec_timed_out(WK_C_i); assert_int_equal(3, curr_view->list_pos); (void)vle_keys_exec_timed_out(WK_C_i); assert_int_equal(5, curr_view->list_pos); /* Wrap scan is disabled, so the cursor isn't moved. */ (void)vle_keys_exec_timed_out(WK_C_i); assert_int_equal(5, curr_view->list_pos); /* Can go back as well. */ (void)vle_keys_exec_timed_out(WK_ESC L"[Z"); assert_int_equal(3, curr_view->list_pos); /* Navigation search mode, incremental search, wrap scan. */ cfg.wrap_scan = 1; (void)vle_keys_exec_timed_out(WK_C_y); assert_int_equal(3, curr_view->list_pos); (void)vle_keys_exec_timed_out(WK_ESC L"[Z"); assert_int_equal(2, curr_view->list_pos); /* Wrapping. */ (void)vle_keys_exec_timed_out(WK_ESC L"[Z"); assert_int_equal(5, curr_view->list_pos); (void)vle_keys_exec_timed_out(WK_ESC); conf_teardown(); cfg.wrap_scan = 0; } TEST(wild_inc_completion) { char *error; cfg.wild_inc = matcher_alloc_glob(":plugin", &error); assert_string_equal(NULL, error); /* A :command that's auto-completed. */ (void)vle_keys_exec_timed_out(L":plugin"); assert_false(stats->inc_completion); (void)vle_keys_exec_timed_out(L" "); assert_true(stats->inc_completion); /* Verify that slash isn't treated specially as it is for user-initiated * completion. */ (void)vle_keys_exec_timed_out(L"/"); assert_wstring_equal(L"plugin /", stats->line); (void)vle_keys_exec_timed_out(L"/"); assert_wstring_equal(L"plugin //", stats->line); (void)vle_keys_exec_timed_out(WK_C_c); /* A :command that's not auto-completed. */ (void)vle_keys_exec_timed_out(L":set"); assert_false(stats->inc_completion); (void)vle_keys_exec_timed_out(L" "); assert_false(stats->inc_completion); matcher_free(cfg.wild_inc); cfg.wild_inc = NULL; } static void prompt_callback(const char response[], void *arg) { update_string(&prompt_response, response); ++prompt_invocation_count; } static void column_line_print(const char buf[], int offset, AlignType align, const char full_column[], const format_info_t *info) { /* Do nothing. */ } /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */ /* vim: set cinoptions+=t0 : */